/******************************************************************************* * Copyright (c) 2008, 2012, Walter Harley 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: * eclipse@cafewalter.com - 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.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.annotation.processing.SupportedSourceVersion; 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.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; 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.ModelTestTrigger; /** * Testing annotation processors through JUnit in the IDE is complex, because each test requires * something different of the processor and all processors must coexist in the plugin registry, and * because the processor has very limited communication with the rest of the IDE. So, we make one * processor run many tests. The JUnit tests specify which test to run by passing its name in to the * ModelTestTrigger annotation. Test failures are reported via the Messager interface. * * @since 3.5 */ @SupportedAnnotationTypes( { "org.eclipse.jdt.apt.pluggable.tests.annotations.ModelTestTrigger" }) @SupportedSourceVersion(SourceVersion.RELEASE_6) @SupportedOptions( {}) public class ModelTesterProc extends AbstractProcessor { public static final String TEST_FIELD_TYPE_PKG = "p"; public static final String TEST_FIELD_TYPE_CLASS = "Foo"; public static final String TEST_FIELD_TYPE_SOURCE = "package p;\n" + "import org.eclipse.jdt.apt.pluggable.tests.annotations.ModelTestTrigger;\n" + "import org.eclipse.jdt.apt.pluggable.tests.annotations.LookAt;\n" + "@ModelTestTrigger(test = \"testFieldType\")" + "public class Foo {\n" + " @LookAt\n" + " private int _fInt = 0;\n" + " @LookAt\n" + " private String _fString = \"\";\n" + " @LookAt\n" + " private Foo _fFoo = null;\n" + "}"; public static final String TEST_METHOD_TYPE_PKG = "p"; public static final String TEST_METHOD_TYPE_CLASS = "Foo"; public static final String TEST_METHOD_TYPE_SOURCE = "package p;\n" + "import org.eclipse.jdt.apt.pluggable.tests.annotations.ModelTestTrigger;\n" + "import org.eclipse.jdt.apt.pluggable.tests.annotations.LookAt;\n" + "@ModelTestTrigger(test = \"testMethodType\")" + "public class Foo {\n" + " @LookAt\n" + " private Foo self() { return this;}\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; } /* * (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) { ModelTestTrigger modelTesterMirror = annotatedEl.getAnnotation(ModelTestTrigger.class); String testMethodName = modelTesterMirror.test(); String arg0 = modelTesterMirror.arg0(); String arg1 = modelTesterMirror.arg1(); if (null != testMethodName && testMethodName.length() > 0) { try { Method testMethod = ModelTesterProc.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 fields (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=248163) * @see #TEST_FIELD_TYPE_SOURCE */ public void testFieldType(RoundEnvironment roundEnv, Element e, String arg0, String arg1) throws Exception { Map<String, VariableElement> fields = new HashMap<String, VariableElement>(); Iterable<? extends Element> elements; // With this line uncommented, test would pass: // elements = e.getEnclosedElements(); // With this line uncommented, test would fail: elements = roundEnv.getElementsAnnotatedWith(LookAt.class); for (VariableElement field : ElementFilter.fieldsIn(elements)) { fields.put(field.getSimpleName().toString(), field); } VariableElement fInt = fields.get("_fInt"); if (fInt == null) { ProcessorTestStatus.fail("Field _fInt was not found"); } if (fInt.getKind() != ElementKind.FIELD) { ProcessorTestStatus.fail("ElementKind of field _fInt was " + fInt.getKind() + ", expected FIELD"); } TypeMirror fIntType = fInt.asType(); if (fIntType.getKind() != TypeKind.INT) { ProcessorTestStatus.fail("Field _fInt asType returned type kind of " + fIntType.getKind() + ", expected INT"); } VariableElement fString = fields.get("_fString"); if (fString == null) { ProcessorTestStatus.fail("Field _fString was not found"); } if (fString.getKind() != ElementKind.FIELD) { ProcessorTestStatus.fail("ElementKind of field _fString was " + fString.getKind() + ", expected FIELD"); } TypeMirror fStringType = fString.asType(); if (fStringType.getKind() != TypeKind.DECLARED) { ProcessorTestStatus.fail("Field _fString asType returned type kind of " + fStringType.getKind() + ", expected DECLARED"); } VariableElement fFoo = fields.get("_fFoo"); if (fFoo == null) { ProcessorTestStatus.fail("Field _fFoo was not found"); } if (fFoo.getKind() != ElementKind.FIELD) { ProcessorTestStatus.fail("ElementKind of field _fFoo was " + fFoo.getKind() + ", expected FIELD"); } TypeMirror fFooType = fFoo.asType(); if (fFooType.getKind() != TypeKind.DECLARED) { ProcessorTestStatus.fail("Field _fFoo asType returned type kind of " + fFooType.getKind() + ", expected DECLARED"); } } /** * Check the types of some fields (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=248163) * @see #TEST_METHOD_TYPE_SOURCE */ public void testMethodType(RoundEnvironment roundEnv, Element e, String arg0, String arg1) throws Exception { Map<String, ExecutableElement> methods = new HashMap<String, ExecutableElement>(); Iterable<? extends Element> elements; // With this line uncommented, test would pass: // elements = e.getEnclosedElements(); // With this line uncommented, test would fail: elements = roundEnv.getElementsAnnotatedWith(LookAt.class); for (ExecutableElement method : ElementFilter.methodsIn(elements)) { methods.put(method.getSimpleName().toString(), method); } ExecutableElement mSelf = methods.get("self"); if (mSelf == null) { ProcessorTestStatus.fail("Method self() was not found"); } if (mSelf.getKind() != ElementKind.METHOD) { ProcessorTestStatus.fail("ElementKind of method self() was " + mSelf.getKind() + ", expected METHOD"); } TypeMirror mSelfType = mSelf.getReturnType(); if (mSelfType.getKind() != TypeKind.DECLARED) { ProcessorTestStatus.fail("Method self() asType returned type kind of " + mSelfType.getKind() + ", expected DECLARED"); } } }