package net.karneim.pojobuilder.processor; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; import javax.tools.JavaFileObject; import net.karneim.pojobuilder.GeneratePojoBuilder; import net.karneim.pojobuilder.PojoBuilderException; import net.karneim.pojobuilder.analysis.AnnotationHierarchyUtil; import net.karneim.pojobuilder.analysis.DirectivesFactory; import net.karneim.pojobuilder.analysis.Input; import net.karneim.pojobuilder.analysis.InputFactory; import net.karneim.pojobuilder.analysis.InvalidElementException; import net.karneim.pojobuilder.analysis.JavaModelAnalyzer; import net.karneim.pojobuilder.analysis.JavaModelAnalyzerUtil; import net.karneim.pojobuilder.analysis.Output; import net.karneim.pojobuilder.model.BuilderM; import net.karneim.pojobuilder.model.ManualBuilderM; import net.karneim.pojobuilder.sourcegen.BuilderSourceGenerator; import net.karneim.pojobuilder.sourcegen.ManualBuilderSourceGenerator; import com.squareup.javawriter.JavaWriter; public class AnnotationProcessor extends AbstractProcessor { private static final String POJO_BUILDER_STARTED = "[PojoBuilder] Started"; private static final String POJO_BUILDER_PROCESSING_ANNOTATIONS_S = "[PojoBuilder] Processing annotations (round %s)"; private static final String POJO_BUILDER_FINISHED_S = "[PojoBuilder] Finished (%s ms)"; private static final String POJO_BUILDER_GENERATED_CLASS_S = "[PojoBuilder] Generated class %s"; private static final String GENERATED_S = "Generated %s"; private static final String POJO_BUILDER_CAUGHT_UNEXPECTED_EXCEPTION_ON_ELEMENT_S_S = "PojoBuilder caught unexpected exception on element %s!%s"; private static final String POJO_BUILDER_CAUGHT_EXCEPTION_ON_ELEMENT_S_S = "PojoBuilder caught exception on element %s!%s"; private static final Logger LOG = Logger.getLogger(AnnotationProcessor.class.getName()); private JavaModelAnalyzer javaModelAnalyzer; private InputFactory inputFactory; private JavaModelAnalyzerUtil javaModelAnalyzerUtil; private AnnotationHierarchyUtil annotationHierarchyUtil; private long started = 0; private int roundCount = 0; private final Set<String> failedTypeNames = new HashSet<String>(); private final Set<String> generatedTypeNames = new HashSet<String>(); private final Map<Element, Exception> failedElementsMap = new HashMap<Element, Exception>(); @Override public Set<String> getSupportedAnnotationTypes() { HashSet<String> result = new HashSet<String>(); result.add("*"); return result; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } private void initHelpers(ProcessingEnvironment env) { this.javaModelAnalyzerUtil = new JavaModelAnalyzerUtil(env.getElementUtils(), env.getTypeUtils()); this.javaModelAnalyzer = new JavaModelAnalyzer(env.getElementUtils(), env.getTypeUtils(), javaModelAnalyzerUtil); this.inputFactory = new InputFactory(env.getTypeUtils(), new DirectivesFactory(env.getElementUtils(), env.getTypeUtils(), javaModelAnalyzerUtil)); this.annotationHierarchyUtil = new AnnotationHierarchyUtil(env.getTypeUtils()); } private void clearState() { javaModelAnalyzerUtil = null; javaModelAnalyzer = null; inputFactory = null; annotationHierarchyUtil = null; failedTypeNames.clear(); generatedTypeNames.clear(); failedElementsMap.clear(); roundCount = 0; started = 0; } /** * This processor claims NOT to process annotations exclusively. */ private static final boolean ANNOTATIONS_NOT_CLAIMED_EXCLUSIVELY = false; @Override public boolean process(Set<? extends TypeElement> aAnnotations, RoundEnvironment aRoundEnv) { roundCount++; if (roundCount == 1) { started = System.currentTimeMillis(); note(POJO_BUILDER_STARTED); } try { initHelpers(processingEnv); if (!aRoundEnv.processingOver()) { note(String.format(POJO_BUILDER_PROCESSING_ANNOTATIONS_S, roundCount)); if (!aAnnotations.isEmpty()) { Set<TypeElement> triggeringAnnotations = annotationHierarchyUtil.filterTriggeringAnnotations(aAnnotations, getTypeElement(GeneratePojoBuilder.class)); List<Element> elementsToProcess = getAnnotatedElements(aRoundEnv, triggeringAnnotations); addElementsThatFailedInLastRound(elementsToProcess); resetFailedElements(); List<Output> outputList = new ArrayList<Output>(); for (Element elem : elementsToProcess) { try { // note(String.format("Processing %s", elem), elem); Input input = inputFactory.getInput(elem); Output output = javaModelAnalyzer.analyze(input); outputList.add(output); } catch (Exception ex) { addFailedElement(elem, ex); } } // Generate source files for (Output output : outputList) { try { generateSources(output); } catch (Exception ex) { error(ex, output.getInput().getAnnotatedElement()); } } } } else { // In the last round we show all collected errors showErrorsForFailedElements(); } } catch (Throwable t) { processingEnv.getMessager().printMessage(Kind.ERROR, toString(t)); } finally { if (aRoundEnv.processingOver()) { long duration = System.currentTimeMillis() - started; note(String.format(POJO_BUILDER_FINISHED_S, duration)); clearState(); } } return ANNOTATIONS_NOT_CLAIMED_EXCLUSIVELY; } private void resetFailedElements() { failedElementsMap.clear(); failedTypeNames.clear(); } private void addElementsThatFailedInLastRound(List<Element> elementsToProcess) { elementsToProcess.addAll(javaModelAnalyzerUtil.findAnnotatedElements(getTypeElements(failedTypeNames), GeneratePojoBuilder.class)); } private void addFailedElement(Element elem, Exception ex) { failedElementsMap.put(elem, ex); failedTypeNames.add(javaModelAnalyzerUtil.getCompilationUnit(elem).getQualifiedName().toString()); } private void showErrorsForFailedElements() { for (Map.Entry<Element, Exception> entry : failedElementsMap.entrySet()) { error(entry.getValue(), entry.getKey()); } } private List<Element> getAnnotatedElements(RoundEnvironment aRoundEnv, Set<TypeElement> triggeringAnnotations) { List<Element> elementsToProcess = new ArrayList<Element>(); for (TypeElement annoTypeEl : triggeringAnnotations) { elementsToProcess.addAll(aRoundEnv.getElementsAnnotatedWith(annoTypeEl)); } return removeAnnotationElements(elementsToProcess); } private List<Element> removeAnnotationElements(List<Element> elements) { List<Element> result = new ArrayList<Element>(); for (Element el : elements) { if (el.getKind() != ElementKind.ANNOTATION_TYPE) { result.add(el); } } return result; } private TypeElement getTypeElement(Class<?> cls) { return getTypeElement(cls.getName()); } private TypeElement getTypeElement(String typeName) { return processingEnv.getElementUtils().getTypeElement(typeName); } private Collection<TypeElement> getTypeElements(Collection<String> typeNames) { List<TypeElement> result = new ArrayList<TypeElement>(); for (String typeName : typeNames) { result.add(getTypeElement(typeName)); } return result; } private void generateSources(Output output) throws IOException { if (!hasAlreadyBeenCreated(getTypeName(output.getBuilderModel()))) { generateBuilderImpl(output); } if (output.getManualBuilderModel() != null && !hasAlreadyBeenCreated(getTypeName(output.getManualBuilderModel())) && !typeExists(getTypeName(output.getManualBuilderModel()))) { generateManualBuilder(output); } } private boolean hasAlreadyBeenCreated(String typename) { return this.generatedTypeNames.contains(typename); } private void generateBuilderImpl(Output output) throws IOException { BuilderM builderModel = output.getBuilderModel(); String qualifiedName = getTypeName(builderModel); JavaFileObject jobj = processingEnv.getFiler().createSourceFile(qualifiedName, asArray(output.getInput().getOrginatingElements())); Writer writer = jobj.openWriter(); JavaWriter javaWriter = new JavaWriter(writer); BuilderSourceGenerator generator = new BuilderSourceGenerator(javaWriter); generator.generateSource(builderModel); writer.close(); for (String warning : generator.getWarnings()) { warn(warning, output.getInput().getAnnotatedElement()); } generatedTypeNames.add(qualifiedName); note(String.format(POJO_BUILDER_GENERATED_CLASS_S, qualifiedName), null); LOG.fine(String.format(GENERATED_S, jobj.toUri())); } private Element[] asArray(Collection<Element> elements) { Element[] result = new Element[elements.size()]; elements.toArray(result); return result; } private void generateManualBuilder(Output output) throws IOException { ManualBuilderM manualBuilderModel = output.getManualBuilderModel(); String qualifiedName = getTypeName(manualBuilderModel); JavaFileObject jobj = processingEnv.getFiler().createSourceFile(qualifiedName, asArray(output.getInput().getOrginatingElements())); Writer writer = jobj.openWriter(); JavaWriter javaWriter = new JavaWriter(writer); ManualBuilderSourceGenerator generator = new ManualBuilderSourceGenerator(javaWriter); generator.generateSource(manualBuilderModel); writer.close(); generatedTypeNames.add(qualifiedName); note(String.format(POJO_BUILDER_GENERATED_CLASS_S, qualifiedName), null); LOG.fine(String.format(GENERATED_S, jobj.toUri())); } private boolean typeExists(String qualifiedName) { return processingEnv.getElementUtils().getTypeElement(qualifiedName) != null; } private String getTypeName(ManualBuilderM manualBuilderModel) { String qualifiedName = manualBuilderModel.getType().getName(); return qualifiedName; } private String getTypeName(BuilderM builderModel) { String qualifiedName = builderModel.getType().getName(); return qualifiedName; } private void error(Exception ex, Element processedElement) { if (ex instanceof InvalidElementException) { InvalidElementException invElemEx = (InvalidElementException) ex; Element elem = invElemEx.getElement(); error(invElemEx.getMessage(), elem); } else if (ex instanceof PojoBuilderException) { String message = String.format(POJO_BUILDER_CAUGHT_EXCEPTION_ON_ELEMENT_S_S, processedElement, toString(ex)); error(message, processedElement); } else { String message = String.format(POJO_BUILDER_CAUGHT_UNEXPECTED_EXCEPTION_ON_ELEMENT_S_S, processedElement, toString(ex)); error(message, processedElement); } } private void error(String msg, Element element) { if (element.asType().getKind() != TypeKind.ERROR) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, element); } else { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, null); } LOG.severe(msg); } private void note(String msg, Element element) { processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg, element); } private void note(String msg) { processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg); } private void warn(String msg, Element element) { processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, msg, element); } private String toString(Throwable ex) { if (ex == null) { return ""; } StringWriter writer = new StringWriter(); writer.append("\n"); ex.printStackTrace(new PrintWriter(writer)); return writer.toString(); } }