/*******************************************************************************
* 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());
}
}