All Downloads are FREE. Search and download functionalities are using the official Maven repository.

toothpick.compiler.factory.FactoryProcessor Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
package toothpick.compiler.factory;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.inject.Inject;
import javax.inject.Scope;
import javax.inject.Singleton;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import toothpick.Factory;
import toothpick.ScopeInstances;
import toothpick.compiler.common.ToothpickProcessor;
import toothpick.compiler.factory.generators.FactoryGenerator;
import toothpick.compiler.factory.targets.ConstructorInjectionTarget;
import toothpick.compiler.registry.generators.RegistryGenerator;
import toothpick.compiler.registry.targets.RegistryInjectionTarget;
import toothpick.registries.factory.AbstractFactoryRegistry;

import static java.lang.String.format;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;

/**
 * This processor's role is to create {@link Factory}.
 * We create factories in different situations :
 * 
    *
  • When a class {@code Foo} has an {@link javax.inject.Inject} annotated constructor :
    * --> we create a Factory to create {@code Foo} instances. *
* The processor will also try to relax the constraints to generate factories in a few cases. These factories * are helpful as they require less work from developers : *
    *
  • When a class {@code Foo} is annotated with {@link javax.inject.Singleton} :
    * --> it will use the annotated constructor or the default constructor if possible. Otherwise an error is raised. *
  • When a class {@code Foo} is annotated with {@link ScopeInstances} :
    * --> it will use the annotated constructor or the default constructor if possible. Otherwise an error is raised. *
  • When a class {@code Foo} has an {@link javax.inject.Inject} annotated field {@code @Inject B b} :
    * --> it will use the annotated constructor or the default constructor if possible. Otherwise an error is raised. *
  • When a class {@code Foo} has an {@link javax.inject.Inject} method {@code @Inject m()} :
    * --> it will use the annotated constructor or the default constructor if possible. Otherwise an error is raised. *
* Note that if a class is abstract, the relax mechanism doesn't generate a factory and raises no error. */ //http://stackoverflow.com/a/2067863/693752 @SupportedAnnotationTypes({ ToothpickProcessor.INJECT_ANNOTATION_CLASS_NAME, // ToothpickProcessor.SINGLETON_ANNOTATION_CLASS_NAME, // ToothpickProcessor.PRODUCES_SINGLETON_ANNOTATION_CLASS_NAME }) @SupportedOptions({ ToothpickProcessor.PARAMETER_REGISTRY_PACKAGE_NAME, // ToothpickProcessor.PARAMETER_REGISTRY_CHILDREN_PACKAGE_NAMES, // ToothpickProcessor.PARAMETER_EXCLUDES }) // public class FactoryProcessor extends ToothpickProcessor { private Map mapTypeElementToConstructorInjectionTarget = new LinkedHashMap<>(); @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { readProcessorOptions(); findAndParseTargets(roundEnv); if (!roundEnv.processingOver()) { return false; } // Generate Factories List elementsWithFactoryCreated = new ArrayList<>(); for (Map.Entry entry : mapTypeElementToConstructorInjectionTarget.entrySet()) { ConstructorInjectionTarget constructorInjectionTarget = entry.getValue(); FactoryGenerator factoryGenerator = new FactoryGenerator(constructorInjectionTarget); TypeElement typeElement = entry.getKey(); String fileDescription = format("Factory for type %s", typeElement); boolean success = writeToFile(factoryGenerator, fileDescription, typeElement); if (success) { elementsWithFactoryCreated.add(typeElement); } } // Generate Registry //this allows tests to by pass the option mechanism in processors if (toothpickRegistryPackageName != null) { RegistryInjectionTarget registryInjectionTarget = new RegistryInjectionTarget(Factory.class, AbstractFactoryRegistry.class, toothpickRegistryPackageName, toothpickRegistryChildrenPackageNameList, elementsWithFactoryCreated); RegistryGenerator registryGenerator = new RegistryGenerator(registryInjectionTarget); String fileDescription = "Factory registry"; Element[] allTypes = elementsWithFactoryCreated.toArray(new Element[elementsWithFactoryCreated.size()]); writeToFile(registryGenerator, fileDescription, allTypes); } return false; } private void findAndParseTargets(RoundEnvironment roundEnv) { createFactoriesForClassesWithInjectAnnotatedConstructors(roundEnv); createFactoriesForClassesAnnotatedScopeInstances(roundEnv); createFactoriesForClassesAnnotatedSingleton(roundEnv); createFactoriesForClassesWithInjectAnnotatedFields(roundEnv); createFactoriesForClassesWithInjectAnnotatedMethods(roundEnv); } private void createFactoriesForClassesWithInjectAnnotatedMethods(RoundEnvironment roundEnv) { for (ExecutableElement methodElement : ElementFilter.methodsIn(roundEnv.getElementsAnnotatedWith(Inject.class))) { processClassContainingInjectAnnotatedMember(methodElement.getEnclosingElement(), mapTypeElementToConstructorInjectionTarget); } } private void createFactoriesForClassesWithInjectAnnotatedFields(RoundEnvironment roundEnv) { for (VariableElement fieldElement : ElementFilter.fieldsIn(roundEnv.getElementsAnnotatedWith(Inject.class))) { processClassContainingInjectAnnotatedMember(fieldElement.getEnclosingElement(), mapTypeElementToConstructorInjectionTarget); } } private void createFactoriesForClassesAnnotatedScopeInstances(RoundEnvironment roundEnv) { for (Element singletonAnnotatedElement : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(ScopeInstances.class))) { TypeElement singletonAnnotatedTypeElement = (TypeElement) singletonAnnotatedElement; processClassContainingInjectAnnotatedMember(singletonAnnotatedTypeElement, mapTypeElementToConstructorInjectionTarget); } } private void createFactoriesForClassesAnnotatedSingleton(RoundEnvironment roundEnv) { for (Element singletonAnnotatedElement : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Singleton.class))) { TypeElement singletonAnnotatedTypeElement = (TypeElement) singletonAnnotatedElement; processClassContainingInjectAnnotatedMember(singletonAnnotatedTypeElement, mapTypeElementToConstructorInjectionTarget); } } private void createFactoriesForClassesWithInjectAnnotatedConstructors(RoundEnvironment roundEnv) { for (ExecutableElement constructorElement : ElementFilter.constructorsIn(roundEnv.getElementsAnnotatedWith(Inject.class))) { TypeElement enclosingElement = (TypeElement) constructorElement.getEnclosingElement(); if (!isSingleInjectAnnotatedConstructor(constructorElement)) { error(constructorElement, "Class %s cannot have more than one @Inject annotated constructor.", enclosingElement.getQualifiedName()); } processInjectAnnotatedConstructor(constructorElement, mapTypeElementToConstructorInjectionTarget); } } private void processClassContainingInjectAnnotatedMember(Element enclosingElement, Map mapTypeElementToConstructorInjectionTarget) { final TypeElement typeElement = (TypeElement) typeUtils.asElement(enclosingElement.asType()); if (mapTypeElementToConstructorInjectionTarget.containsKey(typeElement)) { //the class is already known return; } if (isExcludedByFilters(typeElement)) { return; } // Verify common generated code restrictions. if (!canTypeHaveAFactory(typeElement)) { return; } ConstructorInjectionTarget constructorInjectionTarget = createConstructorInjectionTarget(typeElement); if (constructorInjectionTarget != null) { mapTypeElementToConstructorInjectionTarget.put(typeElement, constructorInjectionTarget); } } private boolean isSingleInjectAnnotatedConstructor(Element constructorElement) { TypeElement enclosingElement = (TypeElement) constructorElement.getEnclosingElement(); boolean isSingleInjectedConstructor = true; List constructorElements = ElementFilter.constructorsIn(enclosingElement.getEnclosedElements()); for (ExecutableElement constructorElementInClass : constructorElements) { if (constructorElementInClass.getAnnotation(Inject.class) != null && !constructorElement.equals(constructorElementInClass)) { isSingleInjectedConstructor = false; } } return isSingleInjectedConstructor; } private void processInjectAnnotatedConstructor(ExecutableElement constructorElement, Map targetClassMap) { TypeElement enclosingElement = (TypeElement) constructorElement.getEnclosingElement(); // Verify common generated code restrictions. if (!isValidInjectAnnotatedConstructor(constructorElement)) { return; } if (isExcludedByFilters(enclosingElement)) { return; } if (!canTypeHaveAFactory(enclosingElement)) { error(enclosingElement, "The class %s is abstract or private. It cannot have an injected constructor.", enclosingElement.getQualifiedName()); return; } targetClassMap.put(enclosingElement, createConstructorInjectionTarget(constructorElement)); } private boolean isValidInjectAnnotatedConstructor(ExecutableElement element) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify modifiers. Set modifiers = element.getModifiers(); if (modifiers.contains(PRIVATE)) { error(element, "@Inject constructors must not be private in class %s.", enclosingElement.getQualifiedName()); return false; } // Verify parentScope modifiers. Set parentModifiers = enclosingElement.getModifiers(); if (!parentModifiers.contains(PUBLIC)) { error(element, "Class %s is private. @Inject constructors are not allowed in non public classes.", enclosingElement.getQualifiedName()); return false; } if (isNonStaticInnerClass(enclosingElement)) { return false; } for (VariableElement paramElement : element.getParameters()) { if (!isValidInjectedType(paramElement)) { return false; } } return true; } private ConstructorInjectionTarget createConstructorInjectionTarget(ExecutableElement constructorElement) { TypeElement enclosingElement = (TypeElement) constructorElement.getEnclosingElement(); final String scopeName = getScopeName(enclosingElement); final boolean hasScopeInstancesAnnotation = enclosingElement.getAnnotation(ScopeInstances.class) != null; if (hasScopeInstancesAnnotation && scopeName == null) { error(enclosingElement, "The type %s uses @ScopeInstances but doesn't have a scope annotation.", enclosingElement.getQualifiedName().toString()); } TypeElement superClassWithInjectedMembers = getMostDirectSuperClassWithInjectedMembers(enclosingElement, false); ConstructorInjectionTarget constructorInjectionTarget = new ConstructorInjectionTarget(enclosingElement, scopeName, hasScopeInstancesAnnotation, superClassWithInjectedMembers); constructorInjectionTarget.parameters.addAll(getParamInjectionTargetList(constructorElement)); return constructorInjectionTarget; } private ConstructorInjectionTarget createConstructorInjectionTarget(TypeElement typeElement) { final String scopeName = getScopeName(typeElement); final boolean hasScopeInstancesAnnotation = typeElement.getAnnotation(ScopeInstances.class) != null; if (hasScopeInstancesAnnotation && scopeName == null) { error(typeElement, "The type %s uses @ScopeInstances but doesn't have a scope annotation.", typeElement.getQualifiedName().toString()); } TypeElement superClassWithInjectedMembers = getMostDirectSuperClassWithInjectedMembers(typeElement, false); List constructorElements = ElementFilter.constructorsIn(typeElement.getEnclosedElements()); //we just need to deal with the case of the default constructor only. //like Guice, we will call it by default in the optimistic factory //injected constructors will be handled at some point in the compilation cycle //if there is an injected constructor, it will be caught later, just leave for (ExecutableElement constructorElement : constructorElements) { if (constructorElement.getAnnotation(Inject.class) != null) { return null; } } //search for default constructor for (ExecutableElement constructorElement : constructorElements) { if (constructorElement.getParameters().isEmpty()) { if (constructorElement.getModifiers().contains(Modifier.PRIVATE)) { warning(constructorElement, "The class %s has a private default constructor, Toothpick can't create a factory for it.", typeElement.getQualifiedName().toString()); return null; } ConstructorInjectionTarget constructorInjectionTarget = new ConstructorInjectionTarget(typeElement, scopeName, hasScopeInstancesAnnotation, superClassWithInjectedMembers); return constructorInjectionTarget; } } warning(typeElement, "The class %s has injected fields but has no injected constructor, and no public default constructor." + " Toothpick can't create a factory for it.", typeElement.getQualifiedName().toString()); return null; } /** * Lookup {@link javax.inject.Scope} annotated annotations to provide the name of the scope the {@code typeElement} belongs to. * The method logs an error if the {@code typeElement} has multiple scope annotations. * * @param typeElement the element for which a scope is to be found. * @return the scope of this {@code typeElement} or {@code null} if it has no scope annotations. */ private String getScopeName(TypeElement typeElement) { String scopeName = null; for (AnnotationMirror annotationMirror : typeElement.getAnnotationMirrors()) { TypeElement annotationTypeElement = (TypeElement) annotationMirror.getAnnotationType().asElement(); if (annotationTypeElement.getAnnotation(Scope.class) != null) { if (scopeName != null) { error(typeElement, "Only one @Scope qualified annotation is allowed : %s", scopeName); } scopeName = annotationTypeElement.getQualifiedName().toString(); } } return scopeName; } private boolean canTypeHaveAFactory(TypeElement typeElement) { boolean isAbstract = typeElement.getModifiers().contains(Modifier.ABSTRACT); boolean isPrivate = typeElement.getModifiers().contains(Modifier.PRIVATE); return !isAbstract && !isPrivate; } //used for testing only void setToothpickRegistryPackageName(String toothpickRegistryPackageName) { this.toothpickRegistryPackageName = toothpickRegistryPackageName; } //used for testing only void setToothpickRegistryChildrenPackageNameList(List toothpickRegistryChildrenPackageNameList) { this.toothpickRegistryChildrenPackageNameList = toothpickRegistryChildrenPackageNameList; } //used for testing only void setToothpickExcludeFilters(String toothpickExcludeFilters) { this.toothpickExcludeFilters = toothpickExcludeFilters; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy