toothpick.compiler.factory.generators.FactoryGenerator 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'
The newest version!
/*
* Copyright 2019 Stephane Nicolas
* Copyright 2019 Daniel Molinero Reguera
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package toothpick.compiler.factory.generators;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import javax.lang.model.element.Modifier;
import javax.lang.model.util.Types;
import toothpick.Factory;
import toothpick.MemberInjector;
import toothpick.Scope;
import toothpick.compiler.common.generators.CodeGenerator;
import toothpick.compiler.common.generators.targets.ParamInjectionTarget;
import toothpick.compiler.factory.targets.ConstructorInjectionTarget;
/**
* Generates a {@link Factory} for a given {@link ConstructorInjectionTarget}. Typically a factory
* is created for a class a soon as it contains an {@link javax.inject.Inject} annotated
* constructor. See Optimistic creation of factories in TP wiki.
*/
public class FactoryGenerator extends CodeGenerator {
private static final String FACTORY_SUFFIX = "__Factory";
private ConstructorInjectionTarget constructorInjectionTarget;
public FactoryGenerator(ConstructorInjectionTarget constructorInjectionTarget, Types types) {
super(types);
this.constructorInjectionTarget = constructorInjectionTarget;
}
public String brewJava() {
// Interface to implement
ClassName className = ClassName.get(constructorInjectionTarget.builtClass);
ParameterizedTypeName parameterizedTypeName =
ParameterizedTypeName.get(ClassName.get(Factory.class), className);
// Build class
TypeSpec.Builder factoryTypeSpec =
TypeSpec.classBuilder(
getGeneratedSimpleClassName(constructorInjectionTarget.builtClass) + FACTORY_SUFFIX)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(parameterizedTypeName);
emitSuperMemberInjectorFieldIfNeeded(factoryTypeSpec);
emitCreateInstance(factoryTypeSpec);
emitGetTargetScope(factoryTypeSpec);
emitHasScopeAnnotation(factoryTypeSpec);
emitHasSingletonAnnotation(factoryTypeSpec);
emitHasReleasableAnnotation(factoryTypeSpec);
emitHasProvidesSingletonAnnotation(factoryTypeSpec);
emitHasProvidesReleasableAnnotation(factoryTypeSpec);
JavaFile javaFile = JavaFile.builder(className.packageName(), factoryTypeSpec.build()).build();
return javaFile.toString();
}
private void emitSuperMemberInjectorFieldIfNeeded(TypeSpec.Builder scopeMemberTypeSpec) {
if (constructorInjectionTarget.superClassThatNeedsMemberInjection != null) {
ClassName superTypeThatNeedsInjection =
ClassName.get(constructorInjectionTarget.superClassThatNeedsMemberInjection);
ParameterizedTypeName memberInjectorSuperParameterizedTypeName =
ParameterizedTypeName.get(
ClassName.get(MemberInjector.class), superTypeThatNeedsInjection);
FieldSpec.Builder superMemberInjectorField =
FieldSpec.builder(
memberInjectorSuperParameterizedTypeName, "memberInjector", Modifier.PRIVATE)
// TODO use proper typing here
.initializer(
"new $L__MemberInjector()",
getGeneratedFQNClassName(
constructorInjectionTarget.superClassThatNeedsMemberInjection));
scopeMemberTypeSpec.addField(superMemberInjectorField.build());
}
}
@Override
public String getFqcn() {
return getGeneratedFQNClassName(constructorInjectionTarget.builtClass) + FACTORY_SUFFIX;
}
private void emitCreateInstance(TypeSpec.Builder builder) {
ClassName className = ClassName.get(constructorInjectionTarget.builtClass);
MethodSpec.Builder createInstanceBuilder =
MethodSpec.methodBuilder("createInstance")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.get(Scope.class), "scope")
.returns(className);
// change the scope to target scope so that all dependencies are created in the target scope
// and the potential injection take place in the target scope too
if (!constructorInjectionTarget.parameters.isEmpty()
|| constructorInjectionTarget.superClassThatNeedsMemberInjection != null) {
// We only need it when the constructor contains parameters or dependencies
createInstanceBuilder.addStatement("scope = getTargetScope(scope)");
}
StringBuilder localVarStatement = new StringBuilder("");
String simpleClassName = getSimpleClassName(className);
localVarStatement.append(simpleClassName).append(" ");
String varName = "" + Character.toLowerCase(className.simpleName().charAt(0));
varName += className.simpleName().substring(1);
localVarStatement.append(varName).append(" = ");
localVarStatement.append("new ");
localVarStatement.append(simpleClassName).append("(");
int counter = 1;
String prefix = "";
CodeBlock.Builder codeBlockBuilder = CodeBlock.builder();
if (constructorInjectionTarget.throwsThrowable) {
codeBlockBuilder.beginControlFlow("try");
}
for (ParamInjectionTarget paramInjectionTarget : constructorInjectionTarget.parameters) {
CodeBlock invokeScopeGetMethodWithNameCodeBlock =
getInvokeScopeGetMethodWithNameCodeBlock(paramInjectionTarget);
String paramName = "param" + counter++;
codeBlockBuilder.add("$T $L = scope.", getParamType(paramInjectionTarget), paramName);
codeBlockBuilder.add(invokeScopeGetMethodWithNameCodeBlock);
codeBlockBuilder.add(";");
codeBlockBuilder.add(LINE_SEPARATOR);
localVarStatement.append(prefix);
localVarStatement.append(paramName);
prefix = ", ";
}
localVarStatement.append(")");
codeBlockBuilder.addStatement(localVarStatement.toString());
if (constructorInjectionTarget.superClassThatNeedsMemberInjection != null) {
codeBlockBuilder.addStatement("memberInjector.inject($L, scope)", varName);
}
codeBlockBuilder.addStatement("return $L", varName);
if (constructorInjectionTarget.throwsThrowable) {
codeBlockBuilder.nextControlFlow("catch($L ex)", ClassName.get(Throwable.class));
codeBlockBuilder.addStatement("throw new $L(ex)", ClassName.get(RuntimeException.class));
codeBlockBuilder.endControlFlow();
}
createInstanceBuilder.addCode(codeBlockBuilder.build());
builder.addMethod(createInstanceBuilder.build());
}
private void emitGetTargetScope(TypeSpec.Builder builder) {
CodeBlock.Builder getParentScopeCodeBlockBuilder = getParentScopeCodeBlockBuilder();
MethodSpec.Builder getScopeBuilder =
MethodSpec.methodBuilder("getTargetScope")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.get(Scope.class), "scope")
.returns(ClassName.get(Scope.class))
.addStatement("return scope$L", getParentScopeCodeBlockBuilder.build().toString());
builder.addMethod(getScopeBuilder.build());
}
private void emitHasScopeAnnotation(TypeSpec.Builder builder) {
String scopeName = constructorInjectionTarget.scopeName;
boolean hasScopeAnnotation = scopeName != null;
MethodSpec.Builder hasScopeAnnotationBuilder =
MethodSpec.methodBuilder("hasScopeAnnotation")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.BOOLEAN)
.addStatement("return $L", hasScopeAnnotation);
builder.addMethod(hasScopeAnnotationBuilder.build());
}
private void emitHasSingletonAnnotation(TypeSpec.Builder builder) {
boolean hasSingletonAnnotation = constructorInjectionTarget.hasSingletonAnnotation;
MethodSpec.Builder hasScopeAnnotationBuilder =
MethodSpec.methodBuilder("hasSingletonAnnotation")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.BOOLEAN)
.addStatement("return $L", hasSingletonAnnotation);
builder.addMethod(hasScopeAnnotationBuilder.build());
}
private void emitHasReleasableAnnotation(TypeSpec.Builder builder) {
boolean hasReleasableAnnotation = constructorInjectionTarget.hasReleasableAnnotation;
MethodSpec.Builder hasScopeAnnotationBuilder =
MethodSpec.methodBuilder("hasReleasableAnnotation")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.BOOLEAN)
.addStatement("return $L", hasReleasableAnnotation);
builder.addMethod(hasScopeAnnotationBuilder.build());
}
private void emitHasProvidesSingletonAnnotation(TypeSpec.Builder builder) {
MethodSpec.Builder hasProducesSingletonBuilder =
MethodSpec.methodBuilder("hasProvidesSingletonAnnotation")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.BOOLEAN)
.addStatement(
"return $L", constructorInjectionTarget.hasProvidesSingletonInScopeAnnotation);
builder.addMethod(hasProducesSingletonBuilder.build());
}
private void emitHasProvidesReleasableAnnotation(TypeSpec.Builder builder) {
MethodSpec.Builder hasProducesSingletonBuilder =
MethodSpec.methodBuilder("hasProvidesReleasableAnnotation")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.BOOLEAN)
.addStatement("return $L", constructorInjectionTarget.hasProvidesReleasableAnnotation);
builder.addMethod(hasProducesSingletonBuilder.build());
}
private CodeBlock.Builder getParentScopeCodeBlockBuilder() {
CodeBlock.Builder getParentScopeCodeBlockBuilder = CodeBlock.builder();
String scopeName = constructorInjectionTarget.scopeName;
if (scopeName != null) {
// there is no scope name or the current @Scoped annotation.
if (javax.inject.Singleton.class.getName().equals(scopeName)) {
getParentScopeCodeBlockBuilder.add(".getRootScope()");
} else {
getParentScopeCodeBlockBuilder.add(".getParentScope($L.class)", scopeName);
}
}
return getParentScopeCodeBlockBuilder;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy