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

toothpick.compiler.factory.generators.FactoryGenerator Maven / Gradle / Ivy

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 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) {
    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);
    emitHasScopeInstancesAnnotation(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
    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 = "";

    for (ParamInjectionTarget paramInjectionTarget : constructorInjectionTarget.parameters) {
      CodeBlock invokeScopeGetMethodWithNameCodeBlock = getInvokeScopeGetMethodWithNameCodeBlock(paramInjectionTarget);
      String paramName = "param" + counter++;
      createInstanceBuilder.addCode("$T $L = scope.", getParamType(paramInjectionTarget), paramName);
      createInstanceBuilder.addCode(invokeScopeGetMethodWithNameCodeBlock);
      createInstanceBuilder.addCode(";");
      createInstanceBuilder.addCode(LINE_SEPARATOR);
      localVarStatement.append(prefix);
      localVarStatement.append(paramName);
      prefix = ", ";
    }

    localVarStatement.append(")");
    createInstanceBuilder.addStatement(localVarStatement.toString());
    if (constructorInjectionTarget.superClassThatNeedsMemberInjection != null) {
      createInstanceBuilder.addStatement("memberInjector.inject($L, scope)", varName);
    }
    createInstanceBuilder.addStatement("return $L", varName);

    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 emitHasScopeInstancesAnnotation(TypeSpec.Builder builder) {
    MethodSpec.Builder hasProducesSingletonBuilder = MethodSpec.methodBuilder("hasScopeInstancesAnnotation")
        .addAnnotation(Override.class)
        .addModifiers(Modifier.PUBLIC)
        .returns(TypeName.BOOLEAN)
        .addStatement("return $L", constructorInjectionTarget.hasScopeInstancesAnnotation);
    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