/******************************************************************************* * Copyright (c) 2014 Jesper Steen Moller and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Jesper Steen Moller - initial API and implementation * *******************************************************************************/ package org.eclipse.jdt.apt.pluggable.tests.processors.modeltester; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import org.eclipse.jdt.apt.pluggable.tests.ModelTests; import org.eclipse.jdt.apt.pluggable.tests.ProcessorTestStatus; import org.eclipse.jdt.apt.pluggable.tests.annotations.LookAt; import org.eclipse.jdt.apt.pluggable.tests.annotations.ModelTest8Trigger; /** * This processor tests features specific to JEP 118. * One processor can run many tests. The JUnit tests specify which test to run by passing its name in to the * ModelTest8Trigger annotation. * * Although this test processor only needs to be run with 1.8 JRE, we don't explicitly state the supported version. * The clients invoking this must ensure that this is invoked only with JRE 1.8 and above, * like it's done in {@link ModelTests#testMethodParameters}. * * @since 3.9 BETA_JAVA8 */ @SupportedAnnotationTypes( { "org.eclipse.jdt.apt.pluggable.tests.annotations.ModelTest8Trigger" }) @SupportedOptions( {}) public class ModelTester8Proc extends AbstractProcessor { public static final String TEST_METHOD_PARAMETERS_TYPE1_PKG = "p"; public static final String TEST_METHOD_PARAMETERS_TYPE1_CLASS = "Bar"; public static final String TEST_METHOD_PARAMETERS_TYPE1_SOURCE = "package p;\n" + "public class Bar {\n" + " public void otherStuff(final double fun, String beans) { }\n" + "}"; public static final String TEST_METHOD_PARAMETERS_TYPE2_PKG = "p"; public static final String TEST_METHOD_PARAMETERS_TYPE2_CLASS = "MyEnum"; public static final String TEST_METHOD_PARAMETERS_TYPE2_SOURCE = "package p;\n" + "\n" + "public enum MyEnum {\n" + " ONE(1), TWO(2);\n" + " \n" + " private MyEnum(final int finalIntValue) { this.var = finalIntValue; }\n" + " int var;\n" + "}\n"; public static final String TEST_METHOD_PARAMETERS_TYPE3_PKG = "p"; public static final String TEST_METHOD_PARAMETERS_TYPE3_CLASS = "Foo"; public static final String TEST_METHOD_PARAMETERS_TYPE3_SOURCE = "package p;\n" + "import org.eclipse.jdt.apt.pluggable.tests.annotations.ModelTest8Trigger;\n" + "import org.eclipse.jdt.apt.pluggable.tests.annotations.LookAt;\n" + "@ModelTest8Trigger(test = \"testMethodParameters\")" + "public class Foo {\n" + " @LookAt\n" + " public Bar doStuff(final int number, String textual) { return null; }\n" + " @LookAt\n" + " public MyEnum guess(final int isItOne) { return isItOne == 1 ? MyEnum.ONE : MyEnum.TWO; }\n" + "}"; @SuppressWarnings("unused") private ProcessingEnvironment _processingEnv; /* * (non-Javadoc) * * @see javax.annotation.processing.AbstractProcessor#init(javax.annotation.processing.ProcessingEnvironment) */ @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); _processingEnv = processingEnv; } public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } /* * (non-Javadoc) * * @see javax.annotation.processing.AbstractProcessor#process(java.util.Set, * javax.annotation.processing.RoundEnvironment) */ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { ProcessorTestStatus.setProcessorRan(); if (!roundEnv.processingOver() && !annotations.isEmpty()) { round(annotations, roundEnv); } return true; } /** * Perform a round of processing: for a given annotation instance, determine what test method it * specifies, and invoke that method, passing in the annotated element. */ private void round(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { TypeElement modelTesterAnno = annotations.iterator().next(); Set<? extends Element> annotatedEls = roundEnv.getElementsAnnotatedWith(modelTesterAnno); for (Element annotatedEl : annotatedEls) { ModelTest8Trigger modelTesterMirror = annotatedEl.getAnnotation(ModelTest8Trigger.class); String testMethodName = modelTesterMirror.test(); String arg0 = modelTesterMirror.arg0(); String arg1 = modelTesterMirror.arg1(); if (null != testMethodName && testMethodName.length() > 0) { try { Method testMethod = ModelTester8Proc.class.getMethod(testMethodName, RoundEnvironment.class, Element.class, String.class, String.class); testMethod.invoke(this, roundEnv, annotatedEl, arg0, arg1); } catch (Exception e) { Throwable t; t = (e instanceof InvocationTargetException) ? t = e.getCause() : e; t.printStackTrace(); // IllegalStateException probably means test method called ProcessorTestStatus.fail() String msg = (t instanceof IllegalStateException) ? t.getMessage() : t.getClass().getSimpleName() + " invoking test method " + testMethodName + " - see console for details"; ProcessorTestStatus.fail(msg); } } } } /** * Check the types of some methods (check that the annotation processing uses the parsed MethodParameters * attribute from class files according to JEP 118) * @see #TEST_METHOD_PARAMETERS_TYPE1_SOURCE * @see #TEST_METHOD_PARAMETERS_TYPE2_SOURCE * @see #TEST_METHOD_PARAMETERS_TYPE3_SOURCE */ public void testMethodParameters(RoundEnvironment roundEnv, Element e, String arg0, String arg1) throws Exception { Map<String, ExecutableElement> methods = new HashMap<String, ExecutableElement>(); Iterable<? extends Element> elements; elements = roundEnv.getElementsAnnotatedWith(LookAt.class); for (ExecutableElement method : ElementFilter.methodsIn(elements)) { methods.put(method.getSimpleName().toString(), method); } // Examine the easy case, the Foo.doStuff method ExecutableElement mDoStuff = methods.get("doStuff"); if (mDoStuff == null) { ProcessorTestStatus.fail("Method doStuff() was not found"); } if (mDoStuff.getKind() != ElementKind.METHOD) { ProcessorTestStatus.fail("ElementKind of method doStuff() was " + mDoStuff.getKind() + ", expected METHOD"); } // Examine parameters List<? extends VariableElement> parameters = mDoStuff.getParameters(); if (parameters.size() != 2) { ProcessorTestStatus.fail("Expected two parameters for doStuff()"); } ProcessorTestStatus.assertEquals("Wrong name", "number", parameters.get(0).getSimpleName().toString()); ProcessorTestStatus.assertEquals("Wrong name", "textual", parameters.get(1).getSimpleName().toString()); /////////////////////////////////////////////////////////////////////////////////// // Cool, now check 'p.Bar.otherStuff' which is also the return type of doStuff TypeMirror returnType = mDoStuff.getReturnType(); if (returnType.getKind() != TypeKind.DECLARED) ProcessorTestStatus.fail("TypeKind of method doStuff()'s return type " + returnType.getKind() + ", expected DECLARED"); DeclaredType barType = (DeclaredType) returnType; TypeElement bar = (TypeElement) barType.asElement(); for (Element method : bar.getEnclosedElements()) { if (method.getKind() == ElementKind.METHOD) methods.put(method.getSimpleName().toString(), (ExecutableElement)method); } ExecutableElement mOtherStuff = methods.get("otherStuff"); if (mOtherStuff == null) { ProcessorTestStatus.fail("Method otherStuff() was not found"); } if (mOtherStuff.getKind() != ElementKind.METHOD) { ProcessorTestStatus.fail("ElementKind of method otherStuff() was " + mOtherStuff.getKind() + ", expected METHOD"); } // Examine parameters List<? extends VariableElement> otherParameters = mOtherStuff.getParameters(); if (otherParameters.size() != 2) { ProcessorTestStatus.fail("Expected two parameters for otherStuff()"); } ProcessorTestStatus.assertEquals("Wrong name", "fun", otherParameters.get(0).getSimpleName().toString()); ProcessorTestStatus.assertEquals("Wrong name", "beans", otherParameters.get(1).getSimpleName().toString()); /////////////////////////////////////////////////////////////////////////////////// // Examine the enum as returned by Foo.guess method ExecutableElement mGuess = methods.get("guess"); if (mGuess == null) { ProcessorTestStatus.fail("Method guess() was not found"); } if (mGuess.getKind() != ElementKind.METHOD) { ProcessorTestStatus.fail("ElementKind of method doStuff() was " + mGuess.getKind() + ", expected METHOD"); } // Cool, now check 'p.Bar.otherStuff' which is also the return type of doStuff TypeMirror guessReturnType = mGuess.getReturnType(); if (guessReturnType.getKind() != TypeKind.DECLARED) ProcessorTestStatus.fail("TypeKind of method guess()'s return type " + guessReturnType.getKind() + ", expected DECLARED"); DeclaredType myEnumType = (DeclaredType) guessReturnType; TypeElement myEnumClass = (TypeElement) myEnumType.asElement(); List<ExecutableElement> ctors = new LinkedList<ExecutableElement>(); for (Element method : myEnumClass.getEnclosedElements()) { if (method.getKind() == ElementKind.CONSTRUCTOR) { ctors.add((ExecutableElement)method); } } ProcessorTestStatus.assertEquals("Bad # of constructors for MyEnum", 1, ctors.size()); // Examine parameters List<? extends VariableElement> ctorParameters = ctors.get(0).getParameters(); ProcessorTestStatus.assertEquals("Bad # of parameters for MyEnum ctor", 1, ctorParameters.size()); ProcessorTestStatus.assertEquals("Wrong name", "finalIntValue", ctorParameters.get(0).getSimpleName().toString()); } }