Index: java/main/src/main/java/com/framsticks/params/ReflectionAccess.java
===================================================================
--- java/main/src/main/java/com/framsticks/params/ReflectionAccess.java	(revision 86)
+++ java/main/src/main/java/com/framsticks/params/ReflectionAccess.java	(revision 87)
@@ -4,10 +4,15 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
+import javax.annotation.concurrent.Immutable;
+
 import org.apache.log4j.Logger;
 
+import com.framsticks.params.annotations.AutoAppendAnnotation;
 import com.framsticks.util.FramsticksException;
+import com.framsticks.util.lang.Pair;
 
 import static com.framsticks.util.lang.Containers.*;
@@ -22,28 +27,146 @@
  */
 public class ReflectionAccess extends SimpleAbstractAccess {
-	private final static Logger log = Logger.getLogger(ReflectionAccess.class
-			.getName());
+	private final static Logger log = Logger.getLogger(ReflectionAccess.class.getName());
 
 	protected final Class<?> reflectedClass;
+	protected final Backend backend;
+
 	private Object object;
 
-
-	protected interface ReflectedSetter {
-		public <T> void set(Object object, T value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException;
-	}
-
-	protected interface ReflectedGetter {
-		public abstract <T> T get(Object object, Class<T> type) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException;
-	}
-
-	protected static class ReflectedValueParam {
-		public ReflectedSetter setter;
-		public ReflectedGetter getter;
-	}
-
-	protected final Map<ValueParam, ReflectedValueParam> reflectedValueParams = new HashMap<>();
+	@Immutable
+	public static class Backend {
+
+		protected static Map<Pair<Class<?>, FramsClass>, Backend> synchronizedCache = Collections.synchronizedMap(new HashMap<Pair<Class<?>, FramsClass>, Backend>());
+
+		public static class ReflectedValueParam {
+			public ReflectedSetter setter;
+			public ReflectedGetter getter;
+		}
+
+		public interface ReflectedSetter {
+			public <T> void set(Object object, T value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException;
+		}
+
+		public interface ReflectedGetter {
+			public abstract <T> T get(Object object, Class<T> type) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException;
+		}
+
+		protected final Map<ValueParam, ReflectedValueParam> params;
+		protected final Map<Class<?>, Method> autoAppendMethods;
+
+		/**
+		 * @param params
+		 */
+		public Backend(Map<ValueParam, ReflectedValueParam> params, Map<Class<?>, Method> autoAppendMethods) {
+			// this.params = Collections.unmodifiableMap(params);
+			this.params = params;
+			this.autoAppendMethods = autoAppendMethods;
+		}
+
+		public static Backend getOrCreateFor(Class<?> reflectedClass, FramsClass framsClass) {
+
+			Pair<Class<?>, FramsClass> id = new Pair<Class<?>, FramsClass>(reflectedClass, framsClass);
+			Backend backend = synchronizedCache.get(id);
+			if (backend != null) {
+				return backend;
+			}
+
+			log.debug("constructing backend for " + id);
+			final Map<ValueParam, ReflectedValueParam> params = new HashMap<>();
+
+			Map<String, ParamCandidate> candidates = ParamCandidate.getAllCandidates(reflectedClass);
+
+			try {
+				for (final ValueParam vp : filterInstanceof(framsClass.getParamEntries(), ValueParam.class)) {
+					if (!candidates.containsKey(vp.getId())) {
+						throw new ConstructionException().msg("missing candidate for param").arg("param", vp);
+					}
+					ParamCandidate pc = candidates.get(vp.getId());
+					if (pc.isReadOnly() && !vp.hasFlag(Flags.READONLY)) {
+						throw new ConstructionException().msg("readonly state conflict").arg("param", vp);
+					}
+					if (!typeMatch(pc.getRawType(), vp.getStorageType())) {
+						throw new ConstructionException().msg("types mismatch for param").arg("param", vp).arg("candidate", pc.getType()).arg("storage", vp.getStorageType());
+					}
+
+					ReflectedValueParam rvp = new ReflectedValueParam();
+					params.put(vp, rvp);
+					final boolean primitive = pc.isPrimitive();
+					if (pc.getField() != null) {
+						final Field f = pc.getField();
+						rvp.getter = new ReflectedGetter() {
+							@Override
+							public <T> T get(Object object, Class<T> type) throws IllegalArgumentException, IllegalAccessException {
+								return type.cast(f.get(object));
+							}
+						};
+						if (!pc.isFinal()) {
+							rvp.setter = new ReflectedSetter() {
+								@Override
+								public <T> void set(Object object, T value) throws IllegalArgumentException, IllegalAccessException {
+									if (value == null && primitive) {
+										throw new FramsticksException().msg("setting null to primitive value");
+									}
+									f.set(object, value);
+								}
+							};
+						}
+					} else {
+						final Method g = pc.getGetter();
+
+						rvp.getter = new ReflectedGetter() {
+							@Override
+							public <T> T get(Object object, Class<T> type) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+								return type.cast(g.invoke(object));
+							}
+						};
+
+						if (!pc.isFinal()) {
+							final Method s = pc.getSetter();
+							rvp.setter = new ReflectedSetter() {
+								@Override
+								public <T> void set(Object object, T value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+									if (value == null && primitive) {
+										throw new FramsticksException().msg("setting null to primitive value");
+									}
+									s.invoke(object, value);
+								}
+							};
+						}
+					}
+				}
+			} catch (ConstructionException e) {
+				throw e.arg("java class", reflectedClass).arg("framsClass", framsClass);
+			}
+
+			Map<Class<?>, Method> autoAppendMethods = new HashMap<>();
+
+			Class<?> javaClass = reflectedClass;
+			while (javaClass != null) {
+
+				for (Method m : javaClass.getDeclaredMethods()) {
+					AutoAppendAnnotation a = m.getAnnotation(AutoAppendAnnotation.class);
+					if (a == null) {
+						continue;
+					}
+					Class<?>[] args = m.getParameterTypes();
+					if (args.length != 1) {
+						throw new ConstructionException().msg("invalid number of arguments in AutoAppend marked method").arg("method", m).arg("arguments", args.length);
+					}
+					autoAppendMethods.put(args[0], m);
+				}
+
+				javaClass = javaClass.getSuperclass();
+			}
+
+			backend = new Backend(params, autoAppendMethods);
+			synchronizedCache.put(id, backend);
+			return backend;
+		}
+
+	}
 
 	public ReflectionAccess(Class<?> reflectedClass) throws ConstructionException {
-		this(reflectedClass, FramsClassBuilder.buildForClass(reflectedClass));
+		this(reflectedClass, FramsClass.build().forClass(reflectedClass));
 	}
 
@@ -68,71 +191,8 @@
 
 	public ReflectionAccess(Class<?> reflectedClass, FramsClass framsClass) throws ConstructionException {
+		super(framsClass);
 		this.reflectedClass = reflectedClass;
-		setFramsClass(framsClass);
-
-		Map<String, ParamCandidate> candidates = ParamCandidate.getAllCandidates(reflectedClass);
-
-		try {
-			for (final ValueParam vp : filterInstanceof(framsClass.getParamEntries(), ValueParam.class)) {
-				if (!candidates.containsKey(vp.getId())) {
-					throw new ConstructionException().msg("missing candidate for param").arg("param", vp);
-				}
-				ParamCandidate pc = candidates.get(vp.getId());
-				if (pc.isReadOnly() && !vp.hasFlag(Flags.READONLY)) {
-					throw new ConstructionException().msg("readonly state conflict").arg("param", vp);
-				}
-				if (!typeMatch(pc.getRawType(), vp.getStorageType())) {
-					throw new ConstructionException().msg("types mismatch for param").arg("param", vp).arg("candidate", pc.getType()).arg("storage", vp.getStorageType());
-				}
-
-				ReflectedValueParam rvp = new ReflectedValueParam();
-				reflectedValueParams.put(vp, rvp);
-				final boolean primitive = pc.isPrimitive();
-				if (pc.getField() != null) {
-					final Field f = pc.getField();
-					rvp.getter = new ReflectedGetter() {
-						@Override
-						public <T> T get(Object object, Class<T> type) throws IllegalArgumentException, IllegalAccessException {
-							return type.cast(f.get(object));
-						}
-					};
-					if (!pc.isFinal()) {
-						rvp.setter = new ReflectedSetter() {
-							@Override
-							public <T> void set(Object object, T value) throws IllegalArgumentException, IllegalAccessException {
-								if (value == null && primitive) {
-									throw new FramsticksException().msg("setting null to primitive value");
-								}
-								f.set(object, value);
-							}
-						};
-					}
-				} else {
-					final Method g = pc.getGetter();
-
-					rvp.getter = new ReflectedGetter() {
-						@Override
-						public <T> T get(Object object, Class<T> type) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
-							return type.cast(g.invoke(object));
-						}
-					};
-
-					if (!pc.isFinal()) {
-						final Method s = pc.getSetter();
-						rvp.setter = new ReflectedSetter() {
-							@Override
-							public <T> void set(Object object, T value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
-								if (value == null && primitive) {
-									throw new FramsticksException().msg("setting null to primitive value");
-								}
-								s.invoke(object, value);
-							}
-						};
-					}
-				}
-			}
-		} catch (ConstructionException e) {
-			throw e.arg("java class", reflectedClass).arg("framsClass", framsClass);
-		}
+		this.backend = Backend.getOrCreateFor(reflectedClass, framsClass);
+		// log.info("created ReflectionAccess " + this);
 	}
 
@@ -149,5 +209,5 @@
 				}
 
-				return reflectedValueParams.get(param).getter.get(object, type);
+				return backend.params.get(param).getter.get(object, type);
 			} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
 				throw new FramsticksException().msg("failed to get").cause(e);
@@ -169,5 +229,5 @@
 					throw new FramsticksException().msg("no object set");
 				}
-				ReflectedSetter s = reflectedValueParams.get(param).setter;
+				Backend.ReflectedSetter s = backend.params.get(param).setter;
 				if (s == null) {
 					throw new FramsticksException().msg("trying to set final");
@@ -238,5 +298,4 @@
 	}
 
-
 	@Override
 	public ReflectionAccess cloneAccess() throws ConstructionException {
@@ -265,4 +324,19 @@
 	}
 
+	@Override
+	public boolean tryAutoAppend(Object value) {
+		assert object != null;
+		for (Map.Entry<Class<?>, Method> a : backend.autoAppendMethods.entrySet()) {
+			if (a.getKey().isAssignableFrom(value.getClass())) {
+				try {
+					a.getValue().invoke(object, value);
+					return true;
+				} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+					throw new FramsticksException().msg("failed to auto append").cause(e).arg("value", value).arg("into object", object).arg("with method", a.getValue());
+				}
+			}
+		}
+		return false;
+	}
 }
 
