package org.xpect.state; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.xtext.util.Pair; import org.eclipse.xtext.util.Tuples; import org.xpect.text.CharSequences; import com.google.common.base.Joiner; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; public class ResolvedConfiguration { protected static class ConstructorComparator implements Comparator<Constructor<?>> { private final Map<Class<?>, Integer> depth = Maps.newHashMap(); public int compare(Constructor<?> o1, Constructor<?> o2) { Class<?>[] p1 = o1.getParameterTypes(); Class<?>[] p2 = o2.getParameterTypes(); int r = p2.length - p1.length; if (r != 0) return r; for (int i = 0; i < p1.length; i++) { int d = depth(p2[i]) - depth(p1[i]); if (d != 0) return d; } return 0; } protected int depth(Class<?> cls) { if (cls == null) return 0; Integer integer = depth.get(cls); if (integer != null) return integer; integer = depth(cls.getSuperclass()); for (Class<?> iface : cls.getInterfaces()) { Integer d = depth(iface); if (d > integer) integer = d; } return integer + 1; } } public static class DerivedValue extends Value { private Factory factory; private Method invalidator; private final Method method; private final Class<?> owner; public DerivedValue(Class<?> owner, Method method, Class<?> returnType) { super(returnType); this.owner = owner; this.method = method; } public Class<? extends Annotation> getAnnotatedWith() { return method.getAnnotation(Creates.class).value(); } public Factory getFactory() { return factory; } public Method getInvalidator() { return invalidator; } public Method getMethod() { return method; } public Class<?> getOwner() { return owner; } @Override public String toString() { Class<? extends Annotation> annotatedWith = getAnnotatedWith(); String an = annotatedWith != Default.class ? "@" + annotatedWith.getSimpleName() + " " : ""; String m = owner.getSimpleName() + "." + method.getName() + "()"; String t = getType() != null ? getType().getSimpleName() : "(unresolved)"; return getClass().getSimpleName() + "[" + an + t + " " + m + "]"; } } public static class Factory { private final Constructor<?> constructor; private final Value[] in; private final Collection<DerivedValue> out; public Factory(Constructor<?> constructor, Value[] in, Collection<DerivedValue> out) { super(); this.constructor = constructor; this.in = in; this.out = Collections.unmodifiableCollection(out); } public Constructor<?> getConstructor() { return constructor; } public Value[] getIn() { return in; } public Collection<DerivedValue> getOut() { return out; } public Class<?> getOwner() { return constructor.getDeclaringClass(); } public boolean isResolved() { for (Value i : in) if (i == null) return false; return true; } @Override public String toString() { List<String> f = Lists.newArrayList(); for (int i = 0; i < in.length; i++) { if (in == null) { f.add("unresolved " + constructor.getParameterTypes()[i]); } else { f.add(" in " + in); } } for (Value out : getOut()) f.add(" out " + out); Collections.sort(f); return getConstructor() + " {\n " + Joiner.on('\n').join(f) + "\n}"; } } public static class PrimaryValue extends Value { private final Class<? extends Annotation> annotatedWith; private final Managed<?> value; public PrimaryValue(Class<? extends Annotation> annotatedWith, Class<?> type, Managed<?> value) { super(type); this.annotatedWith = annotatedWith; this.value = value; } @Override public Class<? extends Annotation> getAnnotatedWith() { return annotatedWith; } public Managed<?> getValue() { return value; } @Override public String toString() { String an = annotatedWith != Default.class ? "@" + annotatedWith.getSimpleName() + " " : ""; String val = CharSequences.toSingleLineString(value, 80); return getClass().getSimpleName() + "[" + an + getType().getSimpleName() + " " + val + "]"; } } public static abstract class Value { private final Class<?> type; public Value(Class<?> type) { super(); this.type = type; } public abstract Class<? extends Annotation> getAnnotatedWith(); public Class<?> getType() { return type; } } private final List<Throwable> errors; private final String name; private final List<Class<?>> overwrittenFactories; private final ResolvedConfiguration parent; private final List<Factory> resolvedFactories; private final List<Factory> unresolvedFactories; private final Multimap<Class<? extends Annotation>, Value> values; public ResolvedConfiguration(Configuration configuration) { this(null, configuration); } public ResolvedConfiguration(ResolvedConfiguration parent, Configuration configuration) { this.parent = parent; this.errors = Lists.newArrayList(); this.name = configuration.getName(); Set<Class<?>> allFactoryClasses = collectAllFactoryClasses(parent, configuration); Set<Class<?>> overwrittenFactoryClasses = collectOverwrittenClasses(allFactoryClasses); Set<Class<?>> activeFactoryClasses = Sets.difference(allFactoryClasses, overwrittenFactoryClasses); Multimap<Class<? extends Annotation>, Value> allValues = collectAllValues(activeFactoryClasses, configuration); Multimap<Class<?>, DerivedValue> annotatio2value = collectDerivedValuesByFactory(allValues.values()); Set<Factory> allFactories = collectAllFactories(allValues, annotatio2value); Set<Factory> resolvedFactories = collectResolvedFactories(allFactories); Multimap<Class<? extends Annotation>, Value> resolvedValues = collectValues(allValues, resolvedFactories); this.overwrittenFactories = ImmutableList.copyOf(overwrittenFactoryClasses); this.resolvedFactories = ImmutableList.copyOf(resolvedFactories); this.unresolvedFactories = ImmutableList.copyOf(Sets.difference(allFactories, resolvedFactories)); this.values = ImmutableMultimap.copyOf(resolvedValues); } protected void checkMarkerAnnotation(Class<? extends Annotation> annotation) { Retention retention = annotation.getAnnotation(Retention.class); if (retention == null) throw new IllegalStateException("@Retention annotation missing on @" + annotation.getName()); if (retention.value() != RetentionPolicy.RUNTIME) throw new IllegalStateException("RetentionPolicy on @" + annotation.getName() + " must be RUNTIME"); XpectStateAnnotation stateAnnotation = annotation.getAnnotation(XpectStateAnnotation.class); if (stateAnnotation == null) throw new IllegalStateException("@" + XpectStateAnnotation.class.getSimpleName() + " annotation missing on @" + annotation.getName()); } protected Set<Factory> collectAllFactories(Multimap<Class<? extends Annotation>, Value> all, Multimap<Class<?>, DerivedValue> derived) { ConstructorComparator comparator = new ConstructorComparator(); Set<Factory> result = Sets.newLinkedHashSet(); NEXT: for (Class<?> factory : derived.keySet()) { List<Constructor<?>> constructors = Lists.newArrayList(factory.getConstructors()); Collections.sort(constructors, comparator); for (Constructor<?> constructor : constructors) { Value[] in = findParams(all, constructor); Collection<DerivedValue> out = derived.get(factory); Factory fact = new Factory(constructor, in, out); result.add(fact); boolean resolved = true; for (Value i : in) { resolved &= i != null; } if (resolved) { for (DerivedValue m : out) m.factory = fact; continue NEXT; } } } return result; } protected Set<Class<?>> collectAllFactoryClasses(ResolvedConfiguration parent, Configuration cfg) { Set<Class<?>> result = Sets.newLinkedHashSet(); if (parent != null) for (Factory fact : parent.getUnresolvedFactories()) result.add(fact.getOwner()); result.addAll(cfg.getFactories()); return result; } protected Multimap<Class<? extends Annotation>, Value> collectAllValues(Collection<Class<?>> factories2, Configuration config) { Multimap<Class<? extends Annotation>, Value> annotatio2value = LinkedHashMultimap.create(); annotatio2value.put(Default.class, new PrimaryValue(Default.class, StateContainer.class, new ManagedImpl<Object>(null))); for (Map.Entry<Class<? extends Annotation>, Collection<Pair<Class<?>, Managed<?>>>> e : config.getValues().asMap().entrySet()) for (Pair<Class<?>, Managed<?>> value : e.getValue()) { checkMarkerAnnotation(e.getKey()); annotatio2value.put(e.getKey(), new PrimaryValue(e.getKey(), value.getFirst(), value.getSecond())); } for (Class<?> factory : factories2) { boolean found = false; Map<TypeVariable<?>, Class<?>> parameterBindings = getTypeParameterBindings(factory, annotatio2value); Map<Pair<Class<? extends Annotation>, Class<?>>, DerivedValue> values = Maps.newHashMap(); Method[] methods = factory.getMethods(); for (Method m : methods) { Creates annotation = m.getAnnotation(Creates.class); if (annotation != null) { found = true; Class<?> returnType = getReturnType(m, parameterBindings); DerivedValue value = new DerivedValue(factory, m, returnType); checkMarkerAnnotation(annotation.value()); annotatio2value.put(annotation.value(), value); values.put(Tuples.<Class<? extends Annotation>, Class<?>> create(annotation.value(), value.getType()), value); } } if (!found) errors.add(new IllegalStateException("No CreateMethods found in " + factory)); for (Method m : methods) { Invalidates annotation = m.getAnnotation(Invalidates.class); if (annotation != null) { Class<?>[] parameterTypes = m.getParameterTypes(); if (parameterTypes.length != 1) { errors.add(new IllegalStateException("Invalidate-methods must have exactly one parameter. method: " + m)); continue; } DerivedValue value = values.get(Tuples.create(annotation.annotatedWith(), parameterTypes[0])); if (value == null) { errors.add(new IllegalStateException("No @Creates-method found for invalidate-method. method: " + m)); continue; } if (value.invalidator != null) { errors.add(new IllegalStateException("Can not have two invalidate-methods for the same annotation/type. method: " + m)); continue; } value.invalidator = m; } } } return annotatio2value; } protected Multimap<Class<?>, DerivedValue> collectDerivedValuesByFactory(Collection<Value> values) { Multimap<Class<?>, DerivedValue> annotatio2value = LinkedHashMultimap.create(); for (Value val : values) if (val instanceof DerivedValue) { DerivedValue cm = (DerivedValue) val; annotatio2value.put(cm.getOwner(), cm); } return annotatio2value; } protected Set<Class<?>> collectOverwrittenClasses(Set<Class<?>> classes) { Set<Class<?>> superClasses = Sets.newLinkedHashSet(); for (Class<?> c : classes) { Class<?> current = c.getSuperclass(); while (current != Object.class && superClasses.add(current)) current = current.getSuperclass(); } return Sets.intersection(classes, superClasses); } protected Set<Factory> collectResolvedFactories(Collection<Factory> factory) { Set<Factory> result = Sets.newLinkedHashSet(); Map<Factory, Boolean> cache = Maps.newHashMap(); for (Factory fact : factory) if (isResolved(fact, cache)) result.add(fact); return result; } protected Multimap<Class<? extends Annotation>, Value> collectValues(Multimap<Class<? extends Annotation>, Value> all, Set<Factory> factories) { Multimap<Class<? extends Annotation>, Value> result = HashMultimap.create(); for (Value v : all.values()) if (v instanceof PrimaryValue) result.put(v.getAnnotatedWith(), v); else if (v instanceof DerivedValue && factories.contains(((DerivedValue) v).getFactory())) result.put(v.getAnnotatedWith(), v); return result; } protected Value[] findParams(Multimap<Class<? extends Annotation>, Value> annotation2values, Constructor<?> constructor) { Class<?>[] parameterTypes = constructor.getParameterTypes(); Value[] result = new Value[parameterTypes.length]; Annotation[][] parameterAnnotations = constructor.getParameterAnnotations(); for (int i = 0; i < parameterTypes.length; i++) { Collection<Value> vals = findValues(annotation2values, parameterAnnotations[i]); if (vals != null) { Value value = findValue(vals, parameterTypes[i]); if (value == null && parent != null) // FIXME: cleanup value = parent.getValue(parameterAnnotations[i].length > 0 ? parameterAnnotations[i][0].annotationType() : Default.class, parameterTypes[i]); result[i] = value; } } return result; } protected Value findValue(Collection<Value> values, Class<?> expectedType) { for (Value val : values) { Class<?> type = val.getType(); if (type != null && expectedType.isAssignableFrom(type)) return val; } return null; } protected Collection<Value> findValues(Multimap<Class<? extends Annotation>, Value> annotation2values, Annotation[] annotations) { if (annotations.length == 0) return annotation2values.get(Default.class); for (Annotation a : annotations) { Collection<Value> result = annotation2values.get(a.annotationType()); if (result != null) return result; } return null; } public List<Throwable> getErrors() { return errors; } public String getName() { return name; } public List<Class<?>> getOverwrittenFactories() { return overwrittenFactories; } public ResolvedConfiguration getParent() { return parent; } public List<PrimaryValue> getPrimaryValues() { List<PrimaryValue> result = Lists.newArrayList(); for (Value val : values.values()) if (val instanceof PrimaryValue) result.add((PrimaryValue) val); return result; } public List<Factory> getResolvedFactories() { return resolvedFactories; } protected Class<?> getReturnType(Method method, Map<TypeVariable<?>, Class<?>> parameterBindings) { Type genericReturnType = method.getGenericReturnType(); if (genericReturnType instanceof TypeVariable<?>) { Class<?> binding = parameterBindings.get(genericReturnType); return binding; } Class<?> returnType = method.getReturnType(); if (returnType == Managed.class) { ParameterizedType type = (ParameterizedType) method.getGenericReturnType(); return (Class<?>) type.getActualTypeArguments()[0]; } return returnType; } protected Map<TypeVariable<?>, Class<?>> getTypeParameterBindings(Class<?> client, Multimap<Class<? extends Annotation>, Value> annotatio2value) { if (client.getTypeParameters().length == 0) return Collections.emptyMap(); Map<TypeVariable<?>, Class<?>> result = Maps.newHashMap(); for (Constructor<?> c : client.getConstructors()) { Type[] parameterTypes = c.getGenericParameterTypes(); Annotation[][] annotations = c.getParameterAnnotations(); PARAM: for (int i = 0; i < parameterTypes.length; i++) if (parameterTypes[i] instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) parameterTypes[i]; if (pt.getRawType() == Class.class) { Class<? extends Annotation> a = annotations[i].length > 0 ? annotations[i][0].annotationType() : Default.class; for (Type arg : pt.getActualTypeArguments()) if (arg instanceof TypeVariable<?>) { TypeVariable<?> var = (TypeVariable<?>) arg; Class<?> binding = null; for (Value value : annotatio2value.get(a)) if (value instanceof PrimaryValue && value.getType() == Class.class) binding = (Class<?>) ((PrimaryValue) value).getValue().get(); if (binding != null && !binding.getName().startsWith("org.xpect")) { // never bing e.g. XpectInvocation for ? extends EObject. for (Type bound : var.getBounds()) if (!(bound instanceof Class && ((Class<?>) bound).isAssignableFrom(binding))) continue PARAM; result.put(var, binding); } } } } } return result; } public List<Factory> getUnresolvedFactories() { return unresolvedFactories; } public Value getValue(Class<? extends Annotation> annotatedWith, Class<?> expectedType) { for (Value val : values.get(annotatedWith)) if (expectedType == val.getType()) return val; if (parent != null) return parent.getValue(annotatedWith, expectedType); return null; } public Multimap<Class<? extends Annotation>, Value> getValues() { return values; } protected boolean isResolved(Factory fact, Map<Factory, Boolean> cache) { if (fact == null || !fact.isResolved()) return false; Boolean result = cache.get(fact); if (result != null) return result; for (Value in : fact.getIn()) if (in == null || (in instanceof DerivedValue && !isResolved(((DerivedValue) in).getFactory(), cache))) { cache.put(fact, false); return false; } cache.put(fact, true); return true; } @Override public String toString() { List<String> result = Lists.newArrayList(); if (name != null) result.add("------------ " + name + " ------------"); if (!errors.isEmpty()) { result.add("Errors {"); List<String> err = Lists.newArrayList(); for (Throwable error : errors) err.add(" " + error.getMessage()); Collections.sort(err); result.addAll(err); result.add("}"); } result.add("Primary Values {"); List<String> vals = Lists.newArrayList(); for (Value val : values.values()) if (val instanceof PrimaryValue) vals.add(" " + val.toString()); Collections.sort(vals); result.addAll(vals); result.add("}"); result.add("Derived Values {"); for (Factory fact : Iterables.concat(resolvedFactories, unresolvedFactories)) { result.add(" " + (fact.isResolved() ? "" : "UNRESOLVED ") + fact.getConstructor().getName() + " {"); List<String> f = Lists.newArrayList(); Value[] in2 = fact.getIn(); for (int i = 0; i < in2.length; i++) { if (in2[i] == null) { f.add(" in (unresolved " + fact.getConstructor().getParameterTypes()[i] + ")"); } else { f.add(" in " + in2[i]); } } for (Value out : fact.getOut()) f.add(" out " + out); Collections.sort(f); result.addAll(f); result.add(" }"); } result.add("}"); if (parent != null) { result.add(parent.toString()); } return Joiner.on("\n").join(result); } }