package org.xpect.model; import static org.xpect.util.JvmAnnotationUtil.getAnnotationTypeValue; import static org.xpect.util.JvmAnnotationUtil.getAnnotationValue; import static org.xpect.util.JvmAnnotationUtil.getJavaAnnotationsViaMetaAnnotation; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.EMap; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.xtext.common.types.JvmAnnotationReference; import org.eclipse.xtext.common.types.JvmAnnotationTarget; import org.eclipse.xtext.common.types.JvmDeclaredType; import org.eclipse.xtext.common.types.JvmFeature; import org.eclipse.xtext.common.types.JvmFormalParameter; import org.eclipse.xtext.common.types.JvmIdentifiableElement; import org.eclipse.xtext.common.types.JvmOperation; import org.eclipse.xtext.common.types.JvmType; import org.eclipse.xtext.common.types.JvmTypeAnnotationValue; import org.eclipse.xtext.common.types.JvmTypeReference; import org.eclipse.xtext.common.types.JvmVisibility; import org.eclipse.xtext.util.Pair; import org.eclipse.xtext.util.Tuples; import org.junit.Test; import org.junit.runner.RunWith; import org.xpect.Environment; import org.xpect.XjmClass; import org.xpect.XjmContribution; import org.xpect.XjmElement; import org.xpect.XjmMethod; import org.xpect.XjmTest; import org.xpect.XpectContributionRole; import org.xpect.XpectImport; import org.xpect.XpectJavaModelFactory; import org.xpect.XpectReplace; import org.xpect.runner.Xpect; import org.xpect.runner.XpectSuiteClasses; import org.xpect.setup.XpectSetup; import org.xpect.util.JvmAnnotationUtil; import org.xpect.util.JvmTypesUtil; import com.google.common.base.Joiner; import com.google.common.collect.HashMultimap; 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; @SuppressWarnings({ "deprecation" }) public class XpectJavaModelImplCustom extends XpectJavaModelImpl { private boolean contributionsInitialized = false; private boolean methodsInitialized = false; private boolean testsInitalized = false; private void collectContributions(JvmAnnotationTarget ctx, XjmElement owner, Map<String, XjmContribution> contributions, Set<Object> visited) { if (ctx == null || ctx.eIsProxy() || !visited.add(Tuples.create(ctx, owner))) return; XjmContribution contribution = null; if (ctx instanceof JvmDeclaredType) { String qualifiedName = ctx.getQualifiedName(); contribution = contributions.get(qualifiedName); if (contribution == null) { List<? extends Annotation> roles = getJavaAnnotationsViaMetaAnnotation(ctx, XpectContributionRole.class); if (!roles.isEmpty()) { contribution = XpectJavaModelFactory.eINSTANCE.createXjmContribution(); ((XjmContributionImplCustom) contribution).initialize((JvmDeclaredType) ctx, roles); contributions.put(qualifiedName, contribution); } } if (contribution != null && owner != null) owner.getImports().add(contribution); } Set<JvmAnnotationTarget> targets = Sets.newLinkedHashSet(); targets.add(ctx); if (ctx instanceof JvmDeclaredType) for (JvmType superType : JvmTypesUtil.getAllSuperTypes((JvmDeclaredType) ctx)) if (superType instanceof JvmAnnotationTarget) targets.add((JvmAnnotationTarget) superType); Set<JvmDeclaredType> imports = Sets.newLinkedHashSet(); for (JvmAnnotationTarget target : targets) { imports.addAll(getAnnotationTypeValue(target, XpectImport.class)); imports.addAll(getAnnotationTypeValue(target, XpectReplace.class)); imports.addAll(getAnnotationTypeValue(target, XpectSetup.class)); } XjmElement newOwner = contribution == null ? owner : contribution; for (JvmDeclaredType imp : imports) collectContributions(imp, newOwner, contributions, visited); } private void collectContributions(XjmClass owner, Map<String, XjmContribution> contribution, Set<Object> visited) { collectContributions(owner.getJvmClass(), owner, contribution, visited); } private void collectTestClasses(JvmDeclaredType classOrSuite, Map<String, XjmTest> result) { JvmTypeAnnotationValue typeValue = getAnnotationValue(classOrSuite, XpectSuiteClasses.class, JvmTypeAnnotationValue.class); if (typeValue != null) for (JvmTypeReference ref : typeValue.getValues()) if (ref != null && !ref.eIsProxy()) { JvmType jvmType = ref.getType(); if (jvmType instanceof JvmDeclaredType && !jvmType.eIsProxy()) { String name = jvmType.getQualifiedName(); XjmTest cls = result.get(name); if (cls == null) { JvmDeclaredType jvmDeclaredType = (JvmDeclaredType) jvmType; cls = XpectJavaModelFactory.eINSTANCE.createXjmTest(); cls.setJvmClass(jvmDeclaredType); result.put(name, cls); collectTestClasses(jvmDeclaredType, result); } } } } private Multimap<XjmClass, String> computeReverseImports() { Multimap<XjmClass, String> result = LinkedHashMultimap.create(); List<XjmElement> elements = Lists.<XjmElement> newArrayList(getTests()); elements.addAll(getTests()); elements.addAll(getMethods().values()); elements.addAll(getContributions()); for (XjmElement cnt : elements) for (XjmClass imported : cnt.getImports()) result.put(imported, toSimpleName(cnt)); return result; } @Override public EList<XjmContribution> getContributions() { if (!contributionsInitialized) { initContributions(); contributionsInitialized = true; } return super.getContributions(); } @Override public EMap<String, XjmMethod> getMethods() { if (!methodsInitialized) { initTestClassMethods(); methodsInitialized = true; } return super.getMethods(); } @Override public EList<XjmTest> getTests() { if (!testsInitalized) { initTestClasses(); testsInitalized = true; } return super.getTests(); } private void initContributions() { Set<Object> visited = Sets.newHashSet(); Map<String, XjmContribution> contributions = Maps.newLinkedHashMap(); XjmTest root = getTestOrSuite(); List<JvmDeclaredType> runners = JvmAnnotationUtil.getAnnotationTypeValue(root.getJvmClass(), RunWith.class); for (JvmDeclaredType runner : runners) collectContributions(runner, root, contributions, visited); for (XjmTest test : getTests()) { collectContributions(test, contributions, visited); for (XjmMethod method : test.getMethods()) { JvmOperation jvmMethod = method.getJvmMethod(); if (jvmMethod == null || jvmMethod.eIsProxy()) continue; for (JvmAnnotationReference ref : jvmMethod.getAnnotations()) collectContributions(ref.getAnnotation(), method, contributions, visited); for (JvmFormalParameter param : jvmMethod.getParameters()) { for (JvmAnnotationReference ref : param.getAnnotations()) collectContributions(ref.getAnnotation(), method, contributions, visited); JvmTypeReference typeRef = param.getParameterType(); if (typeRef != null && !typeRef.eIsProxy()) { JvmType type = typeRef.getType(); if (type instanceof JvmAnnotationTarget && !type.eIsProxy()) collectContributions((JvmAnnotationTarget) type, method, contributions, visited); } } } } EList<XjmContribution> newContributions = super.getContributions(); newContributions.clear(); newContributions.addAll(contributions.values()); initSubstitutions(newContributions); } private void initSubstitutions(Collection<XjmContribution> contributions) { Map<JvmDeclaredType, XjmContribution> contributionsByType = Maps.newHashMap(); for (XjmContribution cont : contributions) { if (cont.isActive()) { JvmDeclaredType jvmClass = cont.getJvmClass(); if (jvmClass != null && !jvmClass.eIsProxy()) contributionsByType.put(jvmClass, cont); } } for (XjmContribution cont : contributionsByType.values()) { List<JvmDeclaredType> replaces = JvmAnnotationUtil.getAnnotationTypeValue(cont.getJvmClass(), XpectReplace.class); for (JvmDeclaredType replace : replaces) { XjmContribution replaced = contributionsByType.get(replace); if (replaced != null) replaced.setReplacedBy(cont); } } } private void initTestClasses() { Map<String, XjmTest> name2test = Maps.newLinkedHashMap(); XjmTest test = getTestOrSuite(); EList<XjmTest> newTests = super.getTests(); newTests.clear(); if (test != null && !test.eIsProxy()) { JvmDeclaredType jvmClass = test.getJvmClass(); if (jvmClass != null && !jvmClass.eIsProxy()) { name2test.put(jvmClass.getQualifiedName(), test); collectTestClasses(test.getJvmClass(), name2test); newTests.addAll(name2test.values()); } } } private void initTestClassMethods() { // collect all methods Multimap<Pair<Boolean, String>, Pair<XjmTest, JvmOperation>> xpectMethods = LinkedHashMultimap.create(); for (XjmTest type : getTests()) for (JvmFeature feature : type.getJvmClass().getAllFeatures()) if (feature instanceof JvmOperation && feature.getVisibility() == JvmVisibility.PUBLIC) { if (JvmAnnotationUtil.isAnnotatedWith(feature, Xpect.class)) xpectMethods.put(Tuples.create(true, feature.getSimpleName()), Tuples.create(type, (JvmOperation) feature)); if (JvmAnnotationUtil.isAnnotatedWith(feature, Test.class)) xpectMethods.put(Tuples.create(false, feature.getSimpleName()), Tuples.create(type, (JvmOperation) feature)); } // only add the methods for which only one exists. XpectJavaModelFactory factory = XpectJavaModelFactory.eINSTANCE; EMap<String, XjmMethod> name2method = super.getMethods(); for (Map.Entry<Pair<Boolean, String>, Collection<Pair<XjmTest, JvmOperation>>> e : xpectMethods.asMap().entrySet()) if (e.getValue().size() == 1) { Pair<XjmTest, JvmOperation> pair = e.getValue().iterator().next(); XjmMethod method = e.getKey().getFirst() ? factory.createXjmXpectMethod() : factory.createXjmTestMethod(); method.setJvmMethod(pair.getSecond()); ((XjmTestImplCustom) pair.getFirst()).addMethod(method); name2method.put(method.getName(), method); } } private String toQualifiedName(JvmIdentifiableElement element) { if (element == null) return "null"; if (element.eIsProxy()) return "Proxy: " + ((InternalEObject) element).eProxyURI(); String name = element.getQualifiedName(); return name == null ? "null" : name; } private String toSimpleName(XjmElement element) { if (element instanceof XjmMethod) { JvmOperation method = ((XjmMethod) element).getJvmMethod(); if (method == null) return null; if (method.eIsProxy()) return "Proxy: " + ((InternalEObject) method).eProxyURI(); JvmDeclaredType type = method.getDeclaringType(); return type.getSimpleName() + "." + method.getSimpleName() + "()"; } else if (element instanceof XjmClass) { JvmDeclaredType jvmClass = ((XjmClass) element).getJvmClass(); if (jvmClass == null) return null; if (jvmClass.eIsProxy()) return "Proxy: " + ((InternalEObject) jvmClass).eProxyURI(); return jvmClass.getSimpleName(); } return element == null ? "null" : element.toString(); } @Override public String toString() { Multimap<XjmClass, String> reverseImports = computeReverseImports(); Multimap<String, XjmContribution> contributionByRole = HashMultimap.create(); for (XjmContribution cnt : getContributions()) for (Annotation role : cnt.getRoles()) contributionByRole.put(role.annotationType().getSimpleName(), cnt); List<Object> items = Lists.newArrayList(); items.addAll(getTests()); for (Map.Entry<String, Collection<XjmContribution>> e : contributionByRole.asMap().entrySet()) items.add(toString(e.getKey(), e.getValue(), reverseImports)); String body = " {\n " + Joiner.on("\n").join(items).replace("\n", "\n ") + "\n}"; return "suite " + toQualifiedName(getTestOrSuite().getJvmClass()) + body; } private String toString(String role, Collection<XjmContribution> contributions, Multimap<XjmClass, String> reverseImports) { List<String> items = Lists.newArrayList(); for (XjmContribution cnt : contributions) items.add(toString(cnt, reverseImports.get(cnt))); Collections.sort(items); String body = " {\n " + Joiner.on("\n").join(items).replace("\n", "\n ") + "\n}"; return "contributionsFor @" + role + body; } private String toString(XjmContribution contribution, Collection<String> importedBy) { StringBuilder result = new StringBuilder(); if (!contribution.isActive()) result.append("[INACTIVE] "); result.append(toQualifiedName(contribution.getJvmClass())); String reason = contribution.getDeactivationReason(); if (reason != null) { result.append(" InactiveBecause: "); result.append(reason); } if (!importedBy.isEmpty()) { result.append(" ImportedBy:"); result.append(Joiner.on(" ").join(importedBy)); } return result.toString(); } @Override public Iterable<XjmContribution> getContributions(Class<? extends Annotation> role) { List<XjmContribution> list = Lists.newArrayList(); N: for (XjmContribution cont : getContributions()) for (Annotation cand : cont.getRoles()) if (cand.annotationType() == role) { list.add(cont); continue N; } return list; } @Override public Iterable<XjmContribution> getContributions(Class<? extends Annotation> role, Environment environment) { List<XjmContribution> list = Lists.newArrayList(); N: for (XjmContribution cont : getContributions()) if (cont.getEnvironments().contains(environment)) for (Annotation cand : cont.getRoles()) if (cand.annotationType() == role) { list.add(cont); continue N; } return list; } }