package com.framsticks.params; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import javax.annotation.concurrent.Immutable; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import com.framsticks.params.annotations.AutoAppendAnnotation; import com.framsticks.params.types.EventParam; import com.framsticks.params.types.ProcedureParam; import com.framsticks.util.FramsticksException; import com.framsticks.util.lang.Pair; import static com.framsticks.util.lang.Containers.*; /** * The Class ReflectionAccess. Stores data in provided object using reflection. * * @author Mateusz Jarus (please replace name and * surname with my personal data) * * @author Piotr Sniegowski */ public class ReflectionAccess extends SimpleAbstractAccess { private final static Logger log = LogManager.getLogger(ReflectionAccess.class.getName()); protected final Class javaClass; protected final Backend backend; private Object object; @Immutable public static class Backend { protected static final Map, FramsClass>, Backend> synchronizedCache = Collections.synchronizedMap(new HashMap, FramsClass>, Backend>()); public interface ReflectedGetter { public T get(Object object, Class type) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException; } public interface ReflectedSetter { public void set(Object object, T value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException; } public interface ReflectedCaller { public Object call(Object object, Object[] arguments) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException; } public interface ReflectedAdder{ public void reg(Object object, EventListener listener) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException; } public interface ReflectedRemover{ public void regRemove(Object object, EventListener listener) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException; } protected final Map setters = new IdentityHashMap<>(); protected final Map getters = new IdentityHashMap<>(); protected final Map callers = new IdentityHashMap<>(); protected final Map adders = new IdentityHashMap<>(); protected final Map removers = new IdentityHashMap<>(); protected final List autoAppendMethods = new ArrayList<>(); /** * @param params */ public Backend() { } public static Backend getOrCreateFor(Class reflectedClass, FramsClass framsClass) { Pair, FramsClass> id = new Pair, FramsClass>(reflectedClass, framsClass); Backend backend = synchronizedCache.get(id); if (backend != null) { return backend; } log.debug("constructing backend for {}", id); backend = new Backend(); Map candidates = ParamCandidate.getAllCandidates(reflectedClass).getCandidates(); try { for (final ProcedureParam pp : filterInstanceof(framsClass.getParamEntries(), ProcedureParam.class)) { if (!candidates.containsKey(pp.getId())) { log.trace("java class does implement method {}", pp); continue; } ParamCandidate pc = candidates.get(pp.getId()); final Method method = pc.getCaller(); backend.callers.put(pp, new ReflectedCaller() { @Override public Object call(Object object, Object[] arguments) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { return method.invoke(object, arguments); } }); } for (final EventParam ep : filterInstanceof(framsClass.getParamEntries(), EventParam.class)) { if (!candidates.containsKey(ep.getId())) { log.trace("java class does not implement the event param {}", ep); continue; } ParamCandidate ec = candidates.get(ep.getId()); final Method adder = ec.getAdder(); final Method remover = ec.getRemover(); backend.adders.put(ep, new ReflectedAdder() { @Override public void reg(Object object, EventListener listener) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { adder.invoke(object, listener); } }); backend.removers.put(ep, new ReflectedRemover() { @Override public void regRemove(Object object, EventListener listener) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { remover.invoke(object, listener); } }); } 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(ParamFlags.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()); } final boolean primitive = pc.isPrimitive(); if (pc.getField() != null) { final Field f = pc.getField(); backend.getters.put(vp, new ReflectedGetter() { @Override public T get(Object object, Class type) throws IllegalArgumentException, IllegalAccessException { return type.cast(f.get(object)); } }); if (!pc.isFinal()) { backend.setters.put(vp, new ReflectedSetter() { @Override public 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(); backend.getters.put(vp, new ReflectedGetter() { @Override public T get(Object object, Class type) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { return type.cast(g.invoke(object)); } }); if (!pc.isFinal()) { final Method s = pc.getSetter(); backend.setters.put(vp, new ReflectedSetter() { @Override public 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); } 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); } backend.autoAppendMethods.add(m); } javaClass = javaClass.getSuperclass(); } Collections.sort(backend.autoAppendMethods, new Comparator() { @Override public int compare(Method m0, Method m1) { Class arg0 = m0.getParameterTypes()[0]; Class arg1 = m1.getParameterTypes()[0]; if (arg0.isAssignableFrom(arg1)) { return 1; } if (arg1.isAssignableFrom(arg0)) { return -1; } return 0; } }); synchronizedCache.put(id, backend); return backend; } } public static boolean typeMatch(Class a, Class b) { if (b.isPrimitive()) { throw new FramsticksException().msg("failed to match type, right argument is primitive").arg("left", a).arg("right", b); } if (!a.isPrimitive()) { return a.equals(b); } if (a.equals(int.class)) { return b.equals(Integer.class); } if (a.equals(double.class)) { return b.equals(Double.class); } if (a.equals(boolean.class)) { return b.equals(Boolean.class); } throw new FramsticksException().msg("failed to match types").arg("left", a).arg("right", b); } public ReflectionAccess(Class javaClass) throws ConstructionException { this(javaClass, FramsClass.build().forClass(javaClass)); } public ReflectionAccess(Class javaClass, FramsClass framsClass) throws ConstructionException { this(javaClass, framsClass, Backend.getOrCreateFor(javaClass, framsClass)); } protected ReflectionAccess(Class javaClass, FramsClass framsClass, Backend backend) throws ConstructionException { super(framsClass); this.javaClass = javaClass; this.backend = backend; } @Override public ReflectionAccess cloneAccess() throws ConstructionException { return new ReflectionAccess(javaClass, framsClass, backend); } @Override public T get(ValueParam param, Class type) { try { try { if (object == null) { throw new FramsticksException().msg("no object set"); } return backend.getters.get(param).get(object, type); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new FramsticksException().msg("failed to get").cause(e); } } catch (FramsticksException e) { throw e.arg("param", param).arg("type", type).arg("access", this); } } @Override protected void internalSet(ValueParam param, T value) { setValue(param, value); } private void setValue(ValueParam param, T value) { try { try { if (object == null) { throw new FramsticksException().msg("no object set"); } Backend.ReflectedSetter s = backend.setters.get(param); if (s == null) { throw new FramsticksException().msg("trying to set unsettable"); // return; // if (value != backend.getters.get(param).get(object, Object.class)) { // } } s.set(object, value); } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { throw new FramsticksException().msg("failed to set").cause(e); } } catch (FramsticksException e) { throw e.arg("param", param).arg("value", value).arg("access", this); } } @Override public void reg(EventParam param, EventListener listener) { try { try { if (object == null) { throw new FramsticksException().msg("no object set"); } backend.adders.get(param).reg(object, listener); return; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new FramsticksException().msg("failed to add listener").cause(e); } } catch (FramsticksException e) { throw e.arg("param", param).arg("access", this); } } @Override public void regRemove(EventParam param, EventListener listener) { try { try { if (object == null) { throw new FramsticksException().msg("no object set"); } backend.removers.get(param).regRemove(object, listener); return; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new FramsticksException().msg("failed to remove listener").cause(e); } } catch (FramsticksException e) { throw e.arg("param", param).arg("access", this); } } @Override public Object call(String id, Object[] arguments) { return call(framsClass.getParamEntry(id, ProcedureParam.class), arguments); } @Override public Object call(ProcedureParam param, Object[] arguments) { try { try { if (object == null) { throw new FramsticksException().msg("no object set"); } Backend.ReflectedCaller c = backend.callers.get(param); if (c == null) { throw new FramsticksException().msg("method is not bound"); } return c.call(object, arguments); } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { throw new FramsticksException().msg("failed to call").cause(e); } } catch (FramsticksException e) { throw e.arg("param", param).arg("access", this); } } void resetErrors() { //TODO this replaces returnedObject.resetErrors(); } @Override public void clearValues() { if (object == null) { return; } resetErrors(); try { for (ValueParam p : filterInstanceof(framsClass.getParamEntries(), ValueParam.class)) { setValue(p, p.getDef(Object.class)); } } catch (IllegalArgumentException ex) { ex.printStackTrace(); } } /** * Sets the new object to operate on. * * @param object * new object to operate on */ @Override public ReflectionAccess select(Object object) { this.object = Util.selectObjectForAccess(this, object, javaClass); return this; } @Override public Object getSelected() { return object; } // TODO: find a better place for it public static String objectToString(Object object) { StringBuilder b = new StringBuilder(); for (Field f : object.getClass().getFields()) { b.append(f.getName()); b.append(":"); try { Object value = f.get(object); b.append((value != null) ? value.toString() : ""); } catch (IllegalAccessException e) { e.printStackTrace(); } b.append("\n"); } return b.toString(); } @Override public Object createAccessee() { try { return javaClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } log.fatal("failed to create reflected object of class {} for frams type {}", javaClass.getCanonicalName(), framsClass.getId()); return null; } @Override public void tryAutoAppend(Object value) { assert object != null; try { for (Method m : backend.autoAppendMethods) { if (m.getParameterTypes()[0].isAssignableFrom(value.getClass())) { try { log.trace("auto appending with value {} with method {}", value, m); m.invoke(object, value); return; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | FramsticksException e) { throw new FramsticksException().msg("failed to auto append").cause(e).arg("with method", m); } } } throw new FramsticksException().msg("no method found to append"); } catch (FramsticksException e) { throw e.arg("value", value).arg("into object", object); } } @Override public String toString() { StringBuilder b = new StringBuilder(); b.append(framsClass); if (getSelected() != null) { b.append("(").append(getSelected()).append(")"); } return b.toString(); } }