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

io.avaje.inject.generator.SimpleBeanWriter Maven / Gradle / Ivy

package io.avaje.inject.generator;

import static io.avaje.inject.generator.APContext.createSourceFile;
import static io.avaje.inject.generator.APContext.logError;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import javax.lang.model.type.TypeKind;
import javax.tools.JavaFileObject;

import io.avaje.inject.generator.MethodReader.MethodParam;

/**
 * Write the source code for the bean.
 */
final class SimpleBeanWriter {

  private final BeanReader beanReader;
  private final String originName;
  private final String shortName;
  private final String packageName;
  private final String suffix;
  private final boolean proxied;
  private Append writer;

  SimpleBeanWriter(BeanReader beanReader) {
    this.beanReader = beanReader;
    this.packageName = beanReader.packageName();
    this.shortName = beanReader.shortName();
    this.suffix = beanReader.suffix();
    this.proxied = beanReader.isGenerateProxy();
    this.originName = packageName + "." + shortName;
  }

  private Writer createFileWriter() throws IOException {
    String originName = this.originName;
    if (beanReader.beanType().getNestingKind().isNested()) {
      originName = originName.replace(shortName, shortName.replace(".", "$"));
    }
    final JavaFileObject jfo = createSourceFile(originName + suffix);
    return jfo.openWriter();
  }

  void write() throws IOException {
    writer = new Append(createFileWriter());
    writePackage();
    writeImports();
    writeClassStart();
    if (isRequestScopedController()) {
      writeRequestCreate();
    } else {
      writeGenericTypeFields();
      writeStaticFactoryMethod();
      writeStaticFactoryBeanMethods();
    }
    writeClassEnd();
    writer.close();
  }

  private void writeGenericTypeFields() {
    // collect all types to prevent duplicates
    Set genericTypes =
      beanReader.allGenericTypes().stream()
        .map(Util::unwrapProvider)
        .filter(UType::isGeneric)
        .collect(toSet());

    if (!genericTypes.isEmpty()) {
      final Map seenShortNames = new HashMap<>();
      final Set writtenFields = new HashSet<>();

      for (final UType utype : genericTypes) {
        var type = Util.unwrapProvider(utype);
        final var fieldName = Util.shortName(type).replace(".", "_");
        final var components = type.componentTypes();
        if (components.size() == 1 && components.get(0).kind() == TypeKind.WILDCARD
            || components.stream().anyMatch(u -> u.kind() == TypeKind.TYPEVAR)
            || !writtenFields.add(fieldName)) {
          continue;
        }

        writer.append("  public static final Type TYPE_%s =", fieldName).eol()
          .append("      new GenericType<");
        writeGenericType(type, seenShortNames, writer);
        // use fully qualified types here rather than use type.writeShort(writer)
        writer.append(">(){}.type();").eol();
      }
      writer.eol();
    }
  }

  private void writeGenericType(UType type, Map seenShortNames, Append writer) {
    final var typeShortName = Util.shortName(type.mainType());
    final var mainType = seenShortNames.computeIfAbsent(typeShortName, k -> type.mainType());
    if (type.isGeneric()) {
      final var shortName = Objects.equals(type.mainType(), mainType) ? typeShortName : type.mainType();
      writer.append(shortName);
      writer.append("<");
      boolean first = true;
      for (final var param : type.componentTypes()) {
        if (first) {
          first = false;
          writeGenericType(param, seenShortNames, writer);
          continue;
        }
        writer.append(", ");
        writeGenericType(param, seenShortNames, writer);
      }
      writer.append(">");
    } else {
      final var shortName = Objects.equals(type.mainType(), mainType) ? typeShortName : type.mainType();
      writer.append(shortName);
    }
  }

  private void writeRequestCreate() {
    beanReader.writeRequestCreate(writer);
  }

  private boolean isRequestScopedController() {
    return beanReader.isRequestScopedController();
  }

  private void writeStaticFactoryBeanMethods() {
    for (MethodReader factoryMethod : beanReader.factoryMethods()) {
      writeFactoryBeanMethod(factoryMethod);
    }
  }

