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.List; import java.util.Map; import javax.annotation.concurrent.Immutable; import org.apache.log4j.Logger; import com.framsticks.params.annotations.AutoAppendAnnotation; 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 = Logger.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; } protected final Map setters = new HashMap<>(); protected final Map getters = new HashMap<>(); protected final Map callers = new HashMap<>(); 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 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()); } 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 ReflectionAccess(Class reflectedClass) throws ConstructionException { this(reflectedClass, FramsClass.build().forClass(reflectedClass)); } public static boolean typeMatch(Class a, Class b) { assert !b.isPrimitive(); 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); } assert false; return false; } public ReflectionAccess(Class reflectedClass, FramsClass framsClass) throws ConstructionException { super(framsClass); this.javaClass = reflectedClass; this.backend = Backend.getOrCreateFor(reflectedClass, framsClass); } @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"); } 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 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) { assert object == null || javaClass.isInstance(object); this.object = object; 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 ReflectionAccess cloneAccess() throws ConstructionException { return new ReflectionAccess(javaClass, framsClass); } @Override public Object createAccessee() { try { return javaClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } log.fatal("failed to create reflected object of class " + javaClass.getCanonicalName() + " for frams type " + framsClass.getId()); return null; } @Override public String toString() { StringBuilder b = new StringBuilder(); b.append(framsClass); if (object != null) { b.append("(").append(object).append(")"); } return b.toString(); } @Override public boolean tryAutoAppend(Object value) { assert object != null; for (Method m : backend.autoAppendMethods) { if (m.getParameterTypes()[0].isAssignableFrom(value.getClass())) { try { log.trace("auto appending with value " + value + " with method " + m); m.invoke(object, value); return true; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | FramsticksException e) { throw new FramsticksException().msg("failed to auto append").cause(e).arg("value", value).arg("into object", object).arg("with method", m); } } } return false; } }