diff --git a/core/src/com/google/inject/Injector.java b/core/src/com/google/inject/Injector.java index f485aa4453..a0535405e2 100644 --- a/core/src/com/google/inject/Injector.java +++ b/core/src/com/google/inject/Injector.java @@ -16,7 +16,9 @@ package com.google.inject; +import com.google.common.collect.ListMultimap; import com.google.inject.spi.Element; +import com.google.inject.spi.InjectionPoint; import com.google.inject.spi.TypeConverterBinding; import java.lang.annotation.Annotation; import java.util.List; @@ -269,7 +271,32 @@ public interface Injector { *

The returned list does not include elements inherited from a {@link #getParent() parent * injector}, should one exist. * + *

The returned list is immutable; it contains only the elements that were present when {@link + * #getElements} was invoked. Subsequent calls may return a list with additional elements. + * + *

The returned list does not include data inherited from a {@link #getParent() parent + * injector}, should one exist. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + * * @since 5.0 */ List getElements(); + + /** + * Returns the injection points created for calls to {@link #getMembersInjector} (either directly + * or indirectly, e.g. through {@link #injectMembers}. + * + *

This excludes any injection points from elements (which are accessible from each element via + * the SPI), unless {@link #getMembersInjector} or {@link #injectMembers} were also called for the + * same key. + * + *

The returned multimap does not include data inherited from a {@link #getParent() parent + * injector}, should one exist. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + * + * @since 5.0 + */ + ListMultimap, InjectionPoint> getAllMembersInjectorInjectionPoints(); } diff --git a/core/src/com/google/inject/internal/FailableCache.java b/core/src/com/google/inject/internal/FailableCache.java index e1a461d8f7..def5d980dd 100644 --- a/core/src/com/google/inject/internal/FailableCache.java +++ b/core/src/com/google/inject/internal/FailableCache.java @@ -19,6 +19,9 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import java.util.Map; /** * Lazily creates (and caches) values for keys. If creating the value fails (with errors), an @@ -62,4 +65,16 @@ public V get(K key, Errors errors) throws ErrorsException { boolean remove(K key) { return delegate.asMap().remove(key) != null; } + + Map asMap() { + return Maps.transformValues( + Maps.filterValues( + ImmutableMap.copyOf(delegate.asMap()), + resultOrError -> !(resultOrError instanceof Errors)), + resultOrError -> { + @SuppressWarnings("unchecked") // create returned a non-error result, so this is safe + V result = (V) resultOrError; + return result; + }); + } } diff --git a/core/src/com/google/inject/internal/InjectorImpl.java b/core/src/com/google/inject/internal/InjectorImpl.java index b6c389039c..9c091e23ff 100644 --- a/core/src/com/google/inject/internal/InjectorImpl.java +++ b/core/src/com/google/inject/internal/InjectorImpl.java @@ -22,10 +22,12 @@ import com.google.common.base.Objects; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ListMultimap; import com.google.common.collect.Maps; +import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; import com.google.inject.Binder; import com.google.inject.Binding; @@ -128,6 +130,9 @@ enum JitLimitation { Lookups lookups = new DeferredLookups(this); + /** The set of types passed to {@link #getMembersInjector} and {@link #injectMembers}. */ + final Set> userRequestedMembersInjectorTypes = Sets.newConcurrentHashSet(); + InjectorImpl(InjectorImpl parent, State state, InjectorOptions injectorOptions) { this.parent = parent; this.state = state; @@ -984,6 +989,14 @@ public List getElements() { return elements.build(); } + @Override + public ListMultimap, InjectionPoint> getAllMembersInjectorInjectionPoints() { + return ImmutableListMultimap.copyOf( + Multimaps.filterKeys( + membersInjectorStore.getAllInjectionPoints(), + userRequestedMembersInjectorTypes::contains)); + } + /** Returns parameter injectors, or {@code null} if there are no parameters. */ SingleParameterInjector[] getParametersInjectors(List> parameters, Errors errors) throws ErrorsException { @@ -1038,6 +1051,8 @@ public void injectMembers(Object instance) { @Override public MembersInjector getMembersInjector(TypeLiteral typeLiteral) { checkNotNull(typeLiteral, "typeLiteral"); + userRequestedMembersInjectorTypes.add(typeLiteral); + Errors errors = new Errors(typeLiteral); try { return membersInjectorStore.get(typeLiteral, errors); diff --git a/core/src/com/google/inject/internal/InternalInjectorCreator.java b/core/src/com/google/inject/internal/InternalInjectorCreator.java index 43f326de0a..1c063b644b 100644 --- a/core/src/com/google/inject/internal/InternalInjectorCreator.java +++ b/core/src/com/google/inject/internal/InternalInjectorCreator.java @@ -16,6 +16,7 @@ package com.google.inject.internal; +import com.google.common.collect.ListMultimap; import com.google.inject.Binding; import com.google.inject.Injector; import com.google.inject.Key; @@ -28,6 +29,7 @@ import com.google.inject.internal.util.Stopwatch; import com.google.inject.spi.Dependency; import com.google.inject.spi.Element; +import com.google.inject.spi.InjectionPoint; import com.google.inject.spi.TypeConverterBinding; import java.lang.annotation.Annotation; import java.util.ArrayList; @@ -312,6 +314,11 @@ public List getElements() { return delegateInjector.getElements(); } + @Override + public ListMultimap, InjectionPoint> getAllMembersInjectorInjectionPoints() { + return delegateInjector.getAllMembersInjectorInjectionPoints(); + } + @Override public Provider getProvider(Key key) { throw new UnsupportedOperationException( diff --git a/core/src/com/google/inject/internal/MembersInjectorStore.java b/core/src/com/google/inject/internal/MembersInjectorStore.java index 6328902ddf..4a2f2ec5c6 100644 --- a/core/src/com/google/inject/internal/MembersInjectorStore.java +++ b/core/src/com/google/inject/internal/MembersInjectorStore.java @@ -16,7 +16,10 @@ package com.google.inject.internal; +import static com.google.common.collect.ImmutableListMultimap.flatteningToImmutableListMultimap; + import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.ConfigurationException; @@ -26,6 +29,7 @@ import com.google.inject.spi.TypeListenerBinding; import java.lang.reflect.Field; import java.util.List; +import java.util.Map.Entry; import java.util.Set; /** @@ -133,4 +137,11 @@ ImmutableList getInjectors( } return ImmutableList.copyOf(injectors); } + + ImmutableListMultimap, InjectionPoint> getAllInjectionPoints() { + return cache.asMap().entrySet().stream() + .collect( + flatteningToImmutableListMultimap( + Entry::getKey, entry -> entry.getValue().getInjectionPoints().stream())); + } } diff --git a/core/test/com/google/inject/spi/InjectorSpiTest.java b/core/test/com/google/inject/spi/InjectorSpiTest.java index 91a0bd5851..e592fcd04f 100644 --- a/core/test/com/google/inject/spi/InjectorSpiTest.java +++ b/core/test/com/google/inject/spi/InjectorSpiTest.java @@ -2,6 +2,8 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; import com.google.inject.AbstractModule; import com.google.inject.Asserts; import com.google.inject.Binding; @@ -317,4 +319,79 @@ private static class Baz { @Inject Provider fooP; } + + public void testGetAllMembersInjectorInjectionPoints_injectMembers_returned() { + Injector injector = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(Integer.class).toInstance(42); + bind(String.class).toInstance("bar"); + } + }); + injector.injectMembers(new ClassWithInjectableField("foo")); + Multimap, InjectionPoint> injectionPoints = + injector.getAllMembersInjectorInjectionPoints(); + TypeLiteral expectedTypeLiteral = + TypeLiteral.get(ClassWithInjectableField.class); + assertThat(injectionPoints.keySet()).containsExactly(expectedTypeLiteral); + Key actualDependencyKey = + Iterables.getOnlyElement( + Iterables.getOnlyElement(injectionPoints.get(expectedTypeLiteral)) + .getDependencies()) + .getKey(); + assertEquals(Key.get(Integer.class), actualDependencyKey); + } + + public void testGetAllMembersInjectorInjectionPoints_getInstance() { + Injector injector = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(Integer.class).toInstance(42); + bind(String.class).toInstance("bar"); + } + }); + injector.getInstance(ClassWithInjectableField.class); + Multimap, InjectionPoint> injectionPoints = + injector.getAllMembersInjectorInjectionPoints(); + assertThat(injectionPoints).isEmpty(); + } + + public void testGetAllMembersInjectorInjectionPoints_getInstanceAndInjectMembers() { + Injector injector = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(Integer.class).toInstance(42); + bind(String.class).toInstance("bar"); + } + }); + injector.injectMembers(new ClassWithInjectableField("foo")); + injector.getInstance(ClassWithInjectableField.class); + Multimap, InjectionPoint> injectionPoints = + injector.getAllMembersInjectorInjectionPoints(); + TypeLiteral expectedTypeLiteral = + TypeLiteral.get(ClassWithInjectableField.class); + assertThat(injectionPoints.keySet()).containsExactly(expectedTypeLiteral); + Key actualDependencyKey = + Iterables.getOnlyElement( + Iterables.getOnlyElement(injectionPoints.get(expectedTypeLiteral)) + .getDependencies()) + .getKey(); + assertEquals(Key.get(Integer.class), actualDependencyKey); + } + + private static class ClassWithInjectableField { + + @Inject + ClassWithInjectableField(String name) {} + + @Inject private Integer instanceField; + + @Inject private static Double staticField; + } }