Repository: avaje/avaje-inject
Pull request #236 (2022-06-11 05:11:49)

#234 - Support factory with qualifiers


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 ffc55d0f..914871bd 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
@@ -97,7 +97,11 @@ class BeanReader {
     List<Dependency> list = new ArrayList<>();
     if (constructor != null) {
       for (MethodReader.MethodParam param : constructor.getParams()) {
-        list.add(param.getDependsOn());
+        Dependency dependsOn = param.getDependsOn();
+        // BeanScope is always injectable with no impact on injection ordering
+        if (!dependsOn.dependsOn().equals(Constants.BEANSCOPE)) {
+          list.add(dependsOn);
+        }
       }
     }
     return list;
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 f4db6d2b..75086ff9 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
@@ -1,9 +1,11 @@
 package io.avaje.inject.generator;
 
 import io.avaje.inject.Bean;
-import jakarta.inject.Named;
 
-import javax.lang.model.element.*;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
 import javax.lang.model.type.TypeMirror;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -35,7 +37,7 @@ class MethodReader {
     this(context, element, beanType, null, null, false);
   }
 
-  MethodReader(ProcessingContext context, ExecutableElement element, TypeElement beanType, Bean bean, Named named, boolean prototype) {
+  MethodReader(ProcessingContext context, ExecutableElement element, TypeElement beanType, Bean bean, String qualifierName, boolean prototype) {
     this.isFactory = bean != null;
     this.prototype = prototype;
     this.element = element;
@@ -57,7 +59,7 @@ class MethodReader {
     this.isVoid = Util.isVoid(topType);
     String initMethod = (bean == null) ? null : bean.initMethod();
     String destroyMethod = (bean == null) ? null : bean.destroyMethod();
-    this.name = (named == null) ? null : named.value().toLowerCase();
+    this.name = qualifierName;
     TypeElement returnElement = (TypeElement)context.asElement(returnMirror);
     if (returnElement == null) {
       this.typeReader = null;
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java
index 4408a05b..71967890 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java
@@ -116,7 +116,7 @@ class ScopeInfo {
 
   private String initName(String topPackage) {
     if (name == null || name.isEmpty()) {
-      name = ScopeUtil.name(topPackage);
+      name = ScopeUtil.initName(topPackage);
     }
     return name;
   }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeUtil.java b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeUtil.java
index 7a7ff84f..53cd2c91 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeUtil.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeUtil.java
@@ -60,6 +60,11 @@ class ScopeUtil {
     return requiresList;
   }
 
+  static String initName(String name) {
+    name = name(name);
+    return name.equals("Inject") ? "DInject" : name;
+  }
+
   static String name(String name) {
     if (name == null) {
       return null;
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsInjection.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsInjection.java
index fa2d20ed..2f3c09a8 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsInjection.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsInjection.java
@@ -3,7 +3,6 @@ package io.avaje.inject.generator;
 import io.avaje.inject.Bean;
 import io.avaje.inject.Prototype;
 import jakarta.inject.Inject;
-import jakarta.inject.Named;
 
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ExecutableElement;
@@ -146,10 +145,9 @@ class TypeExtendsInjection {
 
 
   private void addFactoryMethod(ExecutableElement methodElement, Bean bean) {
-    // Not yet reading Qualifier annotations, Named only at this stage
-    Named named = methodElement.getAnnotation(Named.class);
+    String qualifierName = Util.getNamed(methodElement);
     boolean prototype = methodElement.getAnnotation(Prototype.class) != null;
-    factoryMethods.add(new MethodReader(context, methodElement, baseType, bean, named, prototype).read());
+    factoryMethods.add(new MethodReader(context, methodElement, baseType, bean, qualifierName, prototype).read());
   }
 
   BeanAspects hasAspects() {
diff --git a/inject/src/main/java/io/avaje/inject/spi/DBeanScopeProxy.java b/inject/src/main/java/io/avaje/inject/spi/DBeanScopeProxy.java
new file mode 100644
index 00000000..55dd110f
--- /dev/null
+++ b/inject/src/main/java/io/avaje/inject/spi/DBeanScopeProxy.java
@@ -0,0 +1,102 @@
+package io.avaje.inject.spi;
+
+import io.avaje.inject.BeanEntry;
+import io.avaje.inject.BeanScope;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Proxy used when injecting the BeanScope.
+ */
+class DBeanScopeProxy implements BeanScope {
+
+  private BeanScope delegate;
+
+  /**
+   * This happens just before the BeanScope is started.
+   *
+   * @param delegate The actual BeanScope
+   */
+  void inject(BeanScope delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public <T> T get(Class<T> type) {
+    return delegate.get(type);
+  }
+
+  @Override
+  public <T> T get(Class<T> type, String name) {
+    return delegate.get(type, name);
+  }
+
+  @Override
+  public <T> T get(Type type, String name) {
+    return delegate.get(type, name);
+  }
+
+  @Override
+  public <T> Optional<T> getOptional(Class<T> type) {
+    return delegate.getOptional(type);
+  }
+
+  @Override
+  public <T> Optional<T> getOptional(Type type, String name) {
+    return delegate.getOptional(type, name);
+  }
+
+  @Override
+  public List<Object> listByAnnotation(Class<?> annotation) {
+    return delegate.listByAnnotation(annotation);
+  }
+
+  @Override
+  public <T> List<T> list(Class<T> type) {
+    return delegate.list(type);
+  }
+
+  @Override
+  public <T> List<T> list(Type type) {
+    return delegate.list(type);
+  }
+
+  @Override
+  public <T> List<T> listByPriority(Class<T> type) {
+    return delegate.listByPriority(type);
+  }
+
+  @Override
+  public <T> List<T> listByPriority(Class<T> type, Class<? extends Annotation> priority) {
+    return delegate.listByPriority(type, priority);
+  }
+
+  @Override
+  public <T> Map<String, T> map(Type type) {
+    return delegate.map(type);
+  }
+
+  @Override
+  public List<BeanEntry> all() {
+    return delegate.all();
+  }
+
+  @Override
+  public boolean contains(Type type) {
+    return delegate.contains(type);
+  }
+
+  @Override
+  public boolean contains(String type) {
+    return delegate.contains(type);
+  }
+
+  @Override
+  public void close() {
+    delegate.close();
+  }
+}
diff --git a/inject/src/main/java/io/avaje/inject/spi/DBuilder.java b/inject/src/main/java/io/avaje/inject/spi/DBuilder.java
index d31f9f0f..a5b9541e 100644
--- a/inject/src/main/java/io/avaje/inject/spi/DBuilder.java
+++ b/inject/src/main/java/io/avaje/inject/spi/DBuilder.java
@@ -17,34 +17,29 @@ class DBuilder implements Builder {
    */
   private final List<Runnable> postConstruct = new ArrayList<>();
   private final List<AutoCloseable> preDestroy = new ArrayList<>();
-
   /**
    * List of field injection closures.
    */
   private final List<Consumer<Builder>> injectors = new ArrayList<>();
-
   /**
    * The beans created and added to the scope during building.
    */
   protected final DBeanMap beanMap = new DBeanMap();
-
   protected final BeanScope parent;
   protected final boolean parentOverride;
-
   /**
    * Bean provided by the parent scope that we are not overriding.
    */
   protected Object parentMatch;
-
   /**
    * Debug of the current bean being wired - used in injection errors.
    */
   private Type injectTarget;
-
   /**
    * Flag set when we are running post construct injection.
    */
   private boolean runningPostConstruct;
+  private DBeanScopeProxy beanScopeProxy;
 
   DBuilder(BeanScope parent, boolean parentOverride) {
     this.parent = parent;
@@ -253,6 +248,9 @@ class DBuilder implements Builder {
 
   @Override
   public <T> T get(Type cls, String name) {
+    if (BeanScope.class.equals(cls)) {
+      return injectBeanScope();
+    }
     T bean = getMaybe(cls, name);
     if (bean == null) {
       throw new IllegalStateException(errorInjectingNull(cls, name));
@@ -260,6 +258,14 @@ class DBuilder implements Builder {
     return bean;
   }
 
+  @SuppressWarnings("unchecked")
+  private <T> T injectBeanScope() {
+    if (beanScopeProxy == null) {
+      beanScopeProxy = new DBeanScopeProxy();
+    }
+    return (T) beanScopeProxy;
+  }
+
   private <T> String errorInjectingNull(Type cls, String name) {
     String msg = "Injecting null for " + cls.getTypeName();
     if (name != null) {
@@ -284,6 +290,10 @@ class DBuilder implements Builder {
 
   public BeanScope build(boolean withShutdownHook) {
     runInjectors();
-    return new DBeanScope(withShutdownHook, preDestroy, postConstruct, beanMap, parent).start();
+    var scope = new DBeanScope(withShutdownHook, preDestroy, postConstruct, beanMap, parent);
+    if (beanScopeProxy != null) {
+      beanScopeProxy.inject(scope);
+    }
+    return scope.start();
   }
 }


Test patch:
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/ScopeUtilTest.java b/inject-generator/src/test/java/io/avaje/inject/generator/ScopeUtilTest.java
index 7d933108..cf7e8fec 100644
--- a/inject-generator/src/test/java/io/avaje/inject/generator/ScopeUtilTest.java
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/ScopeUtilTest.java
@@ -14,6 +14,13 @@ class ScopeUtilTest {
     assertEquals("Example", ScopeUtil.name("Example"));
   }
 
+  @Test
+  void initName_inject() {
+    // resulting module can't be InjectModule as that clashes with @InjectModule
+    assertEquals("DInject", ScopeUtil.initName("org.example.inject"));
+    assertEquals("Foo", ScopeUtil.initName("org.example.foo"));
+  }
+
   @Test
   void name_withSpace() {
     assertEquals("ExAmple", ScopeUtil.name("org.ex ample"));
diff --git a/inject-test/src/test/java/org/example/coffee/INeedBeanScope.java b/inject-test/src/test/java/org/example/coffee/INeedBeanScope.java
new file mode 100644
index 00000000..c8b979b6
--- /dev/null
+++ b/inject-test/src/test/java/org/example/coffee/INeedBeanScope.java
@@ -0,0 +1,22 @@
+package org.example.coffee;
+
+import io.avaje.inject.BeanScope;
+import io.avaje.inject.Component;
+import org.example.optional.Que;
+
+/**
+ * Depends on the BeanScope.
+ */
+@Component
+class INeedBeanScope {
+
+  final BeanScope beanScope;
+
+  INeedBeanScope(BeanScope beanScope) {
+    this.beanScope = beanScope;
+  }
+
+  Que getQueue() {
+    return beanScope.get(Que.class, "frodo");
+  }
+}
diff --git a/inject-test/src/test/java/org/example/coffee/InjectBeanScopeTest.java b/inject-test/src/test/java/org/example/coffee/InjectBeanScopeTest.java
new file mode 100644
index 00000000..4becff34
--- /dev/null
+++ b/inject-test/src/test/java/org/example/coffee/InjectBeanScopeTest.java
@@ -0,0 +1,24 @@
+package org.example.coffee;
+
+import io.avaje.inject.BeanScope;
+import org.example.optional.Que;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test that we can inject BeanScope.
+ */
+class InjectBeanScopeTest {
+
+  @Test
+  void test() {
+    try (BeanScope beanScope = BeanScope.builder().build()) {
+      INeedBeanScope need = beanScope.get(INeedBeanScope.class);
+      Que queue = need.getQueue();
+
+      String result = queue.push("hi");
+      assertThat(result).isEqualTo("hi|frodo");
+    }
+  }
+}
diff --git a/inject-test/src/test/java/org/example/optional/Frodo.java b/inject-test/src/test/java/org/example/optional/Frodo.java
new file mode 100644
index 00000000..8610aa35
--- /dev/null
+++ b/inject-test/src/test/java/org/example/optional/Frodo.java
@@ -0,0 +1,11 @@
+package org.example.optional;
+
+import jakarta.inject.Qualifier;
+import java.lang.annotation.Retention;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Qualifier
+@Retention(RUNTIME)
+public @interface Frodo {
+}
diff --git a/inject-test/src/test/java/org/example/optional/OptFactory.java b/inject-test/src/test/java/org/example/optional/OptFactory.java
index a3bb7aa9..3e278363 100644
--- a/inject-test/src/test/java/org/example/optional/OptFactory.java
+++ b/inject-test/src/test/java/org/example/optional/OptFactory.java
@@ -9,7 +9,7 @@ import java.util.Optional;
 @Factory
 class OptFactory {
 
-  @Named("frodo")
+  @Frodo
   @Bean
   Que frodoQueue() {
     return new BasicQue("frodo");