  private void writeFactoryBeanMethod(MethodReader method) {
    method.commentBuildMethod(writer);
    writer.append("  public static void build_%s(%s builder) {", method.name(), beanReader.builderType()).eol();
    method.buildConditional(writer);
    method.buildAddFor(writer);
    method.builderGetFactory(writer, beanReader.hasConditions());
    if (method.isLazy() || method.isProtoType() || method.isUseProviderForSecondary()) {
      method.builderAddBeanProvider(writer);
    } else {
      method.startTry(writer);
      method.builderBuildBean(writer);
      method.builderBuildAddBean(writer);
      method.endTry(writer);
      writer.append("    }").eol();
    }
    writer.append("  }").eol().eol();
  }

  private void writeStaticFactoryMethod() {
    MethodReader constructor = beanReader.constructor();
    if (constructor == null) {
      logError(beanReader.beanType(), "Unable to determine constructor to use for %s? Add explicit @Inject to one of the constructors.", beanReader.beanType());
      return;
    }
    writeBuildMethodStart();
    if (proxied) {
      writer.append("    // this bean is proxied, see %s$Proxy$DI instead", shortName).eol();
    } else {
      writeAddFor(constructor);
    }
    writer.append("  }").eol().eol();
  }

  private void writeAddFor(MethodReader constructor) {
    beanReader.buildConditional(writer);
    beanReader.buildBeanAbsent(writer);
    if (beanReader.registerProvider()) {
      indent += "  ";

      final String registerProvider;
      if (beanReader.lazy()) {
        registerProvider = "registerProvider";
      } else {
        registerProvider = "asPrototype().registerProvider";
      }

      writer.append("      builder.%s(() -> {", registerProvider).eol();
    }
    constructor.startTry(writer);
    writeCreateBean(constructor);
    beanReader.buildRegister(writer);
    beanReader.addLifecycleCallbacks(writer, indent);
    if (beanReader.isExtraInjectionRequired()) {
      writeExtraInjection();
    }
    if (beanReader.registerProvider()) {
      beanReader.prototypePostConstruct(writer, indent);
      writer.indent("        return bean;").eol();
      if (!constructor.methodThrows()) {
        writer.indent("      });").eol();
      }
    }
    writeObserveMethods();
    constructor.endTry(writer);

    if (beanReader.registerProvider() && constructor.methodThrows()) {
      writer.append("     }");
      writer.append(");").eol();
    }

    writer.append("    }");
    writer.eol();
  }

  private void writeBuildMethodStart() {
    writer.append("  public static void build(%s builder) {", beanReader.builderType()).eol();
  }

  private String indent = "     ";

  private void writeCreateBean(MethodReader constructor) {
    writer.indent(indent).append(" var bean = new %s(", shortName);
    // add constructor dependencies
    writeMethodParams("builder", constructor);
  }

  private void writeExtraInjection() {
    if (!beanReader.registerProvider()) {
      writer.indent(indent).append(" builder.addInjector(b -> {").eol();
      writer.indent(indent).append("   // field and method injection").eol();
    }
    injectFields();
    injectMethods();
    if (!beanReader.registerProvider()) {
      writer.indent(indent).append(" });").eol();
    }
  }

