package com.google.inject.blender; import java.io.IOException; import java.lang.String; import java.util.HashMap; import java.util.HashSet; 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.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; /** * An annotation processor that detects classes that need to receive injections. * @author MikeBurton * @author SNI */ @SupportedAnnotationTypes({"com.google.inject.Inject", "javax.inject.Inject", "com.google.inject.Provides"}) @SupportedSourceVersion(SourceVersion.RELEASE_6) @SupportedOptions({"guiceAnnotationDatabasePackageName", "guiceUsesFragmentUtil"}) public class GuiceAnnotationProcessor extends AbstractProcessor { public static final String TEMPLATE_ANNOTATION_DATABASE_PATH = "templates/AnnotationDatabaseImpl.vm"; //TODO add a HashMap<String, Set<String>> /** * Maps each annotation name to an inner map. * The inner map maps classes (containing injection points) names to the list of injected field names. */ private HashMap<String, Map<String, Set<String>> > mapAnnotationToMapClassContainingInjectionToInjectedFieldSet; /** * Maps each annotation name to an inner map. * The inner map maps classes (containing injection points) names to the list of injected method names and parameters classes. */ private HashMap<String, Map<String, Set<String>> > mapAnnotationToMapClassContainingInjectionToInjectedMethodSet; /** * Maps each annotation name to an inner map. * The inner map maps classes (containing injection points) names to the list of injected constructors parameters classes. */ private HashMap<String, Map<String, Set<String>> > mapAnnotationToMapClassContainingInjectionToInjectedConstructorsSet; /** Contains all classes that contain injection points. */ private HashSet<String> classesContainingInjectionPointsSet = new HashSet<String>(); /** Contains all classes that can be injected into a class with injection points.*/ private HashSet<String> bindableClasses; /** Name of the package to generate the annotation database into.*/ private String annotationDatabasePackageName; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); annotationDatabasePackageName = processingEnv.getOptions().get("guiceAnnotationDatabasePackageName"); mapAnnotationToMapClassContainingInjectionToInjectedFieldSet = new HashMap<String, Map<String,Set<String>> >(); mapAnnotationToMapClassContainingInjectionToInjectedMethodSet = new HashMap<String, Map<String,Set<String>> >(); mapAnnotationToMapClassContainingInjectionToInjectedConstructorsSet = new HashMap<String, Map<String,Set<String>> >(); bindableClasses = new HashSet<String>(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // Not sure why, but sometimes we're getting called with an empty list of annotations. if(annotations.isEmpty()) return true; for( TypeElement annotation : annotations ) { String annotationClassName = getTypeName(annotation); //merge the 2 inject annotations if( "javax.inject.Inject".equals(annotationClassName) ) { annotationClassName = "com.google.inject.Inject"; } for( Element injectionPoint : roundEnv.getElementsAnnotatedWith(annotation)) { if( injectionPoint.getEnclosingElement() instanceof TypeElement && injectionPoint instanceof VariableElement ) { addFieldToAnnotationDatabase(annotationClassName, injectionPoint); } else if( injectionPoint.getEnclosingElement() instanceof ExecutableElement && injectionPoint instanceof VariableElement ) { addParameterToAnnotationDatabase(annotationClassName, injectionPoint); } else if( injectionPoint instanceof ExecutableElement ) { addMethodOrConstructorToAnnotationDatabase(annotationClassName, injectionPoint); } else if( injectionPoint instanceof TypeElement ) { addClassToAnnotationDatabase(injectionPoint); } } } for( Map<String, Set<String>> entryAnnotationToclassesContainingInjectionPoints : mapAnnotationToMapClassContainingInjectionToInjectedFieldSet.values() ) { classesContainingInjectionPointsSet.addAll(entryAnnotationToclassesContainingInjectionPoints.keySet()); } for( Map<String, Set<String>> entryAnnotationToclassesContainingInjectionPoints : mapAnnotationToMapClassContainingInjectionToInjectedMethodSet.values() ) { classesContainingInjectionPointsSet.addAll(entryAnnotationToclassesContainingInjectionPoints.keySet()); } for( Map<String, Set<String>> entryAnnotationToclassesContainingInjectionPoints : mapAnnotationToMapClassContainingInjectionToInjectedConstructorsSet.values() ) { classesContainingInjectionPointsSet.addAll(entryAnnotationToclassesContainingInjectionPoints.keySet()); } JavaFileObject jfo; try { String className = "AnnotationDatabaseImpl"; if( annotationDatabasePackageName != null && !annotationDatabasePackageName.isEmpty() ) { className = annotationDatabasePackageName+'.'+className; } jfo = processingEnv.getFiler().createSourceFile( className ); AnnotationDatabaseGenerator annotationDatabaseGenerator = createAnnotationDatabaseGenerator(); configure(annotationDatabaseGenerator); annotationDatabaseGenerator.generateAnnotationDatabase(jfo); } catch (IOException e) { e.printStackTrace(); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage()); } return true; } protected AnnotationDatabaseGenerator createAnnotationDatabaseGenerator() { AnnotationDatabaseGenerator annotationDatabaseGenerator = new AnnotationDatabaseGenerator(); return annotationDatabaseGenerator; } protected void configure(AnnotationDatabaseGenerator annotationDatabaseGenerator) { annotationDatabaseGenerator.setTemplatePath(TEMPLATE_ANNOTATION_DATABASE_PATH); annotationDatabaseGenerator.setPackageName(annotationDatabasePackageName); annotationDatabaseGenerator.setBindableClasses(bindableClasses); annotationDatabaseGenerator.setClassesContainingInjectionPointsSet(classesContainingInjectionPointsSet); annotationDatabaseGenerator.setMapAnnotationToMapClassWithInjectionNameToConstructorSet(mapAnnotationToMapClassContainingInjectionToInjectedConstructorsSet); annotationDatabaseGenerator.setMapAnnotationToMapClassWithInjectionNameToMethodSet(mapAnnotationToMapClassContainingInjectionToInjectedMethodSet); annotationDatabaseGenerator.setMapAnnotationToMapClassWithInjectionNameToFieldSet(mapAnnotationToMapClassContainingInjectionToInjectedFieldSet); } private void addClassToAnnotationDatabase(Element injectionPoint) { TypeElement typeElementRequiringScanning = (TypeElement) injectionPoint; String typeElementName = getTypeName(typeElementRequiringScanning); //System.out.printf("Type: %s, is injected\n",typeElementName); classesContainingInjectionPointsSet.add(typeElementName); } private void addFieldToAnnotationDatabase(String annotationClassName, Element injectionPoint) { String injectionPointName; String injectedClassName = getTypeName(injectionPoint); bindableClasses.add( injectedClassName ); injectionPointName = injectionPoint.getSimpleName().toString(); TypeElement typeElementRequiringScanning = (TypeElement) injectionPoint.getEnclosingElement(); String typeElementName = getTypeName(typeElementRequiringScanning); //System.out.printf("Type: %s, injection: %s \n",typeElementName, injectionPointName); addToInjectedFields(annotationClassName, typeElementName, injectionPointName); } private void addParameterToAnnotationDatabase(String annotationClassName, Element injectionPoint) { Element enclosing = injectionPoint.getEnclosingElement(); String injectionPointName = enclosing.getSimpleName().toString(); for( VariableElement variable : ((ExecutableElement)enclosing).getParameters() ) { String parameterTypeName = getTypeName(variable); bindableClasses.add( parameterTypeName ); injectionPointName += ":"+parameterTypeName; } TypeElement typeElementRequiringScanning = (TypeElement) ((ExecutableElement) injectionPoint.getEnclosingElement()).getEnclosingElement(); String typeElementName = getTypeName(typeElementRequiringScanning); //System.out.printf("Type: %s, injection: %s \n",typeElementName, injectionPointName); if( injectionPointName.startsWith("<init>") ) { addToInjectedConstructors(annotationClassName, typeElementName, injectionPointName ); } else { addToInjectedMethods(annotationClassName, typeElementName, injectionPointName ); } } private void addMethodOrConstructorToAnnotationDatabase(String annotationClassName, Element injectionPoint) { String injectionPointName = injectionPoint.getSimpleName().toString(); for( VariableElement variable : ((ExecutableElement)injectionPoint).getParameters() ) { String parameterTypeName = getTypeName((TypeElement)((DeclaredType)variable.asType()).asElement()); bindableClasses.add( parameterTypeName ); injectionPointName += ":"+parameterTypeName; } TypeElement typeElementRequiringScanning = (TypeElement) injectionPoint.getEnclosingElement(); String typeElementName = getTypeName(typeElementRequiringScanning); //System.out.printf("Type: %s, injection: %s \n",typeElementName, injectionPointName); if( injectionPointName.startsWith("<init>") ) { addToInjectedConstructors(annotationClassName, typeElementName, injectionPointName ); } else { addToInjectedMethods(annotationClassName, typeElementName, injectionPointName ); } } protected void addToInjectedConstructors(String annotationClassName, String typeElementName, String injectionPointName) { addToInjectedMembers(annotationClassName, typeElementName, injectionPointName, mapAnnotationToMapClassContainingInjectionToInjectedConstructorsSet); } protected void addToInjectedMethods(String annotationClassName, String typeElementName, String injectionPointName) { addToInjectedMembers(annotationClassName, typeElementName, injectionPointName, mapAnnotationToMapClassContainingInjectionToInjectedMethodSet); } protected void addToInjectedFields(String annotationClassName, String typeElementName, String injectionPointName) { addToInjectedMembers(annotationClassName, typeElementName, injectionPointName, mapAnnotationToMapClassContainingInjectionToInjectedFieldSet); } private String getTypeName(TypeElement typeElementRequiringScanning) { if( typeElementRequiringScanning.getEnclosingElement() instanceof TypeElement ) { return getTypeName(typeElementRequiringScanning.getEnclosingElement()) + "$" + typeElementRequiringScanning.getSimpleName().toString(); } else { return typeElementRequiringScanning.getQualifiedName().toString(); } } private String getTypeName(Element injectionPoint) { String injectedClassName = null; final TypeMirror fieldTypeMirror = injectionPoint.asType(); if( fieldTypeMirror instanceof DeclaredType ) { injectedClassName = getTypeName((TypeElement)((DeclaredType)fieldTypeMirror).asElement()); } else if( fieldTypeMirror instanceof PrimitiveType ) { injectedClassName = fieldTypeMirror.getKind().name(); } return injectedClassName; } private void addToInjectedMembers(String annotationClassName, String typeElementName, String injectionPointName, HashMap<String, Map<String, Set<String>> > mapAnnotationToMapClassWithInjectionNameToMembersSet) { Map<String, Set<String>> mapClassWithInjectionNameToMemberSet = mapAnnotationToMapClassWithInjectionNameToMembersSet.get( annotationClassName ); if( mapClassWithInjectionNameToMemberSet == null ) { mapClassWithInjectionNameToMemberSet = new HashMap<String, Set<String>>(); mapAnnotationToMapClassWithInjectionNameToMembersSet.put(annotationClassName, mapClassWithInjectionNameToMemberSet); } Set<String> injectionPointNameSet = mapClassWithInjectionNameToMemberSet.get(typeElementName); if( injectionPointNameSet == null ) { injectionPointNameSet = new HashSet<String>(); mapClassWithInjectionNameToMemberSet.put(typeElementName, injectionPointNameSet); } injectionPointNameSet.add(injectionPointName); } }