Repository: avaje/avaje-inject
Pull request #837 (2025-06-21 02:51:18)

Support Generic Assist Factory Interfaces
Now can do wacky stuff like sharing factory interfaces for different concrete types


Patch:
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java
index a6a389f6..10025a67 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java
@@ -85,6 +85,11 @@ final class AssistBeanReader {
         APContext.logError(type, "@AssistFactory targets must have only one abstract method");
       }
     }
+
+    if (t.getTypeParameters().size() > 1) {
+      APContext.logError(type, "@AssistFactory targets can't have more than one type parameter");
+    }
+
     var sb = new StringBuilder(String.format("@AssistFactory targets for type %s must have an abstract method with form '%s <methodName>(", shortName(), shortName()));
     var assistNames = new ArrayList<String>();
     for (var iterator = assistedElements.iterator(); iterator.hasNext(); ) {
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleAssistWriter.java b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleAssistWriter.java
index 0ad57f88..caa74126 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleAssistWriter.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleAssistWriter.java
@@ -96,7 +96,7 @@ final class SimpleAssistWriter {
       valhallaStr = "";
     }
 
-    writer.append("final %sclass ", valhallaStr).append(name).append(suffix);
+    writer.append("final %sclass %s%s", valhallaStr, name, suffix);
     writeImplementsOrExtends();
     writer.append(" {").eol().eol();
   }
@@ -113,6 +113,9 @@ final class SimpleAssistWriter {
     writer
       .append(targetInterface.getKind() == ElementKind.INTERFACE ? " implements " : " extends ")
       .append(Util.shortName(targetInterface.getQualifiedName().toString()));
+    if (!targetInterface.getTypeParameters().isEmpty()) {
+      writer.append("<%s>", shortName);
+    }
   }
 
   private void writeInjectFields() {


Test patch:
diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/assist/generic/LordFactory.java b/blackbox-test-inject/src/main/java/org/example/myapp/assist/generic/LordFactory.java
new file mode 100644
index 00000000..56488a5d
--- /dev/null
+++ b/blackbox-test-inject/src/main/java/org/example/myapp/assist/generic/LordFactory.java
@@ -0,0 +1,5 @@
+package org.example.myapp.assist.generic;
+
+public interface LordFactory<N extends NightLord> {
+  N create(String title);
+}
diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/assist/generic/NightLord.java b/blackbox-test-inject/src/main/java/org/example/myapp/assist/generic/NightLord.java
new file mode 100644
index 00000000..6cb295f6
--- /dev/null
+++ b/blackbox-test-inject/src/main/java/org/example/myapp/assist/generic/NightLord.java
@@ -0,0 +1,19 @@
+package org.example.myapp.assist.generic;
+
+import io.avaje.inject.AssistFactory;
+import io.avaje.inject.Assisted;
+
+public interface NightLord {
+
+  @AssistFactory(LordFactory.class)
+  public static class Caligo implements NightLord {
+
+    @Assisted String title;
+  }
+
+  @AssistFactory(LordFactory.class)
+  public static class Libra implements NightLord {
+
+    @Assisted String title;
+  }
+}
diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/assist/generic/NightReign.java b/blackbox-test-inject/src/main/java/org/example/myapp/assist/generic/NightReign.java
new file mode 100644
index 00000000..9752d5b1
--- /dev/null
+++ b/blackbox-test-inject/src/main/java/org/example/myapp/assist/generic/NightReign.java
@@ -0,0 +1,26 @@
+package org.example.myapp.assist.generic;
+
+import org.example.myapp.assist.generic.NightLord.Caligo;
+import org.example.myapp.assist.generic.NightLord.Libra;
+
+import jakarta.inject.Singleton;
+
+@Singleton
+public class NightReign {
+
+  private LordFactory<Caligo> caligoFactory;
+  private LordFactory<Libra> libraFactory;
+
+  public NightReign(LordFactory<Caligo> caligoFactory, LordFactory<Libra> libraFactory) {
+    this.caligoFactory = caligoFactory;
+    this.libraFactory = libraFactory;
+  }
+
+  public Caligo caligo() {
+    return caligoFactory.create("Miasma of Night");
+  }
+
+  public Libra libra() {
+    return libraFactory.create("Creature of Night");
+  }
+}
diff --git a/blackbox-test-inject/src/test/java/org/example/myapp/assist/generic/AssistGenericTest.java b/blackbox-test-inject/src/test/java/org/example/myapp/assist/generic/AssistGenericTest.java
new file mode 100644
index 00000000..b2e475a3
--- /dev/null
+++ b/blackbox-test-inject/src/test/java/org/example/myapp/assist/generic/AssistGenericTest.java
@@ -0,0 +1,22 @@
+package org.example.myapp.assist.generic;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.example.myapp.assist.generic.NightLord.Caligo;
+import org.example.myapp.assist.generic.NightLord.Libra;
+import org.junit.jupiter.api.Test;
+
+import io.avaje.inject.test.InjectTest;
+import jakarta.inject.Inject;
+
+@InjectTest
+class AssistGenericTest {
+
+  @Inject NightReign nightReign;
+
+  @Test
+  void test() {
+    assertThat(nightReign.caligo()).isInstanceOf(Caligo.class);
+    assertThat(nightReign.libra()).isInstanceOf(Libra.class);
+  }
+}
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/assist/generic/Game.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/assist/generic/Game.java
new file mode 100644
index 00000000..992f9420
--- /dev/null
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/assist/generic/Game.java
@@ -0,0 +1,17 @@
+package io.avaje.inject.generator.models.valid.assist.generic;
+
+import io.avaje.inject.generator.models.valid.assist.generic.NightLord.Caligo;
+import io.avaje.inject.generator.models.valid.assist.generic.NightLord.Heolstor;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class Game {
+  private LordFactory<Caligo> miasma;
+  private LordFactory<Heolstor> lord;
+
+  public Game(LordFactory<Caligo> miasma, LordFactory<Heolstor> lord) {
+    super();
+    this.miasma = miasma;
+    this.lord = lord;
+  }
+}
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/assist/generic/LordFactory.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/assist/generic/LordFactory.java
new file mode 100644
index 00000000..0edb03ee
--- /dev/null
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/assist/generic/LordFactory.java
@@ -0,0 +1,5 @@
+package io.avaje.inject.generator.models.valid.assist.generic;
+
+public interface LordFactory<N extends NightLord> {
+  N create(String title);
+}
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/assist/generic/NightLord.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/assist/generic/NightLord.java
new file mode 100644
index 00000000..c6191827
--- /dev/null
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/assist/generic/NightLord.java
@@ -0,0 +1,19 @@
+package io.avaje.inject.generator.models.valid.assist.generic;
+
+import io.avaje.inject.AssistFactory;
+import io.avaje.inject.Assisted;
+
+public interface NightLord {
+
+  @AssistFactory(LordFactory.class)
+  public static class Caligo implements NightLord {
+
+    @Assisted String title;
+  }
+
+  @AssistFactory(LordFactory.class)
+  public static class Heolstor implements NightLord {
+
+    @Assisted String title;
+  }
+}