Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#230 - ENH: Add support for injecting into Map<String,T> (by qualifier) #231

Merged
merged 2 commits into from
Jun 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ static String extractSet(String rawType) {
return setType;
}

static String extractMap(String rawType) {
String valType = rawType.substring(31, rawType.length() - 1);
if (valType.startsWith("? extends")) {
return valType.substring(10);
}
return valType;
}

static UtilType determineType(TypeMirror rawType) {
return UtilType.of(rawType.toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class UtilType {
private enum Type {
LIST,
SET,
MAP,
OPTIONAL,
PROVIDER,
OTHER
Expand All @@ -23,6 +24,8 @@ static UtilType of(String rawType) {
return new UtilType(Type.LIST, rawType);
} else if (rawType.startsWith("java.util.Set<")) {
return new UtilType(Type.SET, rawType);
} else if (rawType.startsWith("java.util.Map<java.lang.String,")) {
return new UtilType(Type.MAP, rawType);
} else if (rawType.startsWith("java.util.Optional<")) {
return new UtilType(Type.OPTIONAL, rawType);
} else if (Util.isProvider(rawType)) {
Expand All @@ -49,6 +52,8 @@ String rawType() {
return Util.extractSet(rawType);
case LIST:
return Util.extractList(rawType);
case MAP:
return Util.extractMap(rawType);
case OPTIONAL:
return Util.extractOptionalType(rawType);
default:
Expand All @@ -62,6 +67,8 @@ String getMethod(boolean nullable) {
return "set(";
case LIST:
return "list(";
case MAP:
return "map(";
case OPTIONAL:
return "getOptional(";
case PROVIDER:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,26 @@ void unwrapProvider() {
assertEquals(Util.unwrapProvider("jakarta.inject.Provider<org.Foo<com.Bazz>>"), "org.Foo<com.Bazz>");
}

@Test
void extractMap() {
assertEquals("Foo", Util.extractMap("java.util.Map<java.lang.String,? extends Foo>"));
assertEquals("org.foo.Bar", Util.extractMap("java.util.Map<java.lang.String,? extends org.foo.Bar>"));
assertEquals("org.foo.Bar", Util.extractMap("java.util.Map<java.lang.String,org.foo.Bar>"));
}

@Test
void extractList() {
assertEquals("Foo", Util.extractList("List<? extends Foo>"));
assertEquals("org.foo.Bar", Util.extractList("List<? extends org.foo.Bar>"));
assertEquals("Foo", Util.extractList("java.util.List<? extends Foo>"));
assertEquals("org.foo.Bar", Util.extractList("java.util.List<? extends org.foo.Bar>"));
assertEquals("org.foo.Bar", Util.extractList("java.util.List<org.foo.Bar>"));

}

@Test
void extractSet() {
assertEquals("Foo", Util.extractSet("Set<? extends Foo>"));
assertEquals("org.foo.Bar", Util.extractSet("Set<? extends org.foo.Bar>"));
assertEquals("Foo", Util.extractSet("java.util.Set<? extends Foo>"));
assertEquals("org.foo.Bar", Util.extractSet("java.util.Set<? extends org.foo.Bar>"));
assertEquals("org.foo.Bar", Util.extractSet("java.util.Set<org.foo.Bar>"));
}

@Test
Expand Down
12 changes: 12 additions & 0 deletions inject-test/src/test/java/org/example/coffee/InjectListTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.example.coffee;

import io.avaje.inject.BeanScope;
import org.example.coffee.list.CombinedMapSomei;
import org.example.coffee.list.CombinedSetSomei;
import org.example.coffee.list.CombinedSomei;
import org.example.coffee.list.SomeInterfaceConsumer;
Expand Down Expand Up @@ -30,6 +31,17 @@ void test_set() {
}
}

@Test
void test_map() {
try (BeanScope context = BeanScope.builder().build()) {
CombinedMapSomei bean = context.get(CombinedMapSomei.class);
List<String> keys = bean.someKeys();
assertThat(keys).containsOnly("a", "b", "a2");
List<String> vals = bean.someVals();
assertThat(vals).containsOnly("a", "b", "a2");
}
}

@Test
void testEmptyList() {
try (BeanScope context = BeanScope.builder().build()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.example.coffee.list;

import jakarta.inject.Inject;
import jakarta.inject.Singleton;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Singleton
public class CombinedMapSomei {

private final Map<String, Somei> somes;

/**
* Inject map of beans keyed by qualifier name.
*/
@Inject
public CombinedMapSomei(Map<String, Somei> somes) {
this.somes = somes;
}

public List<String> someKeys() {
return new ArrayList<>(somes.keySet());
}

public List<String> someVals() {
return somes.values().stream()
.map(Somei::some)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
package org.example.coffee.qualifier;

import io.avaje.inject.BeanScope;
import org.example.autonamed.MyAutoB2;
import org.junit.jupiter.api.Test;

import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

public class StoreManagerWithNamedTest {
class StoreManagerWithNamedTest {

@Test
public void test() {

try (BeanScope context = BeanScope.builder().build()) {
StoreManagerWithNamed manager = context.get(StoreManagerWithNamed.class);
void test() {
try (BeanScope beanScope = BeanScope.builder().build()) {
StoreManagerWithNamed manager = beanScope.get(StoreManagerWithNamed.class);
String store = manager.store();
assertThat(store).isEqualTo("blue");

SomeStore greenStore = beanScope.get(SomeStore.class, "green");
SomeStore blueStore = beanScope.get(SomeStore.class, "blue");
Map<String, SomeStore> stores = beanScope.map(SomeStore.class);

SomeStore green = stores.get("green");
assertThat(green).isSameAs(greenStore);
SomeStore blue = stores.get("blue");
assertThat(blue).isSameAs(blueStore);

// a map with unnamed component
Map<String, MyAutoB2> mapWithUnnamed = beanScope.map(MyAutoB2.class);
assertThat(mapWithUnnamed).hasSize(1);
}
}
}
24 changes: 24 additions & 0 deletions inject-test/src/test/java/org/example/generic/MzCrudService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.example.generic;

import io.avaje.inject.Component;

import java.util.Optional;

@Component
public class MzCrudService implements CRUDService<MzObj, Integer> {

@Override
public String iamCrud() {
return "MzCrud";
}

@Override
public Integer create(MzObj bean) {
return 92;
}

@Override
public Optional<MzObj> get(Integer id) {
return Optional.of(new MzObj());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.example.generic;

import io.avaje.inject.BeanScope;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;

class MzCrudServiceTest {

@Test
void test() {
try (BeanScope beanScope = BeanScope.builder().build()) {
MzCrudService crud = beanScope.get(MzCrudService.class);
ReadService<MzObj,Integer> read = beanScope.get(MzCrudService$DI.TYPE_ReadServiceMzObjInteger, null);
CreateService<MzObj,Integer> create = beanScope.get(MzCrudService$DI.TYPE_CreateServiceMzObjInteger, null);

assertNotNull(crud);
assertThat(crud).isSameAs(read);
assertThat(crud).isSameAs(create);

assertThat(crud.iamCrud()).isEqualTo("MzCrud");
assertThat(create.create(new MzObj())).isEqualTo(92);
assertThat(read.get(42)).isNotEmpty();
}
}
}
4 changes: 4 additions & 0 deletions inject-test/src/test/java/org/example/generic/MzObj.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.example.generic;

public class MzObj {
}
24 changes: 16 additions & 8 deletions inject/src/main/java/io/avaje/inject/BeanScope.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
Expand Down Expand Up @@ -187,7 +188,7 @@ static BeanScopeBuilder newBuilder() {
List<Object> listByAnnotation(Class<?> annotation);

/**
* Return the list of beans that implement the interface.
* Return the list of beans for a given type.
*
* <pre>{@code
*
Expand All @@ -197,30 +198,37 @@ static BeanScopeBuilder newBuilder() {
*
* }</pre>
*
* @param interfaceType An interface class.
* @param type The type of beans to return.
*/
<T> List<T> list(Class<T> interfaceType);
<T> List<T> list(Class<T> type);

/**
* Return the list of beans that implement the given type.
*/
<T> List<T> list(Type interfaceType);
<T> List<T> list(Type type);

/**
* Return the list of beans that implement the interface sorting by priority.
*/
<T> List<T> listByPriority(Class<T> interfaceType);
<T> List<T> listByPriority(Class<T> type);

/**
* Return the beans that implement the interface sorting by the priority annotation used.
* <p>
* The priority annotation will typically be either <code>javax.annotation.Priority</code>
* or <code>jakarta.annotation.Priority</code>.
*
* @param interfaceType The interface type of the beans to return
* @param priority The priority annotation used to sort the beans
* @param type The interface type of the beans to return
* @param priority The priority annotation used to sort the beans
*/
<T> List<T> listByPriority(Class<T> interfaceType, Class<? extends Annotation> priority);
<T> List<T> listByPriority(Class<T> type, Class<? extends Annotation> priority);

/**
* Return the beans for this type mapped by their qualifier name.
* <p>
* Beans with no qualifier name get a generated unique key to use instead.
*/
<T> Map<String, T> map(Type type);

/**
* Return all the bean entries from the scope.
Expand Down
10 changes: 8 additions & 2 deletions inject/src/main/java/io/avaje/inject/spi/Builder.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
Expand Down Expand Up @@ -146,15 +147,20 @@ static Builder newBuilder(List<SuppliedBean> suppliedBeans, List<EnrichBean> enr
<T> T get(Type cls, String name);

/**
* Get a list of dependencies for the interface type .
* Get a list of dependencies for the type.
*/
<T> List<T> list(Type interfaceType);

/**
* Get a set of dependencies for the interface type .
* Get a set of dependencies for the type.
*/
<T> Set<T> set(Type interfaceType);

/**
* Return a map of dependencies keyed by qualifier name.
*/
<T> Map<String, T> map(Type interfaceType);

/**
* Build and return the bean scope.
*/
Expand Down
18 changes: 18 additions & 0 deletions inject/src/main/java/io/avaje/inject/spi/DBeanMap.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.avaje.inject.spi;

import io.avaje.inject.BeanScope;
import jakarta.inject.Provider;

import java.lang.reflect.Type;
Expand Down Expand Up @@ -118,6 +119,23 @@ List<Object> all(Type type) {
return entry != null ? entry.all() : Collections.emptyList();
}

/**
* Return a map of bean instances keyed by qualifier name.
*/
Map<String, Object> map(Type type, BeanScope parent) {
if (parent == null) {
return map(type);
}
Map<String, Object> result = parent.map(type);
result.putAll(map(type));
return result;
}

private Map<String, Object> map(Type type) {
DContextEntry entry = beans.get(type.getTypeName());
return entry != null ? entry.map() : Collections.emptyMap();
}

/**
* Return true if there is a supplied bean for the name and types.
*/
Expand Down
Loading