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

net.autobuilder.core.Analyser Maven / Gradle / Ivy

package net.autobuilder.core;

import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;

import javax.annotation.Generated;
import java.util.ArrayList;
import java.util.List;

import static java.util.Arrays.asList;
import static java.util.Objects.requireNonNull;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.STATIC;
import static net.autobuilder.core.AutoBuilderProcessor.rawType;
import static net.autobuilder.core.Util.joinCodeBlocks;

final class Analyser {

  private final Model model;
  private final MethodSpec initMethod;
  private final MethodSpec staticBuildMethod;
  private final RefTrackingBuilder optionalRefTrackingBuilder;

  private Analyser(Model model) {
    this.model = model;
    this.initMethod = initMethod(model);
    this.staticBuildMethod = staticBuildMethod(model);
    this.optionalRefTrackingBuilder = RefTrackingBuilder.create(model, staticBuildMethod);
  }

  static Analyser create(Model model) {
    return new Analyser(model);
  }

  TypeSpec analyse() {
    TypeSpec.Builder builder = TypeSpec.classBuilder(rawType(model.generatedClass));
    builder.addTypeVariables(model.typevars());
    builder.addMethod(builderMethod());
    builder.addMethod(builderMethodWithParam());
    builder.addMethod(perThreadFactoryMethod(optionalRefTrackingBuilder));
    builder.addMethod(initMethod);
    builder.addMethod(staticBuildMethod);
    builder.addMethod(abstractBuildMethod());
    builder.addType(SimpleBuilder.create(model, staticBuildMethod).define());
    if (optionalRefTrackingBuilder != null) {
      RefTrackingBuilder refTrackingBuilder = requireNonNull(optionalRefTrackingBuilder);
      builder.addType(refTrackingBuilder.define());
      builder.addType(PerThreadFactory.create(model, initMethod, refTrackingBuilder).define());
    } else {
      builder.addType(PerThreadFactory.createStub(model));
    }
    for (Parameter parameter : model.parameters) {
      builder.addField(parameter.asInitializedField());
      builder.addMethod(setterMethod(parameter));
      parameter.optionalish()
          .map(optionalish -> optionalSetterMethod(parameter,
              optionalish))
          .ifPresent(builder::addMethod);
      parameter.collectionish()
          .filter(Collectionish::hasBuilder)
          .ifPresent(collectionish ->
              builder.addField(
                  parameter.asBuilderField()));
      parameter.collectionish()
          .filter(Collectionish::hasBuilder)
          .ifPresent(collectionish -> {
            builder.addMethod(collectorMethod(parameter, collectionish));
            parameter.addAllType().ifPresent(addAllType ->
                builder.addMethod(collectorMethodAddAll(parameter, collectionish, addAllType)));
          });
    }
    builder.addModifiers(model.maybePublic());
    return builder.addModifiers(ABSTRACT)
        .addMethod(MethodSpec.constructorBuilder()
            .addModifiers(PRIVATE).build())
        .addAnnotation(AnnotationSpec.builder(Generated.class)
            .addMember("value", "$S", AutoBuilderProcessor.class.getCanonicalName())
            .build())
        .build();
  }

  private MethodSpec collectorMethod(Parameter parameter, Collectionish collectionish) {
    if (collectionish.type == Collectionish.CollectionType.MAP) {
      return putInMethod(parameter, collectionish);
    }
    return addToMethod(parameter, collectionish);
  }

  private MethodSpec collectorMethodAddAll(
      Parameter parameter, Collectionish collectionish, ParameterizedTypeName addAllType) {
    if (collectionish.type == Collectionish.CollectionType.MAP) {
      return putAllInMethod(parameter, collectionish, addAllType);
    }
    return addAllToMethod(parameter, collectionish, addAllType);
  }

  private MethodSpec addToMethod(Parameter parameter, Collectionish collectionish) {
    FieldSpec field = parameter.asField().build();
    FieldSpec builderField = parameter.asBuilderField();
    ParameterizedTypeName builderType = parameter.builderType();
    ParameterSpec key =
        ParameterSpec.builder(builderType.typeArguments.get(0), "value").build();
    CodeBlock.Builder block = CodeBlock.builder();
    block.beginControlFlow("if (this.$N == null)", builderField)
        .addStatement("this.$N = $T.builder()",
            builderField, collectionish.factoryClassName)
        .endControlFlow();
    block.beginControlFlow("if (this.$N != null)", field)
        .addStatement("this.$N.$L(this.$N)",
            builderField, collectionish.addAllMethod, field)
        .addStatement("this.$N = null", field)
        .endControlFlow();
    block.addStatement("this.$N.$L($N)",
        builderField, collectionish.addMethod, key);
    return MethodSpec.methodBuilder(
        parameter.accumulatorName(collectionish))
        .addCode(block.build())
        .addStatement("return this")
        .addParameter(key)
        .addModifiers(FINAL)
        .addModifiers(model.maybePublic())
        .returns(model.generatedClass)
        .build();
  }