  private void writeObserveMethods() {
    final var bean = "bean";
    final var builder = "builder";

    final var indent = "      ";
    for (MethodReader methodReader : beanReader.observerMethods()) {
      var observeEvent = methodReader.observeParam();
      var observeUtype = observeEvent.getFullUType();
      final var shortWithoutAnnotations = observeUtype.shortWithoutAnnotations();
      var injectParams = methodReader.params().stream().skip(1).collect(toList());

      for (MethodParam param : injectParams) {
        writer.indent(indent).append("var %s = ", methodReader.name() + "$" + param.simpleName());
        param.builderGetDependency(writer, builder);
        writer.append(";").eol();
      }

      writer.indent(indent).append("Consumer<%s> %s = ", shortWithoutAnnotations, methodReader.name());

      var observeTypeString =
        !observeUtype.isGeneric() || observeUtype.param0().kind() == TypeKind.WILDCARD
          ? Util.shortName(observeUtype.mainType()) + ".class"
          : "TYPE_" + Util.shortName(observeUtype).replace(".", "_");

      if (methodReader.params().size() == 1) {
        writer.append("%s::%s;", bean, methodReader.name());
      } else {
        var injectParamNames = injectParams.stream()
          .map(p -> methodReader.name() + "$" + p.simpleName())
          .collect(joining(", "));
        writer.append("e -> bean.%s(e, %s);", methodReader.name(), injectParamNames);
      }
      final var observesPrism = ObservesPrism.getInstanceOn(observeEvent.element());
      writer
          .eol()
          .indent(indent)
          .append(builder)
          .eol()
          .indent(indent)
          .append("    .get(ObserverManager.class)")
          .eol()
          .indent(indent)
          .append("    .<%s>registerObserver(", shortWithoutAnnotations)
          .eol()
          .indent(indent)
          .append(
              "        %s, new Observer<>(%s, %s, %s, \"%s\"));",
              observeTypeString,
              observesPrism.priority(),
              observesPrism.async(),
              methodReader.name(),
              observeEvent.qualifier())
          .eol();
    }
  }

  private void injectFields() {
    String bean = beanReader.registerProvider() ? "bean" : "$bean";
    String builder = beanReader.registerProvider() ? "builder" : "b";
    for (FieldReader fieldReader : beanReader.injectFields()) {
      String fieldName = fieldReader.fieldName();
      String getDependency = fieldReader.builderGetDependency(builder);
      writer.indent("        ").append("%s.%s = %s;", bean, fieldName, getDependency).eol();
    }
  }

  private void injectMethods() {
    final var needsTry = beanReader.needsTryForMethodInjection();
    final var bean = beanReader.registerProvider() ? "bean" : "$bean";
    final var builder = beanReader.registerProvider() ? "builder" : "b";
    if (needsTry) {
      writer.indent("        try {").eol();
    }
    final var indent = needsTry ? "          " : "        ";
    for (MethodReader methodReader : beanReader.injectMethods()) {
      writer.indent(indent).append("%s.%s(", bean, methodReader.name());
      writeMethodParams(builder, methodReader);
    }
    if (needsTry) {
      writer.indent("        } catch (Throwable e) {").eol();
      writer.indent("          throw new RuntimeException(\"Error wiring method\", e);").eol();
      writer.indent("        }").eol();
    }
  }

  private void writeMethodParams(String builderRef, MethodReader methodReader) {
    List methodParams = methodReader.params();
    for (int i = 0; i < methodParams.size(); i++) {
      if (i > 0) {
        writer.append(", ");
      }
      methodParams.get(i).builderGetDependency(writer, builderRef);
    }
    writer.append(");").eol();
  }

  private void writeImports() {
    beanReader.writeImports(writer, packageName);
  }

  private void writeClassEnd() {
    writer.append("}").eol();
  }

  private void writeClassStart() {
    final var requestScopedController = beanReader.isRequestScopedController();
    writer.append(beanReader.generatedType()).append(Constants.AT_GENERATED_COMMENT).eol();
    if (requestScopedController) {
      writer.append(Constants.AT_SINGLETON).eol();
    }
    String shortName = this.shortName;
    if (beanReader.beanType().getNestingKind().isNested()) {
      shortName = shortName.replace(".", "$");
    }
    writer
      .append("public final %sclass ", requestScopedController ? "" : Util.valhalla())
      .append(shortName)
      .append(suffix)
      .append(" ");
    if (requestScopedController) {
      writer.append("implements ");
      beanReader.factoryInterface(writer);
    }
    writer.append(" {").eol().eol();
  }

  private void writePackage() {
    if (packageName != null) {
      writer.append("package %s;", packageName).eol().eol();
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy