Repository: avaje/avaje-inject
Pull request #440 (2023-10-24 22:20:01)

#439 Handle case where Factory method throws exception
Adds a try catch block to the generated code


Patch:
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/Append.java b/inject-generator/src/main/java/io/avaje/inject/generator/Append.java
index b014d5ec..092780ae 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/Append.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/Append.java
@@ -12,11 +12,29 @@ final class Append {
   private final Writer writer;
   private int nameIndex;
   private boolean comma;
+  private String extraIndent;
 
   Append(Writer writer) {
     this.writer = writer;
   }
 
+  Append setExtraIndent(String extraIndent) {
+    this.extraIndent = extraIndent;
+    return this;
+  }
+
+  Append indent(String content) {
+    try {
+      if (extraIndent != null) {
+        writer.append(extraIndent);
+      }
+      writer.append(content);
+      return this;
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
   Append append(String content) {
     try {
       writer.append(content);
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
index b084af34..7d810633 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
@@ -240,7 +240,7 @@ final class BeanReader {
     if (prototype) {
       return;
     }
-    writer.append("      ");
+    writer.indent("      ");
     if (isExtraInjectionRequired() || hasLifecycleMethods()) {
       writer.append("var $bean = ");
     }
@@ -255,14 +255,14 @@ final class BeanReader {
 
   void addLifecycleCallbacks(Append writer, String indent) {
     if (postConstructMethod != null && !prototype) {
-      writer.append("%s builder.addPostConstruct($bean::%s);", indent, postConstructMethod.getSimpleName()).eol();
+      writer.indent(indent).append(" builder.addPostConstruct($bean::%s);", postConstructMethod.getSimpleName()).eol();
     }
     if (preDestroyMethod != null) {
       prototypeNotSupported("@PreDestroy");
       var priority = preDestroyPriority == null || preDestroyPriority == 1000 ? "" : ", " + preDestroyPriority;
-      writer.append("%s builder.addPreDestroy($bean::%s%s);", indent, preDestroyMethod.getSimpleName(), priority).eol();
+      writer.indent(indent).append(" builder.addPreDestroy($bean::%s%s);", preDestroyMethod.getSimpleName(), priority).eol();
     } else if (typeReader.isClosable() && !prototype) {
-      writer.append("%s builder.addPreDestroy($bean);", indent).eol();
+      writer.indent(indent).append(" builder.addPreDestroy($bean);").eol();
     }
   }
 
@@ -448,4 +448,13 @@ final class BeanReader {
   private String beanQualifiedName() {
     return beanType.getQualifiedName().toString();
   }
+
+  boolean needsTryForMethodInjection() {
+    for (MethodReader injectMethod : injectMethods) {
+      if (injectMethod.methodThrows()) {
+        return true;
+      }
+    }
+    return false;
+  }
 }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java
index 3bf5d381..53b3b2d4 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java
@@ -26,7 +26,6 @@ final class MethodReader {
   private final boolean isVoid;
   private final List<MethodParam> params = new ArrayList<>();
   private final String factoryShortName;
-  private final boolean isFactory;
   private final String initMethod;
   private final String destroyMethod;
   private final Integer destroyPriority;
@@ -41,15 +40,12 @@ final class MethodReader {
   }
 
   MethodReader(ExecutableElement element, TypeElement beanType, BeanPrism bean, String qualifierName, ImportTypeMap importTypes) {
-    this.isFactory = bean != null;
     this.element = element;
-    if (isFactory) {
+    if (bean != null) {
       prototype = PrototypePrism.isPresent(element);
       primary = PrimaryPrism.isPresent(element);
       secondary = SecondaryPrism.isPresent(element);
-
       conditions.readAll(element);
-
     } else {
       prototype = false;
       primary = false;
@@ -164,7 +160,7 @@ final class MethodReader {
   }
 
   void builderBuildBean(Append writer) {
-    writer.append("      ");
+    writer.indent("      ");
     if (isVoid) {
       writer.append("factory.%s(", methodName);
     } else {
@@ -175,7 +171,7 @@ final class MethodReader {
       if (i > 0) {
         writer.append(", ");
       }
-      params.get(i).builderGetDependency(writer, "builder", true);
+      params.get(i).builderGetDependency(writer, "builder");
     }
     writer.append(");").eol();
   }
@@ -191,31 +187,31 @@ final class MethodReader {
     }
     String indent = "    ";
     if (prototype) {
-      writer.append(indent).append("  builder.asPrototype().registerProvider(() -> {").eol();
+      writer.indent(indent).append("  builder.asPrototype().registerProvider(() -> {").eol();
     } else {
-      writer.append(indent).append("  builder.asSecondary().registerProvider(() -> {").eol();
+      writer.indent(indent).append("  builder.asSecondary().registerProvider(() -> {").eol();
     }
-    writer.append("%s    return ", indent);
-    writer.append(String.format("factory.%s(", methodName));
+    writer.indent(indent).append("    return ");
+    writer.append("factory.%s(", methodName);
     for (int i = 0; i < params.size(); i++) {
       if (i > 0) {
         writer.append(", ");
       }
-      params.get(i).builderGetDependency(writer, "builder", true);
+      params.get(i).builderGetDependency(writer, "builder");
     }
     writer.append(");").eol();
-    writer.append(indent).append("  });").eol();
-    writer.append(indent).append("}").eol();
+    writer.indent(indent).append("  });").eol();
+    writer.indent(indent).append("}").eol();
   }
 
   void builderBuildAddBean(Append writer) {
     if (!isVoid) {
-      String indent = optionalType ? "        " : "      ";
       if (optionalType) {
         writer.append("      if (optionalBean.isPresent()) {").eol();
         writer.append("        var bean = optionalBean.get();").eol();
       }
-      writer.append(indent);
+      String indent = optionalType ? "        " : "      ";
+      writer.indent(indent);
       if (hasLifecycleMethods()) {
         writer.append("var $bean = ");
       }
@@ -227,15 +223,15 @@ final class MethodReader {
       }
       writer.append(".register(bean);").eol();
       if (notEmpty(initMethod)) {
-        writer.append(indent).append("builder.addPostConstruct($bean::%s);", initMethod).eol();
+        writer.indent(indent).append("builder.addPostConstruct($bean::%s);", initMethod).eol();
       }
       var priority = destroyPriority == null || destroyPriority == 1000 ? "" : ", " + destroyPriority;
       if (notEmpty(destroyMethod)) {
-        writer.append(indent).append("builder.addPreDestroy($bean::%s%s);", destroyMethod, priority).eol();
+        writer.indent(indent).append("builder.addPreDestroy($bean::%s%s);", destroyMethod, priority).eol();
       } else if (typeReader != null && typeReader.isClosable()) {
-        writer.append(indent).append("builder.addPreDestroy($bean::close%s);", priority).eol();
+        writer.indent(indent).append("builder.addPreDestroy($bean::close%s);", priority).eol();
       } else if (beanCloseable) {
-        writer.append(indent).append("builder.addAutoClosable($bean);").eol();
+        writer.indent(indent).append("builder.addAutoClosable($bean);").eol();
       }
       if (optionalType) {
         writer.append("      }").eol();
@@ -285,6 +281,26 @@ final class MethodReader {
     writer.append(")) {").eol();
   }
 
+  boolean methodThrows() {
+    return !element.getThrownTypes().isEmpty();
+  }
+
+  void startTry(Append writer) {
+    if (methodThrows()) {
+      writer.append("      try {").eol();
+      writer.setExtraIndent("  ");
+    }
+  }
+
+  void endTry(Append writer) {
+    if (methodThrows()) {
+      writer.setExtraIndent(null);
+      writer.append("      } catch (Throwable e) {").eol();
+      writer.append("        throw new RuntimeException(\"Error during wiring\", e);").eol();
+      writer.append("      }").eol();
+    }
+  }
+
   /**
    * Check for request scoped dependency.
    */
@@ -356,7 +372,7 @@ final class MethodReader {
 
   static class MethodParam {
 
-    private VariableElement element;
+    private final VariableElement element;
     private final String named;
     private final UtilType utilType;
     private final String paramType;
@@ -382,7 +398,6 @@ final class MethodReader {
       if (nullable || param.asType().toString().startsWith("java.util.Optional<")) {
         ProcessingContext.addOptionalType(paramType);
       }
-
     }
 
     @Override
@@ -396,7 +411,7 @@ final class MethodReader {
       }
     }
 
-    void builderGetDependency(Append writer, String builderName, boolean forFactory) {
+    void builderGetDependency(Append writer, String builderName) {
       writer.append(builderName).append(".").append(utilType.getMethod(nullable, isBeanMap));
       if (!genericType.isGenericType()) {
         writer.append(Util.shortName(genericType.topType())).append(".class");
@@ -472,11 +487,9 @@ final class MethodReader {
     }
 
     void writeMethodParam(Append writer) {
-
       if (nullable) {
         writer.append("@Nullable ");
       }
-
       if (genericType.isGenericType()) {
         genericType.writeShort(writer);
       } else {
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java
index 92f8543b..2910c154 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java
@@ -135,13 +135,14 @@ final class SimpleBeanWriter {
     method.buildConditional(writer);
     method.buildAddFor(writer);
     method.builderGetFactory(writer, beanReader.hasConditions());
-    if (method.isProtoType()) {
-      method.builderAddBeanProvider(writer);
-    } else if (method.isUseProviderForSecondary()) {
+    method.startTry(writer);
+    if (method.isProtoType() || method.isUseProviderForSecondary()) {
       method.builderAddBeanProvider(writer);
+      method.endTry(writer);
     } else {
       method.builderBuildBean(writer);
       method.builderBuildAddBean(writer);
+      method.endTry(writer);
       writer.append("    }").eol();
     }
     writer.append("  }").eol().eol();
@@ -169,6 +170,7 @@ final class SimpleBeanWriter {
       indent += "  ";
       writer.append("      builder.asPrototype().registerProvider(() -> {", shortName, shortName).eol();
     }
+    constructor.startTry(writer);
     writeCreateBean(constructor);
     beanReader.buildRegister(writer);
     beanReader.addLifecycleCallbacks(writer, indent);
@@ -177,9 +179,10 @@ final class SimpleBeanWriter {
     }
     if (beanReader.prototype()) {
       beanReader.prototypePostConstruct(writer, indent);
-      writer.append("        return bean;").eol();
-      writer.append("      });", shortName, shortName).eol();
+      writer.indent("        return bean;").eol();
+      writer.indent("      });").eol();
     }
+    constructor.endTry(writer);
     writer.append("    }").eol();
   }
 
@@ -195,20 +198,20 @@ final class SimpleBeanWriter {
   private String indent = "     ";
 
   private void writeCreateBean(MethodReader constructor) {
-    writer.append(indent).append(" var bean = new %s(", shortName);
+    writer.indent(indent).append(" var bean = new %s(", shortName);
     // add constructor dependencies
     writeMethodParams("builder", constructor);
   }
 
   private void writeExtraInjection() {
     if (!beanReader.prototype()) {
-      writer.append("      builder.addInjector(b -> {").eol();
-      writer.append("        // field and method injection").eol();
+      writer.indent("      ").append("builder.addInjector(b -> {").eol();
+      writer.indent("      ").append("  // field and method injection").eol();
     }
     injectFields();
     injectMethods();
     if (!beanReader.prototype()) {
-      writer.append("      });").eol();
+      writer.indent("      });").eol();
     }
   }
 
@@ -218,17 +221,27 @@ final class SimpleBeanWriter {
     for (FieldReader fieldReader : beanReader.injectFields()) {
       String fieldName = fieldReader.fieldName();
       String getDependency = fieldReader.builderGetDependency(builder);
-      writer.append("        %s.%s = %s;", bean, fieldName, getDependency).eol();
+      writer.indent("        ").append("%s.%s = %s;", bean, fieldName, getDependency).eol();
     }
   }
 
   private void injectMethods() {
-    String bean = beanReader.prototype() ? "bean" : "$bean";
-    String builder = beanReader.prototype() ? "builder" : "b";
+    final var needsTry = beanReader.needsTryForMethodInjection();
+    final var bean = beanReader.prototype() ? "bean" : "$bean";
+    final var builder = beanReader.prototype() ? "builder" : "b";
+    if (needsTry) {
+      writer.indent("        try {").eol();
+    }
+    final var indent = needsTry ? "          " : "        ";
     for (MethodReader methodReader : beanReader.injectMethods()) {
-      writer.append("        %s.%s(", bean, methodReader.name());
+      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) {
@@ -237,7 +250,7 @@ final class SimpleBeanWriter {
       if (i > 0) {
         writer.append(", ");
       }
-      methodParams.get(i).builderGetDependency(writer, builderRef, false);
+      methodParams.get(i).builderGetDependency(writer, builderRef);
     }
     writer.append(");").eol();
   }


Test patch:
diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/config/AFactory.java b/blackbox-test-inject/src/main/java/org/example/myapp/config/AFactory.java
index 29c2132b..ccf5f826 100644
--- a/blackbox-test-inject/src/main/java/org/example/myapp/config/AFactory.java
+++ b/blackbox-test-inject/src/main/java/org/example/myapp/config/AFactory.java
@@ -3,6 +3,8 @@ package org.example.myapp.config;
 import io.avaje.inject.Bean;
 import io.avaje.inject.Factory;
 
+import java.io.IOException;
+
 @Factory
 class AFactory {
 
@@ -27,10 +29,19 @@ class AFactory {
     b2.hashCode();
   }
 
+  @Bean
+  AFoo buildAfoo() throws IOException {
+    return new AFoo();
+  }
+
   static class I0 implements A0.Builder {
 
   }
   static class I1 implements A1.Builder {
 
   }
+
+  static class AFoo {
+
+  }
 }
diff --git a/inject-test/src/test/java/org/example/autonamed/MyAutoB2OneImplied.java b/inject-test/src/test/java/org/example/autonamed/MyAutoB2OneImplied.java
index 707b2a62..dd802f73 100644
--- a/inject-test/src/test/java/org/example/autonamed/MyAutoB2OneImplied.java
+++ b/inject-test/src/test/java/org/example/autonamed/MyAutoB2OneImplied.java
@@ -2,12 +2,14 @@ package org.example.autonamed;
 
 import jakarta.inject.Singleton;
 
+import javax.annotation.PostConstruct;
+
 @Singleton
 public class MyAutoB2OneImplied {
 
   final AutoB2 one;
 
-  public MyAutoB2OneImplied(AutoB2 one) {
+  public MyAutoB2OneImplied(AutoB2 one) throws Exception {
     this.one = one;
   }
 
@@ -15,4 +17,9 @@ public class MyAutoB2OneImplied {
     return one.who();
   }
 
+  @PostConstruct
+  public void postInit() {
+
+  }
+
 }
diff --git a/inject-test/src/test/java/org/example/coffee/fruit/AppleService.java b/inject-test/src/test/java/org/example/coffee/fruit/AppleService.java
index 9a48fc81..7bf8f659 100644
--- a/inject-test/src/test/java/org/example/coffee/fruit/AppleService.java
+++ b/inject-test/src/test/java/org/example/coffee/fruit/AppleService.java
@@ -3,6 +3,8 @@ package org.example.coffee.fruit;
 import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
 
+import java.io.IOException;
+
 @Singleton
 public class AppleService {
 
@@ -11,8 +13,13 @@ public class AppleService {
 
   PeachService peachService;
 
+  public AppleService() throws IOException {
+    // maybe I throw on construction
+  }
+
   @Inject
-  void setPeachService(PeachService peachService) {
+  void setPeachService(PeachService peachService) throws IOException {
+    // maybe I throw on method injection
     this.peachService = peachService;
   }