  private MethodSpec addAllToMethod(
      Parameter parameter, Collectionish collectionish, ParameterizedTypeName addAllType) {
    FieldSpec field = parameter.asField().build();
    FieldSpec builderField = parameter.asBuilderField();
    ParameterSpec values =
        ParameterSpec.builder(addAllType, "values").build();
    CodeBlock.Builder block = CodeBlock.builder();
    block.beginControlFlow("if (this.$N == null)", builderField)
        .addStatement("this.$N = $T.builder()",
            builderField, collectionish.factoryClassName)
        .endControlFlow();
    block.beginControlFlow("if (this.$N != null)", field)
        .addStatement("this.$N.$L(this.$N)",
            builderField, collectionish.addAllMethod, field)
        .addStatement("this.$N = null", field)
        .endControlFlow();
    block.addStatement("this.$N.$L($N)",
        builderField, collectionish.addAllMethod, values);
    return MethodSpec.methodBuilder(
        parameter.accumulatorName(collectionish))
        .addCode(block.build())
        .addStatement("return this")
        .addParameter(values)
        .addModifiers(FINAL)
        .addModifiers(model.maybePublic())
        .returns(model.generatedClass)
        .build();
  }

  private MethodSpec putInMethod(Parameter parameter, Collectionish collectionish) {
    FieldSpec field = parameter.asField().build();
    FieldSpec builderField = parameter.asBuilderField();
    ParameterizedTypeName builderType = parameter.builderType();
    ParameterSpec key =
        ParameterSpec.builder(builderType.typeArguments.get(0), "key").build();
    ParameterSpec value =
        ParameterSpec.builder(builderType.typeArguments.get(1), "value").build();
    CodeBlock.Builder block = CodeBlock.builder();
    block.beginControlFlow("if (this.$N == null)", builderField)
        .addStatement("this.$N = $T.builder()",
            builderField, collectionish.factoryClassName)
        .endControlFlow();
    block.beginControlFlow("if (this.$N != null)", field)
        .addStatement("this.$N.$L(this.$N)",
            builderField, collectionish.addAllMethod, field)
        .addStatement("this.$N = null", field)
        .endControlFlow();
    block.addStatement("this.$N.$L($N, $N)",
        builderField, collectionish.addMethod, key, value);
    return MethodSpec.methodBuilder(
        parameter.accumulatorName(collectionish))
        .addCode(block.build())
        .addStatement("return this")
        .addParameters(asList(key, value))
        .addModifiers(FINAL)
        .addModifiers(model.maybePublic())
        .returns(model.generatedClass)
        .build();
  }

  private MethodSpec putAllInMethod(
      Parameter parameter, Collectionish collectionish, ParameterizedTypeName addAllType) {
    FieldSpec field = parameter.asField().build();
    FieldSpec builderField = parameter.asBuilderField();
    ParameterSpec map =
        ParameterSpec.builder(addAllType, "map").build();
    CodeBlock.Builder block = CodeBlock.builder();
    block.beginControlFlow("if (this.$N == null)", builderField)
        .addStatement("this.$N = $T.builder()",
            builderField, collectionish.factoryClassName)
        .endControlFlow();
    block.beginControlFlow("if (this.$N != null)", field)
        .addStatement("this.$N.$L(this.$N)",
            builderField, collectionish.addAllMethod, field)
        .addStatement("this.$N = null", field)
        .endControlFlow();
    block.addStatement("this.$N.$L($N)",
        builderField, collectionish.addAllMethod, map);
    return MethodSpec.methodBuilder(
        parameter.accumulatorName(collectionish))
        .addCode(block.build())
        .addStatement("return this")
        .addParameter(map)
        .addModifiers(FINAL)
        .addModifiers(model.maybePublic())
        .returns(model.generatedClass)
        .build();
  }

  private MethodSpec perThreadFactoryMethod(RefTrackingBuilder optionalRefTrackingBuilder) {
    MethodSpec.Builder builder = MethodSpec.methodBuilder("perThreadFactory")
        .returns(RefTrackingBuilder.perThreadFactoryClass(model))
        .addModifiers(STATIC);
    if (optionalRefTrackingBuilder != null) {
      RefTrackingBuilder refTrackingBuilder = requireNonNull(optionalRefTrackingBuilder);
      return builder.addStatement("return new $T()",
          refTrackingBuilder.perThreadFactoryClass)
          .build();
    } else {
      return builder.addStatement("throw new $T(\n$S)",
          UnsupportedOperationException.class, model.cacheWarning())
          .addModifiers(PRIVATE)
          .build();
    }
  }

