toothpick.compiler.factory.FactoryProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of toothpick-compiler Show documentation
Show all versions of toothpick-compiler Show documentation
'Annotation Processors of toothpick'
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 extends TypeElement> 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(Element 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;
}
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