package com.framsticks.params; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.framsticks.params.annotations.FramsClassAnnotation; import com.framsticks.params.annotations.ParamAnnotation; import com.framsticks.params.types.ProcedureParam; // import com.framsticks.util.FramsticksException; public class ParamCandidate { public class OneTime { protected final String name; T value; /** * @param name */ public OneTime(String name) { this.name = name; } final void set(T value) { if (this.value == null) { this.value = value; return; } if (!this.value.equals(value)) { throw new ConstructionException().msg("already set") .arg("name", name) .arg("in", ParamCandidate.this) .arg("already", this.value) .arg("now", value); } } public final T get() { return value; } public final boolean has() { return value != null; } @Override public String toString() { return value == null ? "" : value.toString(); } } protected final String id; protected final OneTime name = new OneTime<>("name"); protected final OneTime type = new OneTime<>("type"); protected final OneTime field = new OneTime<>("field"); protected final OneTime setter = new OneTime<>("setter"); protected final OneTime getter = new OneTime<>("getter"); protected final OneTime caller = new OneTime<>("caller"); protected final OneTime adder = new OneTime<>("adder"); protected final OneTime remover = new OneTime<>("remover"); protected final OneTime> paramType = new OneTime<>("paramType"); protected final OneTime stringType = new OneTime<>("stringType"); protected int flags = 0; protected final List annotations = new LinkedList<>(); /** * @param id */ public ParamCandidate(String id) { this.id = id; } /** * @return the id */ public String getId() { return id; } /** * @return the name */ public String getName() { return name.get(); } /** * @return the type */ public Type getType() { return type.get(); } public Class getRawType() { return getRawClass(type.get()); } void setType(Type type) { this.type.set(type); } /** * @return the field */ public Field getField() { return field.get(); } /** * @return the setter */ public Method getSetter() { return setter.get(); } /** * @return the getter */ public Method getGetter() { return getter.get(); } /** * @return the getter */ public Method getCaller() { return caller.get(); } /** * @return the getter */ public Method getAdder() { return adder.get(); } /** * @return the getter */ public Method getRemover() { return remover.get(); } /** * @return the annotations */ public List getAnnotations() { return Collections.unmodifiableList(annotations); } protected final java.util.Set> dependantClasses = new HashSet<>(); // public void addDependantClass(Class javaClass) { // dependantClasses.add(javaClass); // } /** * @return the dependantClasses */ public java.util.Set> getDependantClasses() { return Collections.unmodifiableSet(dependantClasses); } void validate() throws ConstructionException { try { if (adder.has() != remover.has()) { throw new ConstructionException().msg("only one of event manipulator methods is defined"); } if (adder.has() && remover.has()) { return; } if (caller.has()) { if (!isPublic(caller)) { throw new ConstructionException().msg("method is not public"); } if (getter.has() || setter.has()) { throw new ConstructionException().msg("getter or setter coexist with caller"); } return; } if (isPublic(field)) { if (getter.has()) { throw new ConstructionException().msg("getter and public field coexist"); } return; } if (isPublic(field)) { if (setter.has()) { throw new ConstructionException().msg("setter and field coexist"); } } if (!getter.has() && !field.has()) { throw new ConstructionException().msg("missing getter or field"); } if (getter.has() || field.has() || setter.has()) { if (type.get().equals(Void.TYPE)) { throw new ConstructionException().msg("type of field is void"); } } } catch (ConstructionException e) { throw e.arg("in", this); } } boolean isFinal() { if (caller.has()) { return false; } if (adder.has() || remover.has()) { return false; } if (field.has()) { return Modifier.isFinal(field.get().getModifiers()); } if (setter.has()) { return false; } if (Collection.class.isAssignableFrom(getRawType())) { return false; } return true; } boolean isReadOnly() { if (caller.has()) { return false; } if (adder.has() || remover.has()) { return false; } if (Collection.class.isAssignableFrom(getRawType())) { return false; } if (isPublic(setter)) { return false; } if (isPublic(field)) { return Modifier.isFinal(field.get().getModifiers()); } return true; } // public static boolean isParamAnnorationOfTypeOrUnspecified(ParamAnnotation paramAnnotation, Class paramType) { // return paramAnnotation.paramType().equals(Param.class) || paramAnnotation.paramType().equals(paramType); // } // public static boolean isParamAnnorationOfType(ParamAnnotation paramAnnotation, Class paramType) { // return paramAnnotation.paramType().equals(paramType); // } void add(ParamAnnotation paramAnnotation, Member member, String name) { this.name.set(name); annotations.add(paramAnnotation); flags |= paramAnnotation.flags(); if (!paramAnnotation.paramType().equals(Param.class)) { paramType.set(paramAnnotation.paramType()); } if (!"".equals(paramAnnotation.stringType())) { stringType.set(paramAnnotation.stringType()); } if (member instanceof Field) { field.set((Field) member); setType(field.get().getGenericType()); return; } if (member instanceof Method) { Method m = (Method) member; if (paramAnnotation.paramType().equals(ProcedureParam.class)) { caller.set(m); return; } Type[] ps = m.getGenericParameterTypes(); Class[] pts = m.getParameterTypes(); if (ps.length == 0) { if (m.getReturnType().equals(Void.TYPE)) { throw new ConstructionException().msg("failed to add getter of void return type"); } getter.set(m); setType(m.getGenericReturnType()); return; } if (ps.length == 1) { if (pts[0].equals(EventListener.class)) { if (member.getName().startsWith("add")) { adder.set(m); setType(ps[0]); return; } if (member.getName().startsWith("remove")) { remover.set(m); setType(ps[0]); return; } throw new ConstructionException().msg("invalid name of event manipulator").arg("method", m).arg("in", this); } setter.set(m); setType(ps[0]); return; } throw new ConstructionException().msg("invalid number of arguments").arg("method", m).arg("in", this); } throw new ConstructionException().msg("invalid kind of member").arg("member", member).arg("in", this); } public boolean isPrimitive() { return getRawType().isPrimitive(); } public int getFlags() { int f = flags; if (isReadOnly()) { f |= ParamFlags.READONLY; } return f; } @Override public String toString() { return id + "(" + type.toString() + ")"; } public static boolean isPublic(Member member) { return Modifier.isPublic(member.getModifiers()); } public static boolean isPublic(OneTime v) { return v.has() ? isPublic(v.get()) : false; } public static void filterParamsCandidates(Set set, M[] members) { for (M m : members) { ParamAnnotation pa = m.getAnnotation(ParamAnnotation.class); if (pa == null) { continue; } String id = FramsClassBuilder.getId(pa, m); ParamCandidate pc = null; if (set.getCandidates().containsKey(id)) { pc = set.getCandidates().get(id); } else { pc = new ParamCandidate(id); set.getCandidates().put(id, pc); set.getOrder().add(pc); } pc.add(pa, m, FramsClassBuilder.getName(pa, m)); } } public static class Set { protected final Map candidates; protected final List order; protected final java.util.Set> dependantClasses = new HashSet<>(); protected final java.util.Set dependantClassesFromInfo = new HashSet<>(); /** * @param candidates * @param order */ public Set(Map candidates, List order) { this.candidates = candidates; this.order = order; } /** * @return the candidates */ public Map getCandidates() { return candidates; } /** * @return the order */ public List getOrder() { return order; } public java.util.Set> getDependentClasses() { return dependantClasses; } public java.util.Set getDependentClassesFromInfo() { return dependantClassesFromInfo; } } protected static final Map, Set> setsCache = Collections.synchronizedMap(new IdentityHashMap, Set>()); public static Set getAllCandidates(final Class javaClass) throws ConstructionException { Set result = setsCache.get(javaClass); if (result != null) { return result; } List> javaClasses = new LinkedList<>(); Class jc = javaClass; while (jc != null) { javaClasses.add(0, jc); jc = jc.getSuperclass(); } result = new Set(new HashMap(), new LinkedList()); for (Class j : javaClasses) { Set set = new Set(result.getCandidates(), new LinkedList()); filterParamsCandidates(set, j.getDeclaredFields()); filterParamsCandidates(set, j.getDeclaredMethods()); FramsClassAnnotation fa = j.getAnnotation(FramsClassAnnotation.class); if (fa != null) { if (j != javaClass) { result.dependantClasses.add(j); } for (Class r : fa.register()) { result.dependantClasses.add(r); } for (String i : fa.registerFromInfo()) { result.dependantClassesFromInfo.add(i); } final List order = Arrays.asList(fa.order()); Collections.sort(set.getOrder(), new Comparator() { @Override public int compare(ParamCandidate pc0, ParamCandidate pc1) { int u0 = order.indexOf(pc0.getId()); int u1 = order.indexOf(pc1.getId()); if (u0 == -1 || u1 == -1) { return 0; } return u0 - u1; } }); } result.getOrder().addAll(0, set.getOrder()); } for (ParamCandidate pc : result.getOrder()) { pc.validate(); pc.induceParamType(Param.build()); result.dependantClasses.addAll(pc.getDependantClasses()); } setsCache.put(javaClass, result); return result; } public static Class getRawClass(final Type type) { if (type == null) { throw new IllegalArgumentException(); } if (Class.class.isInstance(type)) { return Class.class.cast(type); } if (ParameterizedType.class.isInstance(type)) { final ParameterizedType parameterizedType = ParameterizedType.class.cast(type); return getRawClass(parameterizedType.getRawType()); } else if (GenericArrayType.class.isInstance(type)) { GenericArrayType genericArrayType = GenericArrayType.class.cast(type); Class c = getRawClass(genericArrayType.getGenericComponentType()); return Array.newInstance(c, 0).getClass(); } else { return null; } } protected ParamBuilder induceParamType(ParamBuilder builder, Type type) { // if (type.equals(Void.TYPE)) { // throw new ConstructionException().msg("void is not a valid type"); // } if (type instanceof ParameterizedType) { ParameterizedType p = (ParameterizedType) type; Type rawType = p.getRawType(); Type containedType = null; boolean map = false; StringBuilder b = new StringBuilder(); if (rawType.equals(Map.class)) { containedType = p.getActualTypeArguments()[1]; map = true; b.append("l"); } else if (rawType.equals(List.class)) { containedType = p.getActualTypeArguments()[0]; b.append("l"); } else if (rawType.equals(EventListener.class)) { containedType = p.getActualTypeArguments()[0]; b.append("e"); } else { return induceParamType(builder, rawType); // throw new FramsticksException().msg("unknown raw type").arg("raw type", rawType); } if (!(containedType instanceof Class)) { return builder; } b.append(" "); Class containedClass = (Class) containedType; FramsClassAnnotation fca = containedClass.getAnnotation(FramsClassAnnotation.class); if (fca == null) { throw new ConstructionException().msg("the contained class is not annotated").arg("class", containedClass); } dependantClasses.add(containedClass); b.append(FramsClassBuilder.getName(fca, containedClass)); if (map) { b.append(" uid"); } builder.type(b.toString()); return builder; } if (type instanceof Class) { Class cl = (Class) type; // this is draft implementation of future support for enum // if (cl.isEnum()) { // Class> enumType = (Class>) cl; // Enum[] enums = enumType.getEnumConstants(); // StringBuilder b = new StringBuilder(); // b.append("d 0 ").append(enums.length - 1).append(" 0 "); // for (Enum e : enums) { // b.append("~").append(e.name()); // } // return b.toString(); // } if (cl.equals(Integer.class) || cl.equals(int.class)) { builder.type("d"); return builder; } if (cl.equals(String.class)) { builder.type("s"); return builder; } if (cl.equals(Double.class) || cl.equals(double.class)) { builder.type("f"); return builder; } if (cl.equals(Boolean.class) || cl.equals(boolean.class)) { builder.type( "d 0 1"); return builder; } if (cl.equals(Object.class)) { builder.type("x"); return builder; } // builder.type("o " + (cl).getCanonicalName()); builder.type("o " + cl.getSimpleName()); dependantClasses.add(cl); builder.fillStorageType(cl); return builder; } throw new ConstructionException().msg("failed to find framsticks for native type").arg("type", type); } public ParamBuilder induceParamType(ParamBuilder builder) { if (stringType.has()) { return builder.type(stringType.get()); } Method method = getCaller(); if (method == null) { if (paramType.has()) { return builder.type(paramType.get()); } return induceParamType(builder, getType()); } if (!method.getReturnType().equals(Void.TYPE)) { builder.resultType(induceParamType(Param.build(), method.getGenericReturnType()).finish(ValueParam.class)); } List arguments = new ArrayList<>(); int number = 0; for (Type arg : method.getGenericParameterTypes()) { arguments.add(induceParamType(Param.build(), arg).idAndName("arg" + (number++)).finish(ValueParam.class)); } builder.argumentsType(arguments); return builder; } };