Repository: avaje/avaje-inject
Pull request #387 (2023-07-27 15:12:12)

Use Short Name for Nested Types
Now generated `$DI` classes will use the more readable short name instead of the painfully long fully qualified names.

- Nested type instances will generate with enclosing type (`A.B value` instead of `some.package.A.B value`)
- Generated `_TYPE` variables will attempt to use short names if possible, but will revert to FQN when there are conflicts.


Patch:
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 55449d81..9a591bb9 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
@@ -276,7 +276,7 @@ final class BeanReader {
       if (!genericTypes.isEmpty()) {
         importTypes.add(Constants.TYPE);
         importTypes.add(Constants.GENERICTYPE);
-        // TYPE_ generic types are fully qualified
+        genericTypes.forEach(t->t.addImports(importTypes));
       }
     }
     checkImports();
@@ -371,7 +371,7 @@ final class BeanReader {
     for (FieldReader field : injectFields) {
       field.writeRequestInject(writer);
     }
-    for (MethodReader method : injectMethods) {
+    for (final MethodReader method : injectMethods) {
       writer.append("    bean.%s(", method.name());
       method.writeRequestConstructor(writer);
       writer.append(");").eol();
@@ -411,11 +411,7 @@ final class BeanReader {
   }
 
   String shortName() {
-    if (beanType.getNestingKind().isNested()) {
-      return Util.nestedShortName(beanQualifiedName());
-    } else {
-      return Util.shortName(beanQualifiedName());
-    }
+    return Util.shortName(beanQualifiedName());
   }
 
   String packageName() {
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java
index e9986e89..4057c9bd 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java
@@ -47,10 +47,10 @@ final class FieldReader {
   }
 
   String builderGetDependency(String builder) {
-    StringBuilder sb = new StringBuilder();
+    final var sb = new StringBuilder();
     sb.append(builder).append(".").append(utype.getMethod(nullable, isBeanMap));
     if (isGenericParam()) {
-      sb.append("TYPE_").append(type.shortName());
+      sb.append("TYPE_").append(type.shortName().replace(".", "_"));
     } else {
       sb.append(Util.shortName(fieldType)).append(".class");
     }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/ImportTypeMap.java b/inject-generator/src/main/java/io/avaje/inject/generator/ImportTypeMap.java
index a0936ebe..1f1d6136 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/ImportTypeMap.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ImportTypeMap.java
@@ -1,5 +1,7 @@
 package io.avaje.inject.generator;
 
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -26,16 +28,22 @@ final class ImportTypeMap {
     return mapByShortName.containsKey(suffix);
   }
 
-  /**
-   * Register the full type checking for unique short name and returning the short name to use.
-   */
+  /** Register the full type checking for unique short name and returning the short name to use. */
   String add(String fullType) {
-    String shortName = Util.shortName(fullType);
-    String existingFull = mapByShortName.get(shortName);
+    final String shortName = Util.shortName(fullType);
+    String fullTypeActual;
+    final var index = shortName.lastIndexOf('.');
+    if (index != -1) {
+      fullTypeActual = fullType.replace(shortName, shortName.substring(0, index));
+
+    } else {
+      fullTypeActual = fullType;
+    }
+    final String existingFull = mapByShortName.get(shortName);
     if (existingFull == null) {
-      mapByShortName.put(shortName, fullType);
+      mapByShortName.put(shortName, fullTypeActual);
       return shortName;
-    } else if (existingFull.equals(fullType)) {
+    } else if (existingFull.equals(fullTypeActual)) {
       // already existing
       return shortName;
     } else {
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 c440692b..0be7a207 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
@@ -14,8 +14,6 @@ final class MethodReader {
 
   private static final String CODE_COMMENT_BUILD_FACTORYBEAN = "  /**\n   * Create and register %s via factory bean method %s#%s().\n   */";
 
-  private static final Set<String> UNSAFE_SHORT_NAME = Set.of("Builder", "Generated");
-
   private final ExecutableElement element;
   private final String factoryType;
   private final String methodName;
@@ -398,11 +396,11 @@ final class MethodReader {
     void builderGetDependency(Append writer, String builderName, boolean forFactory) {
       writer.append(builderName).append(".").append(utilType.getMethod(nullable, isBeanMap));
       if (!genericType.isGenericType()) {
-        writer.append(safeShortName(genericType.topType())).append(".class");
+        writer.append(Util.shortName(genericType.topType())).append(".class");
       } else if (isProvider()) {
         writer.append(providerParam()).append(".class");
       } else {
-        writer.append("TYPE_").append(genericType.shortName());
+        writer.append("TYPE_").append(genericType.shortName().replace(".", "_"));
       }
       if (named != null && !named.isEmpty()) {
         writer.append(",\"").append(named).append("\"");
@@ -420,14 +418,6 @@ final class MethodReader {
       writer.append(")");
     }
 
-    private String safeShortName(String fullType) {
-      final String shortName = Util.shortName(fullType);
-      if (UNSAFE_SHORT_NAME.contains(shortName)) {
-        return fullType;
-      }
-      return shortName;
-    }
-
     private String providerParam() {
       return Util.shortName(Util.unwrapProvider(paramType));
     }
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 434db90b..a42f206d 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
@@ -5,7 +5,10 @@ import static io.avaje.inject.generator.ProcessingContext.logError;
 
 import java.io.IOException;
 import java.io.Writer;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 import javax.lang.model.element.TypeElement;
@@ -67,16 +70,52 @@ final class SimpleBeanWriter {
     // collect all types to prevent duplicates
     Set<GenericType> genericTypes = beanReader.allGenericTypes();
     if (!genericTypes.isEmpty()) {
-      for (GenericType type : genericTypes) {
-        writer.append("  public static final Type TYPE_%s = new GenericType<", type.shortName());
+
+    	final Map<String, String> seenShortNames= new HashMap<>();
+
+      for (final GenericType type : genericTypes) {
+        writer
+            .append("  public static final Type TYPE_%s =", type.shortName().replace(".", "_"))
+            .eol()
+            .append("      new GenericType<");
+
+        writeGenericType(type,seenShortNames, writer);
         // use fully qualified types here rather than use type.writeShort(writer)
-        writer.append(type.toString());
+
         writer.append(">(){}.type();").eol();
       }
       writer.eol();
     }
   }
 
+  private void writeGenericType(GenericType type, Map<String, String> seenShortNames, Append writer) {
+    final var typeShortName = Util.shortName(type.topType());
+    final var topType = seenShortNames.computeIfAbsent(typeShortName, k -> type.topType());
+    if (type.isGenericType()) {
+      final var shortName =
+          Objects.equals(type.topType(), topType) ? typeShortName : type.topType();
+
+      writer.append(shortName);
+      writer.append("<");
+      boolean first = true;
+      for (final var param : type.params()) {
+        if (first) {
+          first = false;
+          writeGenericType(param, seenShortNames, writer);
+          continue;
+        }
+        writer.append(", ");
+        writeGenericType(param, seenShortNames, writer);
+      }
+      writer.append(">");
+    } else {
+      final var shortName =
+          Objects.equals(type.topType(), topType) ? typeShortName : type.topType();
+
+      writer.append(shortName);
+    }
+  }
+
   private void writeRequestCreate() {
     beanReader.writeRequestCreate(writer);
   }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeAppender.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeAppender.java
index e894dba8..4738fecd 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeAppender.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeAppender.java
@@ -43,7 +43,7 @@ final class TypeAppender {
 
   private void addGenericType(GenericType genericType) {
     genericTypes.add(genericType);
-    types.add("TYPE_" + genericType.shortName());
+    types.add("TYPE_" + genericType.shortName().replace(".", "_"));
   }
 
   Set<GenericType> genericTypes() {
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeReader.java
index 9b545c67..1ac2de95 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeReader.java
@@ -116,7 +116,7 @@ final class TypeReader {
     if (!genericTypes.isEmpty()) {
       importTypes.add(Constants.TYPE);
       importTypes.add(Constants.GENERICTYPE);
-      // TYPE_ generic types are fully qualified
+      genericTypes.forEach(t->t.addImports(importTypes));
     }
   }
 }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/Util.java b/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
index c6d15952..1e5d4f28 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
@@ -129,22 +129,22 @@ final class Util {
     return sb.toString();
   }
 
-  static String nestedShortName(String fullType) {
-    int pos = fullType.lastIndexOf('.');
-    if (pos < 0) {
-      return fullType;
-    } else {
-      pos = fullType.lastIndexOf('.', pos - 1);
-      return pos < 0 ? fullType : fullType.substring(pos + 1);
-    }
-  }
-
   static String shortName(String fullType) {
     final int p = fullType.lastIndexOf('.');
     if (p == -1) {
       return fullType;
-    } else {
+    } else if (fullType.startsWith("java")) {
       return fullType.substring(p + 1);
+    } else {
+      var result = "";
+      var foundClass = false;
+      for (final String part : fullType.split("\\.")) {
+        if (foundClass || Character.isUpperCase(part.charAt(0))) {
+          foundClass = true;
+          result += (result.isEmpty() ? "" : ".") + part;
+        }
+      }
+      return result;
     }
   }
 


Test patch:
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java b/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java
index c8aac96d..d942782d 100644
--- a/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java
@@ -12,8 +12,7 @@ class UtilTest {
 
   @Test
   void nestedShortName() {
-    assertEquals(Util.nestedShortName("com.example.Foo.Bar"), "Foo.Bar");
-    assertEquals(Util.nestedShortName("com.example.foo.Bar"), "foo.Bar");
+    assertEquals(Util.shortName("com.example.Foo.Bar"), "Foo.Bar");
   }
 
   @Test
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/A0.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/A0.java
new file mode 100644
index 00000000..df621ed4
--- /dev/null
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/A0.java
@@ -0,0 +1,6 @@
+package io.avaje.inject.generator.models.valid;
+
+public interface A0 {
+  /** Builder name clash in generated code, use the full type for this in generated code */
+  interface Builder {}
+}
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/generic/MyConsumer.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/generic/MyConsumer.java
new file mode 100644
index 00000000..d9131be8
--- /dev/null
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/generic/MyConsumer.java
@@ -0,0 +1,12 @@
+package io.avaje.inject.generator.models.valid.generic;
+
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+import io.avaje.inject.generator.models.valid.A0;
+
+public class MyConsumer implements BiConsumer<A0, io.avaje.inject.generator.models.valid.nested.A0> {
+
+  @Override
+  public void accept(A0 t, io.avaje.inject.generator.models.valid.nested.A0 u) {}
+}
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/generic/MyConsumerFactory.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/generic/MyConsumerFactory.java
new file mode 100644
index 00000000..bb2e80b1
--- /dev/null
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/generic/MyConsumerFactory.java
@@ -0,0 +1,13 @@
+package io.avaje.inject.generator.models.valid.generic;
+
+import io.avaje.inject.Bean;
+import io.avaje.inject.Factory;
+
+@Factory
+public class MyConsumerFactory {
+
+  @Bean
+  MyConsumer createTest() {
+    return new MyConsumer();
+  }
+}
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/nested/A0.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/nested/A0.java
new file mode 100644
index 00000000..3697ddd8
--- /dev/null
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/nested/A0.java
@@ -0,0 +1,9 @@
+package io.avaje.inject.generator.models.valid.nested;
+
+public interface A0 {
+
+  /** Builder name clash in generated code, use the full type for this in generated code */
+  interface Builder {
+
+  }
+}
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/nested/A1.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/nested/A1.java
new file mode 100644
index 00000000..f5e6e20b
--- /dev/null
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/nested/A1.java
@@ -0,0 +1,13 @@
+package io.avaje.inject.generator.models.valid.nested;
+
+public interface A1 {
+
+  /** Builder name clash in generated code, use the full type for this in generated code */
+  interface Builder {
+
+  }
+
+  interface DSS {
+
+  }
+}
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/nested/AFactory.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/nested/AFactory.java
new file mode 100644
index 00000000..d90affcc
--- /dev/null
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/nested/AFactory.java
@@ -0,0 +1,33 @@
+package io.avaje.inject.generator.models.valid.nested;
+
+import io.avaje.inject.Bean;
+import io.avaje.inject.Factory;
+
+@Factory
+class AFactory {
+
+  @Bean
+  A0.Builder build0() {
+    return new I0();
+  }
+
+  @Bean
+  A1.Builder build1() {
+    return new I1();
+  }
+
+  @Bean
+  void andUse(A1.Builder a1Build) {
+    a1Build.hashCode();
+  }
+
+  @Bean
+  void ad(A0.Builder b1, A1.Builder b2) {
+    b1.hashCode();
+    b2.hashCode();
+  }
+
+  static class I0 implements A0.Builder {}
+
+  static class I1 implements A1.Builder {}
+}