  private static MethodSpec initMethod(Model model) {
    ParameterSpec builder = ParameterSpec.builder(model.generatedClass, "builder").build();
    ParameterSpec input = ParameterSpec.builder(model.sourceClass, "input").build();
    CodeBlock.Builder block = CodeBlock.builder();
    for (Parameter parameter : model.parameters) {
      block.addStatement("$N.$N = $N.$L()", builder, parameter.setterName, input,
          parameter.getterName);
    }
    return MethodSpec.methodBuilder("init")
        .addCode(block.build())
        .addParameters(asList(builder, input))
        .addModifiers(PRIVATE, STATIC)
        .addTypeVariables(model.typevars())
        .build();
  }

  private MethodSpec setterMethod(Parameter parameter) {
    ParameterSpec p = parameter.asParameter();
    CodeBlock.Builder block = CodeBlock.builder();
    block.add(parameter.setterAssignment());
    parameter.collectionish()
        .filter(Collectionish::hasBuilder)
        .ifPresent(collectionish ->
            block.addStatement("this.$N = null",
                parameter.asBuilderField()));
    block.addStatement("return this");
    return MethodSpec.methodBuilder(
        parameter.setterName)
        .addCode(block.build())
        .addParameter(p)
        .addModifiers(FINAL)
        .addModifiers(model.maybePublic())
        .returns(model.generatedClass)
        .build();
  }

  private MethodSpec optionalSetterMethod(Parameter parameter, Optionalish optionalish) {
    FieldSpec f = parameter.asField().build();
    ParameterSpec p = ParameterSpec.builder(optionalish.wrapped,
        parameter.setterName).build();
    CodeBlock.Builder block = CodeBlock.builder();
    if (optionalish.isOptional()) {
      block.addStatement("this.$N = $T.ofNullable($N)", f, optionalish.wrapper, p);
    } else {
      block.addStatement("this.$N = $T.of($N)", f, optionalish.wrapper, p);
    }
    return MethodSpec.methodBuilder(
        parameter.setterName)
        .addCode(block.build())
        .addStatement("return this")
        .addParameter(p)
        .addModifiers(FINAL)
        .addModifiers(model.maybePublic())
        .returns(model.generatedClass)
        .build();
  }

  private MethodSpec builderMethod() {
    return MethodSpec.methodBuilder("builder")
        .addModifiers(STATIC)
        .addTypeVariables(model.typevars())
        .addStatement("return new $T()", model.simpleBuilderClass)
        .returns(model.generatedClass)
        .build();
  }

  private MethodSpec builderMethodWithParam() {
    ParameterSpec builder = ParameterSpec.builder(model.generatedClass, "builder").build();
    ParameterSpec input = ParameterSpec.builder(model.sourceClass, "input").build();
    CodeBlock.Builder block = CodeBlock.builder()
        .addStatement("$T $N = new $T()", builder.type, builder, model.simpleBuilderClass)
        .addStatement("$N($N, $N)", initMethod, builder, input)
        .addStatement("return $N", builder);
    return MethodSpec.methodBuilder("builder")
        .addCode(block.build())
        .addParameter(input)
        .addModifiers(STATIC)
        .addTypeVariables(model.typevars())
        .returns(model.generatedClass)
        .build();
  }

  private static MethodSpec staticBuildMethod(Model model) {
    ParameterSpec builder = ParameterSpec.builder(model.generatedClass, "builder").build();
    List block = new ArrayList<>(model.parameters.size());
    for (int i = 0; i < model.parameters.size(); i++) {
      Parameter parameter = model.parameters.get(i);
      FieldSpec field = parameter.asField().build();
      block.add(parameter.collectionish()
          .filter(Collectionish::hasBuilder)
          .map(collectionish -> {
            FieldSpec builderField = parameter.asBuilderField();
            return CodeBlock.of("$N.$N != null ? $N.$N.build() : $N.$N",
                builder, builderField,
                builder, builderField,
                builder, field);
          })
          .orElse(CodeBlock.of("$N.$N", builder, field)));
    }
    return MethodSpec.methodBuilder("build")
        .addCode("return new $T(\n    ", model.avType)
        .addCode(block.stream().collect(joinCodeBlocks(",\n    ")))
        .addCode(");\n")
        .addTypeVariables(model.typevars())
        .returns(model.sourceClass)
        .addParameter(builder)
        .addModifiers(PRIVATE, STATIC)
        .build();
  }

  private MethodSpec abstractBuildMethod() {
    return MethodSpec.methodBuilder("build")
        .returns(model.sourceClass)
        .addModifiers(ABSTRACT)
        .addModifiers(model.maybePublic())
        .build();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy