Repository: avaje/avaje-inject (java)

Issue:
Map Beans not detected
suppose I have a factory that provides

```java
  @Bean
  Map<String, Model> map() {

    return Map.of("hi",  new Model, ....);
  }
```

When I rely on this bean somewhere else.

```java
  @Bean
  SomeOtherBean otherBean( Map<String, Model> s) {
....
  }
```
I get this exception.


[ERROR] Dependencies [Model] are not provided - missing @Singleton, @Component, @Factory/@Bean or specify external dependency via @InjectModule requires attribute

One would expect that the map from the factory would be detected.

Pull request:
Support Wiring Regular Maps
Well, I didn't know that map wiring was originally only for getting qualified beans. Seems like a pretty uncommon use case compared to wiring regular maps.

- adds @QualifiedMap annotation to make generated DI classes have the old behavior
- modifies util methods to use regular `builder.get` unless the Map has the annotation.,

fixes #298 


Patch:
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 96f7e75c..0eae491b 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
@@ -14,14 +14,16 @@ final class FieldReader {
   private final GenericType type;
   private boolean requestParam;
   private String requestParamName;
+  private final boolean isBeanMap;
 
   FieldReader(Element element) {
     this.element = element;
     this.name = Util.getNamed(element);
     this.nullable = Util.isNullable(element);
     this.utype = Util.determineType(element.asType());
-    this.fieldType = Util.unwrapProvider(utype.rawType());
-    this.type = GenericType.parse(utype.rawType());
+    this.isBeanMap = QualifiedMapPrism.isPresent(element);
+    this.fieldType = Util.unwrapProvider(utype.rawType(isBeanMap));
+    this.type = GenericType.parse(utype.rawType(isBeanMap));
   }
 
   boolean isGenericParam() {
@@ -44,7 +46,7 @@ final class FieldReader {
 
   String builderGetDependency(String builder) {
     StringBuilder sb = new StringBuilder();
-    sb.append(builder).append(".").append(utype.getMethod(nullable));
+    sb.append(builder).append(".").append(utype.getMethod(nullable, isBeanMap));
     if (isGenericParam()) {
       sb.append("TYPE_").append(type.shortName());
     } else {
@@ -65,9 +67,9 @@ final class FieldReader {
    * Check for request scoped dependency.
    */
   void checkRequest(BeanRequestParams requestParams) {
-    requestParam = requestParams.check(utype.rawType());
+    requestParam = requestParams.check(utype.rawType(isBeanMap));
     if (requestParam) {
-      requestParamName = requestParams.argumentName(utype.rawType());
+      requestParamName = requestParams.argumentName(utype.rawType(isBeanMap));
     }
   }
 
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 eb02dfca..94e85ea7 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
@@ -342,13 +342,15 @@ final class MethodReader {
     private final String simpleName;
     private boolean requestParam;
     private String requestParamName;
+    private final boolean isBeanMap;
 
     MethodParam(VariableElement param) {
       this.simpleName = param.getSimpleName().toString();
       this.named = Util.getNamed(param);
       this.nullable = Util.isNullable(param);
       this.utilType = Util.determineType(param.asType());
-      this.paramType = utilType.rawType();
+      this.isBeanMap = QualifiedMapPrism.isPresent(param);
+      this.paramType = utilType.rawType(isBeanMap);
       this.genericType = GenericType.parse(paramType);
       this.fullGenericType = GenericType.parse(utilType.full());
     }
@@ -365,7 +367,7 @@ final class MethodReader {
     }
 
     void builderGetDependency(Append writer, String builderName, boolean forFactory) {
-      writer.append(builderName).append(".").append(utilType.getMethod(nullable));
+      writer.append(builderName).append(".").append(utilType.getMethod(nullable, isBeanMap));
       if (!genericType.isGenericType()) {
         writer.append(Util.shortName(genericType.topType())).append(".class");
       } else if (isProvider()) {
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/UtilType.java b/inject-generator/src/main/java/io/avaje/inject/generator/UtilType.java
index bf0c749a..2f460634 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/UtilType.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/UtilType.java
@@ -50,14 +50,17 @@ final class UtilType {
     return rawType;
   }
 
-  String rawType() {
+  String rawType(boolean beanMap) {
     switch (type) {
       case SET:
         return Util.extractSet(rawType);
       case LIST:
         return Util.extractList(rawType);
       case MAP:
-        return Util.extractMap(rawType);
+        if (beanMap) {
+          return Util.extractMap(rawType);
+        }
+        return rawType;
       case OPTIONAL:
         return Util.extractOptionalType(rawType);
       default:
@@ -65,14 +68,17 @@ final class UtilType {
     }
   }
 
-  String getMethod(boolean nullable) {
+  String getMethod(boolean nullable, boolean beanMap) {
     switch (type) {
       case SET:
         return "set(";
       case LIST:
         return "list(";
       case MAP:
-        return "map(";
+        if (beanMap) {
+          return "map(";
+        }
+        break;
       case OPTIONAL:
         return "getOptional(";
       case PROVIDER:
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/package-info.java b/inject-generator/src/main/java/io/avaje/inject/generator/package-info.java
index 9e6cc2cc..c3b575ff 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/package-info.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/package-info.java
@@ -14,6 +14,7 @@
 @GeneratePrism(Proxy.class)
 @GeneratePrism(DependencyMeta.class)
 @GeneratePrism(Bean.class)
+@GeneratePrism(QualifiedMap.class)
 @GeneratePrism(Generated.class)
 package io.avaje.inject.generator;
 
@@ -23,6 +24,7 @@ import io.avaje.inject.Factory;
 import io.avaje.inject.InjectModule;
 import io.avaje.inject.Primary;
 import io.avaje.inject.Prototype;
+import io.avaje.inject.QualifiedMap;
 import io.avaje.inject.Secondary;
 import io.avaje.inject.aop.Aspect;
 import io.avaje.inject.spi.DependencyMeta;
diff --git a/inject/src/main/java/io/avaje/inject/QualifiedMap.java b/inject/src/main/java/io/avaje/inject/QualifiedMap.java
new file mode 100644
index 00000000..43548b51
--- /dev/null
+++ b/inject/src/main/java/io/avaje/inject/QualifiedMap.java
@@ -0,0 +1,29 @@
+package io.avaje.inject;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Marks an <code>Map&lt;String, T&gt; </code> field/parameter to receive a map of beans keyed
+ * by qualifier name.
+ *
+ * <pre>{@code
+ * class CrewMate {
+ *
+ *   private final Map<String, Tasks> taskMap;
+ *
+ *   @Inject
+ *   CrewMate(@QualifiedMap Map&lt;String, Tasks&gt taskMap) {
+ *     this.taskMap = taskMap;
+ *   }
+ *
+ * }
+ * }</pre>
+ */
+@Target({PARAMETER, FIELD})
+@Retention(RUNTIME)
+public @interface QualifiedMap {}


Test patch:
diff --git a/inject-test/src/test/java/org/example/coffee/list/CombinedMapSomei.java b/inject-test/src/test/java/org/example/coffee/list/CombinedMapSomei.java
index 534ecfa5..365434bf 100644
--- a/inject-test/src/test/java/org/example/coffee/list/CombinedMapSomei.java
+++ b/inject-test/src/test/java/org/example/coffee/list/CombinedMapSomei.java
@@ -8,6 +8,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import io.avaje.inject.QualifiedMap;
+
 @Singleton
 public class CombinedMapSomei {
 
@@ -17,7 +19,7 @@ public class CombinedMapSomei {
    * Inject map of beans keyed by qualifier name.
    */
   @Inject
-  public CombinedMapSomei(Map<String, Somei> somes) {
+  public CombinedMapSomei(@QualifiedMap Map<String, Somei> somes) {
     this.somes = somes;
   }