/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.inject.internal; import static com.google.inject.internal.RealMapBinder.entryOfJavaxProviderOf; import static com.google.inject.internal.RealMapBinder.entryOfProviderOf; import static com.google.inject.internal.RealMapBinder.mapOf; import static com.google.inject.internal.RealMapBinder.mapOfCollectionOfJavaxProviderOf; import static com.google.inject.internal.RealMapBinder.mapOfCollectionOfProviderOf; import static com.google.inject.internal.RealMapBinder.mapOfJavaxProviderOf; import static com.google.inject.internal.RealMapBinder.mapOfProviderOf; import static com.google.inject.internal.RealMapBinder.mapOfSetOfJavaxProviderOf; import static com.google.inject.internal.RealMapBinder.mapOfSetOfProviderOf; import static com.google.inject.internal.RealMultibinder.collectionOfJavaxProvidersOf; import static com.google.inject.internal.RealMultibinder.collectionOfProvidersOf; import static com.google.inject.internal.RealMultibinder.setOf; import static com.google.inject.internal.SpiUtils.BindType.INSTANCE; import static com.google.inject.internal.SpiUtils.BindType.LINKED; import static com.google.inject.internal.SpiUtils.BindType.PROVIDER_INSTANCE; import static com.google.inject.internal.SpiUtils.BindType.PROVIDER_KEY; import static com.google.inject.internal.SpiUtils.VisitType.BOTH; import static com.google.inject.internal.SpiUtils.VisitType.INJECTOR; import static com.google.inject.internal.SpiUtils.VisitType.MODULE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.Sets; import com.google.inject.Binding; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.internal.Indexer.IndexedBinding; import com.google.inject.internal.RealMapBinder.ProviderMapEntry; import com.google.inject.multibindings.MapBinderBinding; import com.google.inject.multibindings.MultibinderBinding; import com.google.inject.multibindings.MultibindingsTargetVisitor; import com.google.inject.multibindings.OptionalBinderBinding; import com.google.inject.spi.DefaultBindingTargetVisitor; import com.google.inject.spi.Element; import com.google.inject.spi.Elements; import com.google.inject.spi.InstanceBinding; import com.google.inject.spi.LinkedKeyBinding; import com.google.inject.spi.ProviderInstanceBinding; import com.google.inject.spi.ProviderKeyBinding; import com.google.inject.spi.ProviderLookup; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Utilities for testing the Multibinder & MapBinder extension SPI. * * @author sameb@google.com (Sam Berlin) */ public class SpiUtils { private static final boolean HAS_JAVA_OPTIONAL; static { Class<?> optional = null; try { optional = Class.forName("java.util.Optional"); } catch (ClassNotFoundException ignored) { } HAS_JAVA_OPTIONAL = optional != null; } /** The kind of test we should perform. A live Injector, a raw Elements (Module) test, or both. */ enum VisitType { INJECTOR, MODULE, BOTH } /** * Asserts that MapBinderBinding visitors for work correctly. * * @param <T> The type of the binding * @param mapKey The key the map belongs to. * @param keyType the TypeLiteral of the key of the map * @param valueType the TypeLiteral of the value of the map * @param modules The modules that define the mapbindings * @param visitType The kind of test we should perform. A live Injector, a raw Elements (Module) * test, or both. * @param allowDuplicates If duplicates are allowed. * @param expectedMapBindings The number of other mapbinders we expect to see. * @param results The kind of bindings contained in the mapbinder. */ static <T> void assertMapVisitor( Key<T> mapKey, TypeLiteral<?> keyType, TypeLiteral<?> valueType, Iterable<? extends Module> modules, VisitType visitType, boolean allowDuplicates, int expectedMapBindings, MapResult... results) { if (visitType == null) { fail("must test something"); } if (visitType == BOTH || visitType == INJECTOR) { mapInjectorTest( mapKey, keyType, valueType, modules, allowDuplicates, expectedMapBindings, results); } if (visitType == BOTH || visitType == MODULE) { mapModuleTest( mapKey, keyType, valueType, modules, allowDuplicates, expectedMapBindings, results); } } @SuppressWarnings("unchecked") private static <T> void mapInjectorTest( Key<T> mapKey, TypeLiteral<?> keyType, TypeLiteral<?> valueType, Iterable<? extends Module> modules, boolean allowDuplicates, int expectedMapBindings, MapResult... results) { Injector injector = Guice.createInjector(modules); Visitor<T> visitor = new Visitor<T>(); Binding<T> mapBinding = injector.getBinding(mapKey); MapBinderBinding<T> mapbinder = (MapBinderBinding<T>) mapBinding.acceptTargetVisitor(visitor); assertNotNull(mapbinder); assertEquals(keyType, mapbinder.getKeyTypeLiteral()); assertEquals(valueType, mapbinder.getValueTypeLiteral()); assertEquals(allowDuplicates, mapbinder.permitsDuplicates()); List<Map.Entry<?, Binding<?>>> entries = Lists.newArrayList(mapbinder.getEntries()); List<MapResult> mapResults = Lists.newArrayList(results); assertEquals( "wrong entries, expected: " + mapResults + ", but was: " + entries, mapResults.size(), entries.size()); for (MapResult result : mapResults) { Map.Entry<?, Binding<?>> found = null; for (Map.Entry<?, Binding<?>> entry : entries) { Object key = entry.getKey(); Binding<?> value = entry.getValue(); if (key.equals(result.k) && matches(value, result.v)) { found = entry; break; } } if (found == null) { fail("Could not find entry: " + result + " in remaining entries: " + entries); } else { assertTrue( "mapBinder doesn't contain: " + found.getValue(), mapbinder.containsElement(found.getValue())); entries.remove(found); } } if (!entries.isEmpty()) { fail("Found all entries of: " + mapResults + ", but more were left over: " + entries); } Key<?> mapOfJavaxProvider = mapKey.ofType(mapOfJavaxProviderOf(keyType, valueType)); Key<?> mapOfProvider = mapKey.ofType(mapOfProviderOf(keyType, valueType)); Key<?> mapOfSetOfProvider = mapKey.ofType(mapOfSetOfProviderOf(keyType, valueType)); Key<?> mapOfSetOfJavaxProvider = mapKey.ofType(mapOfSetOfJavaxProviderOf(keyType, valueType)); Key<?> mapOfCollectionOfProvider = mapKey.ofType(mapOfCollectionOfProviderOf(keyType, valueType)); Key<?> mapOfCollectionOfJavaxProvider = mapKey.ofType(mapOfCollectionOfJavaxProviderOf(keyType, valueType)); Key<?> mapOfSet = mapKey.ofType(mapOf(keyType, setOf(valueType))); Key<?> setOfEntry = mapKey.ofType(setOf(entryOfProviderOf(keyType, valueType))); Key<?> setOfJavaxEntry = mapKey.ofType(setOf(entryOfJavaxProviderOf(keyType, valueType))); Key<?> collectionOfProvidersOfEntryOfProvider = mapKey.ofType(collectionOfProvidersOf(entryOfProviderOf(keyType, valueType))); Key<?> collectionOfJavaxProvidersOfEntryOfProvider = mapKey.ofType(collectionOfJavaxProvidersOf(entryOfProviderOf(keyType, valueType))); boolean entrySetMatch = false; boolean javaxEntrySetMatch = false; boolean mapJavaxProviderMatch = false; boolean mapProviderMatch = false; boolean mapSetMatch = false; boolean mapSetProviderMatch = false; boolean mapSetJavaxProviderMatch = false; boolean mapCollectionProviderMatch = false; boolean mapCollectionJavaxProviderMatch = false; boolean collectionOfProvidersOfEntryOfProviderMatch = false; boolean collectionOfJavaxProvidersOfEntryOfProviderMatch = false; List<Object> otherMapBindings = Lists.newArrayList(); List<Binding> otherMatches = Lists.newArrayList(); Multimap<Object, IndexedBinding> indexedEntries = MultimapBuilder.hashKeys().hashSetValues().build(); Indexer indexer = new Indexer(injector); int duplicates = 0; for (Binding b : injector.getAllBindings().values()) { boolean contains = mapbinder.containsElement(b); Object visited = b.acceptTargetVisitor(visitor); if (visited instanceof MapBinderBinding) { if (visited.equals(mapbinder)) { assertTrue(contains); } else { otherMapBindings.add(visited); } } else if (b.getKey().equals(mapOfProvider)) { assertTrue(contains); mapProviderMatch = true; } else if (b.getKey().equals(mapOfJavaxProvider)) { assertTrue(contains); mapJavaxProviderMatch = true; } else if (b.getKey().equals(mapOfSet)) { assertTrue(contains); mapSetMatch = true; } else if (b.getKey().equals(mapOfSetOfProvider)) { assertTrue(contains); mapSetProviderMatch = true; } else if (b.getKey().equals(mapOfSetOfJavaxProvider)) { assertTrue(contains); mapSetJavaxProviderMatch = true; } else if (b.getKey().equals(mapOfCollectionOfProvider)) { assertTrue(contains); mapCollectionProviderMatch = true; } else if (b.getKey().equals(mapOfCollectionOfJavaxProvider)) { assertTrue(contains); mapCollectionJavaxProviderMatch = true; } else if (b.getKey().equals(setOfEntry)) { assertTrue(contains); entrySetMatch = true; // Validate that this binding is also a MultibinderBinding. assertTrue(b.acceptTargetVisitor(visitor) instanceof MultibinderBinding); } else if (b.getKey().equals(setOfJavaxEntry)) { assertTrue(contains); javaxEntrySetMatch = true; } else if (b.getKey().equals(collectionOfProvidersOfEntryOfProvider)) { assertTrue(contains); collectionOfProvidersOfEntryOfProviderMatch = true; } else if (b.getKey().equals(collectionOfJavaxProvidersOfEntryOfProvider)) { assertTrue(contains); collectionOfJavaxProvidersOfEntryOfProviderMatch = true; } else if (contains) { if (b instanceof ProviderInstanceBinding) { ProviderInstanceBinding<?> pib = (ProviderInstanceBinding<?>) b; if (pib.getUserSuppliedProvider() instanceof ProviderMapEntry) { // weird casting required to workaround compilation issues with jdk6 ProviderMapEntry<?, ?> pme = (ProviderMapEntry<?, ?>) (Provider) pib.getUserSuppliedProvider(); Binding<?> valueBinding = injector.getBinding(pme.getValueKey()); if (indexer.isIndexable(valueBinding) && !indexedEntries.put(pme.getKey(), valueBinding.acceptTargetVisitor(indexer))) { duplicates++; } } } otherMatches.add(b); } } int sizeOfOther = otherMatches.size(); if (allowDuplicates) { sizeOfOther--; // account for 1 duplicate binding } // Multiply by two because each has a value and Map.Entry. int expectedSize = 2 * (mapResults.size() + duplicates); assertEquals( "Incorrect other matches:\n\t" + Joiner.on("\n\t").join(otherMatches), expectedSize, sizeOfOther); assertTrue(entrySetMatch); assertTrue(javaxEntrySetMatch); assertTrue(mapProviderMatch); assertTrue(mapJavaxProviderMatch); assertTrue(collectionOfProvidersOfEntryOfProviderMatch); assertTrue(collectionOfJavaxProvidersOfEntryOfProviderMatch); assertEquals(allowDuplicates, mapSetMatch); assertEquals(allowDuplicates, mapSetProviderMatch); assertEquals(allowDuplicates, mapSetJavaxProviderMatch); assertEquals(allowDuplicates, mapCollectionJavaxProviderMatch); assertEquals(allowDuplicates, mapCollectionProviderMatch); assertEquals( "other MapBindings found: " + otherMapBindings, expectedMapBindings, otherMapBindings.size()); } @SuppressWarnings("unchecked") private static <T> void mapModuleTest( Key<T> mapKey, TypeLiteral<?> keyType, TypeLiteral<?> valueType, Iterable<? extends Module> modules, boolean allowDuplicates, int expectedMapBindings, MapResult<?, ?>... results) { Set<Element> elements = ImmutableSet.copyOf(Elements.getElements(modules)); Visitor<T> visitor = new Visitor<T>(); MapBinderBinding<T> mapbinder = null; Map<Key<?>, Binding<?>> keyMap = Maps.newHashMap(); for (Element element : elements) { if (element instanceof Binding) { Binding<?> binding = (Binding<?>) element; keyMap.put(binding.getKey(), binding); if (binding.getKey().equals(mapKey)) { mapbinder = (MapBinderBinding<T>) ((Binding<T>) binding).acceptTargetVisitor(visitor); } } } assertNotNull(mapbinder); List<MapResult<?, ?>> mapResults = Lists.newArrayList(results); // Make sure the entries returned from getEntries(elements) are correct. // Because getEntries() can return duplicates, make sure to continue searching, even // after we find one match. List<Map.Entry<?, Binding<?>>> entries = Lists.newArrayList(mapbinder.getEntries(elements)); for (MapResult<?, ?> result : mapResults) { List<Map.Entry<?, Binding<?>>> foundEntries = Lists.newArrayList(); for (Map.Entry<?, Binding<?>> entry : entries) { Object key = entry.getKey(); Binding<?> value = entry.getValue(); if (key.equals(result.k) && matches(value, result.v)) { assertTrue( "mapBinder doesn't contain: " + entry.getValue(), mapbinder.containsElement(entry.getValue())); foundEntries.add(entry); } } assertTrue( "Could not find entry: " + result + " in remaining entries: " + entries, !foundEntries.isEmpty()); entries.removeAll(foundEntries); } assertTrue( "Found all entries of: " + mapResults + ", but more were left over: " + entries, entries.isEmpty()); assertEquals(keyType, mapbinder.getKeyTypeLiteral()); assertEquals(valueType, mapbinder.getValueTypeLiteral()); Key<?> mapOfProvider = mapKey.ofType(mapOfProviderOf(keyType, valueType)); Key<?> mapOfJavaxProvider = mapKey.ofType(mapOfJavaxProviderOf(keyType, valueType)); Key<?> mapOfSetOfProvider = mapKey.ofType(mapOfSetOfProviderOf(keyType, valueType)); Key<?> mapOfSetOfJavaxProvider = mapKey.ofType(mapOfSetOfJavaxProviderOf(keyType, valueType)); Key<?> mapOfCollectionOfProvider = mapKey.ofType(mapOfCollectionOfProviderOf(keyType, valueType)); Key<?> mapOfCollectionOfJavaxProvider = mapKey.ofType(mapOfCollectionOfJavaxProviderOf(keyType, valueType)); Key<?> mapOfSet = mapKey.ofType(mapOf(keyType, setOf(valueType))); Key<?> setOfEntry = mapKey.ofType(setOf(entryOfProviderOf(keyType, valueType))); Key<?> setOfJavaxEntry = mapKey.ofType(setOf(entryOfJavaxProviderOf(keyType, valueType))); Key<?> collectionOfProvidersOfEntryOfProvider = mapKey.ofType(collectionOfProvidersOf(entryOfProviderOf(keyType, valueType))); Key<?> collectionOfJavaxProvidersOfEntryOfProvider = mapKey.ofType(collectionOfJavaxProvidersOf(entryOfProviderOf(keyType, valueType))); boolean entrySetMatch = false; boolean entrySetJavaxMatch = false; boolean mapProviderMatch = false; boolean mapJavaxProviderMatch = false; boolean mapSetMatch = false; boolean mapSetProviderMatch = false; boolean mapSetJavaxProviderMatch = false; boolean mapCollectionProviderMatch = false; boolean mapCollectionJavaxProviderMatch = false; boolean collectionOfProvidersOfEntryOfProviderMatch = false; boolean collectionOfJavaxProvidersOfEntryOfProviderMatch = false; List<Object> otherMapBindings = Lists.newArrayList(); List<Element> otherMatches = Lists.newArrayList(); List<Element> otherElements = Lists.newArrayList(); Indexer indexer = new Indexer(null); Multimap<Object, IndexedBinding> indexedEntries = MultimapBuilder.hashKeys().hashSetValues().build(); int duplicates = 0; for (Element element : elements) { boolean contains = mapbinder.containsElement(element); if (!contains) { otherElements.add(element); } boolean matched = false; Key key = null; Binding b = null; if (element instanceof Binding) { b = (Binding) element; if (b instanceof ProviderInstanceBinding) { ProviderInstanceBinding<?> pb = (ProviderInstanceBinding<?>) b; if (pb.getUserSuppliedProvider() instanceof ProviderMapEntry) { // weird casting required to workaround jdk6 compilation problems ProviderMapEntry<?, ?> pme = (ProviderMapEntry<?, ?>) (Provider) pb.getUserSuppliedProvider(); Binding<?> valueBinding = keyMap.get(pme.getValueKey()); if (indexer.isIndexable(valueBinding) && !indexedEntries.put(pme.getKey(), valueBinding.acceptTargetVisitor(indexer))) { duplicates++; } } } key = b.getKey(); Object visited = b.acceptTargetVisitor(visitor); if (visited instanceof MapBinderBinding) { matched = true; if (visited.equals(mapbinder)) { assertTrue(contains); } else { otherMapBindings.add(visited); } } } else if (element instanceof ProviderLookup) { key = ((ProviderLookup) element).getKey(); } if (!matched && key != null) { if (key.equals(mapOfProvider)) { matched = true; assertTrue(contains); mapProviderMatch = true; } else if (key.equals(mapOfJavaxProvider)) { matched = true; assertTrue(contains); mapJavaxProviderMatch = true; } else if (key.equals(mapOfSet)) { matched = true; assertTrue(contains); mapSetMatch = true; } else if (key.equals(mapOfSetOfProvider)) { matched = true; assertTrue(contains); mapSetProviderMatch = true; } else if (key.equals(mapOfSetOfJavaxProvider)) { matched = true; assertTrue(contains); mapSetJavaxProviderMatch = true; } else if (key.equals(mapOfCollectionOfProvider)) { matched = true; assertTrue(contains); mapCollectionProviderMatch = true; } else if (key.equals(mapOfCollectionOfJavaxProvider)) { matched = true; assertTrue(contains); mapCollectionJavaxProviderMatch = true; } else if (key.equals(setOfEntry)) { matched = true; assertTrue(contains); entrySetMatch = true; // Validate that this binding is also a MultibinderBinding. if (b != null) { assertTrue(b.acceptTargetVisitor(visitor) instanceof MultibinderBinding); } } else if (key.equals(setOfJavaxEntry)) { matched = true; assertTrue(contains); entrySetJavaxMatch = true; } else if (key.equals(collectionOfProvidersOfEntryOfProvider)) { matched = true; assertTrue(contains); collectionOfProvidersOfEntryOfProviderMatch = true; } else if (key.equals(collectionOfJavaxProvidersOfEntryOfProvider)) { matched = true; assertTrue(contains); collectionOfJavaxProvidersOfEntryOfProviderMatch = true; } } if (!matched && contains) { otherMatches.add(element); } } int otherMatchesSize = otherMatches.size(); if (allowDuplicates) { otherMatchesSize--; // allow for 1 duplicate binding } // Multiply by 3 because each has a value, ProviderLookup, and Map.Entry int expectedSize = (mapResults.size() + duplicates) * 3; assertEquals( "incorrect number of contains, leftover matches:\n" + Joiner.on("\n\t").join(otherMatches), expectedSize, otherMatchesSize); assertTrue(entrySetMatch); assertTrue(entrySetJavaxMatch); assertTrue(mapProviderMatch); assertTrue(mapJavaxProviderMatch); assertTrue(collectionOfProvidersOfEntryOfProviderMatch); assertTrue(collectionOfJavaxProvidersOfEntryOfProviderMatch); assertEquals(allowDuplicates, mapSetMatch); assertEquals(allowDuplicates, mapSetProviderMatch); assertEquals(allowDuplicates, mapSetJavaxProviderMatch); assertEquals(allowDuplicates, mapCollectionProviderMatch); assertEquals(allowDuplicates, mapCollectionJavaxProviderMatch); assertEquals( "other MapBindings found: " + otherMapBindings, expectedMapBindings, otherMapBindings.size()); // Validate that we can construct an injector out of the remaining bindings. Guice.createInjector(Elements.getModule(otherElements)); } /** * Asserts that MultibinderBinding visitors work correctly. * * @param <T> The type of the binding * @param setKey The key the set belongs to. * @param elementType the TypeLiteral of the element * @param modules The modules that define the multibindings * @param visitType The kind of test we should perform. A live Injector, a raw Elements (Module) * test, or both. * @param allowDuplicates If duplicates are allowed. * @param expectedMultibindings The number of other multibinders we expect to see. * @param results The kind of bindings contained in the multibinder. */ static <T> void assertSetVisitor( Key<Set<T>> setKey, TypeLiteral<?> elementType, Iterable<? extends Module> modules, VisitType visitType, boolean allowDuplicates, int expectedMultibindings, BindResult... results) { if (visitType == null) { fail("must test something"); } if (visitType == BOTH || visitType == INJECTOR) { setInjectorTest( setKey, elementType, modules, allowDuplicates, expectedMultibindings, results); } if (visitType == BOTH || visitType == MODULE) { setModuleTest(setKey, elementType, modules, allowDuplicates, expectedMultibindings, results); } } @SuppressWarnings("unchecked") private static <T> void setInjectorTest( Key<Set<T>> setKey, TypeLiteral<?> elementType, Iterable<? extends Module> modules, boolean allowDuplicates, int otherMultibindings, BindResult... results) { Key<?> collectionOfProvidersKey = setKey.ofType(collectionOfProvidersOf(elementType)); Key<?> collectionOfJavaxProvidersKey = setKey.ofType(collectionOfJavaxProvidersOf(elementType)); Injector injector = Guice.createInjector(modules); Visitor<Set<T>> visitor = new Visitor<Set<T>>(); Binding<Set<T>> binding = injector.getBinding(setKey); MultibinderBinding<Set<T>> multibinder = (MultibinderBinding<Set<T>>) binding.acceptTargetVisitor(visitor); assertNotNull(multibinder); assertEquals(elementType, multibinder.getElementTypeLiteral()); assertEquals(allowDuplicates, multibinder.permitsDuplicates()); List<Binding<?>> elements = Lists.newArrayList(multibinder.getElements()); List<BindResult> bindResults = Lists.newArrayList(results); assertEquals( "wrong bind elements, expected: " + bindResults + ", but was: " + multibinder.getElements(), bindResults.size(), elements.size()); for (BindResult result : bindResults) { Binding found = null; for (Binding item : elements) { if (matches(item, result)) { found = item; break; } } if (found == null) { fail("Could not find element: " + result + " in remaining elements: " + elements); } else { elements.remove(found); } } if (!elements.isEmpty()) { fail("Found all elements of: " + bindResults + ", but more were left over: " + elements); } Set<Binding> setOfElements = new HashSet<Binding>(multibinder.getElements()); Set<IndexedBinding> setOfIndexed = Sets.newHashSet(); Indexer indexer = new Indexer(injector); for (Binding<?> oneBinding : setOfElements) { setOfIndexed.add(oneBinding.acceptTargetVisitor(indexer)); } List<Object> otherMultibinders = Lists.newArrayList(); List<Binding> otherContains = Lists.newArrayList(); boolean collectionOfProvidersMatch = false; boolean collectionOfJavaxProvidersMatch = false; for (Binding b : injector.getAllBindings().values()) { boolean contains = multibinder.containsElement(b); Key key = b.getKey(); Object visited = b.acceptTargetVisitor(visitor); if (visited != null) { if (visited.equals(multibinder)) { assertTrue(contains); } else { otherMultibinders.add(visited); } } else if (setOfElements.contains(b)) { assertTrue(contains); } else if (key.equals(collectionOfProvidersKey)) { assertTrue(contains); collectionOfProvidersMatch = true; } else if (key.equals(collectionOfJavaxProvidersKey)) { assertTrue(contains); collectionOfJavaxProvidersMatch = true; } else if (contains) { if (!indexer.isIndexable(b) || !setOfIndexed.contains(b.acceptTargetVisitor(indexer))) { otherContains.add(b); } } } assertTrue(collectionOfProvidersMatch); assertTrue(collectionOfJavaxProvidersMatch); if (allowDuplicates) { assertEquals("contained more than it should: " + otherContains, 1, otherContains.size()); } else { assertTrue("contained more than it should: " + otherContains, otherContains.isEmpty()); } assertEquals( "other multibindings found: " + otherMultibinders, otherMultibindings, otherMultibinders.size()); } @SuppressWarnings("unchecked") private static <T> void setModuleTest( Key<Set<T>> setKey, TypeLiteral<?> elementType, Iterable<? extends Module> modules, boolean allowDuplicates, int otherMultibindings, BindResult... results) { Key<?> collectionOfProvidersKey = setKey.ofType(collectionOfProvidersOf(elementType)); Key<?> collectionOfJavaxProvidersKey = setKey.ofType(collectionOfJavaxProvidersOf(elementType)); List<BindResult> bindResults = Lists.newArrayList(results); List<Element> elements = Elements.getElements(modules); Visitor<T> visitor = new Visitor<T>(); MultibinderBinding<Set<T>> multibinder = null; for (Element element : elements) { if (element instanceof Binding && ((Binding) element).getKey().equals(setKey)) { multibinder = (MultibinderBinding<Set<T>>) ((Binding) element).acceptTargetVisitor(visitor); break; } } assertNotNull(multibinder); assertEquals(elementType, multibinder.getElementTypeLiteral()); List<Object> otherMultibinders = Lists.newArrayList(); Set<Element> otherContains = new HashSet<Element>(); List<Element> otherElements = Lists.newArrayList(); int duplicates = 0; Set<IndexedBinding> setOfIndexed = Sets.newHashSet(); Indexer indexer = new Indexer(null); boolean collectionOfProvidersMatch = false; boolean collectionOfJavaxProvidersMatch = false; for (Element element : elements) { boolean contains = multibinder.containsElement(element); if (!contains) { otherElements.add(element); } boolean matched = false; Key key = null; if (element instanceof Binding) { Binding binding = (Binding) element; if (indexer.isIndexable(binding) && !setOfIndexed.add((IndexedBinding) binding.acceptTargetVisitor(indexer))) { duplicates++; } key = binding.getKey(); Object visited = binding.acceptTargetVisitor(visitor); if (visited != null) { matched = true; if (visited.equals(multibinder)) { assertTrue(contains); } else { otherMultibinders.add(visited); } } } if (collectionOfProvidersKey.equals(key)) { assertTrue(contains); assertFalse(matched); collectionOfProvidersMatch = true; } else if (collectionOfJavaxProvidersKey.equals(key)) { assertTrue(contains); assertFalse(matched); collectionOfJavaxProvidersMatch = true; } else if (!matched && contains) { otherContains.add(element); } } if (allowDuplicates) { assertEquals( "wrong contained elements: " + otherContains, bindResults.size() + 1 + duplicates, otherContains.size()); } else { assertEquals( "wrong contained elements: " + otherContains, bindResults.size() + duplicates, otherContains.size()); } assertEquals( "other multibindings found: " + otherMultibinders, otherMultibindings, otherMultibinders.size()); assertTrue(collectionOfProvidersMatch); assertTrue(collectionOfJavaxProvidersMatch); // Validate that we can construct an injector out of the remaining bindings. Guice.createInjector(Elements.getModule(otherElements)); } /** * Asserts that OptionalBinderBinding visitors for work correctly. * * @param <T> The type of the binding * @param keyType The key OptionalBinder is binding * @param modules The modules that define the bindings * @param visitType The kind of test we should perform. A live Injector, a raw Elements (Module) * test, or both. * @param expectedOtherOptionalBindings the # of other optional bindings we expect to see. * @param expectedDefault the expected default binding, or null if none * @param expectedActual the expected actual binding, or null if none * @param expectedUserLinkedActual the user binding that is the actual binding, used if neither * the default nor actual are set and a user binding existed for the type. */ static <T> void assertOptionalVisitor( Key<T> keyType, Iterable<? extends Module> modules, VisitType visitType, int expectedOtherOptionalBindings, BindResult<?> expectedDefault, BindResult<?> expectedActual, BindResult<?> expectedUserLinkedActual) { if (visitType == null) { fail("must test something"); } // if java.util.Optional is bound, there'll be twice as many as we expect. if (HAS_JAVA_OPTIONAL) { expectedOtherOptionalBindings *= 2; } if (visitType == BOTH || visitType == INJECTOR) { optionalInjectorTest( keyType, modules, expectedOtherOptionalBindings, expectedDefault, expectedActual, expectedUserLinkedActual); } if (visitType == BOTH || visitType == MODULE) { optionalModuleTest( keyType, modules, expectedOtherOptionalBindings, expectedDefault, expectedActual, expectedUserLinkedActual); } } @SuppressWarnings({"unchecked", "rawtypes"}) private static <T> void optionalInjectorTest( Key<T> keyType, Iterable<? extends Module> modules, int expectedOtherOptionalBindings, BindResult<?> expectedDefault, BindResult<?> expectedActual, BindResult<?> expectedUserLinkedActual) { if (expectedUserLinkedActual != null) { assertNull("cannot have actual if expecting user binding", expectedActual); assertNull("cannot have default if expecting user binding", expectedDefault); } Key<Optional<T>> optionalKey = keyType.ofType(RealOptionalBinder.optionalOf(keyType.getTypeLiteral())); Key<?> javaOptionalKey = HAS_JAVA_OPTIONAL ? keyType.ofType(RealOptionalBinder.javaOptionalOf(keyType.getTypeLiteral())) : null; Injector injector = Guice.createInjector(modules); Binding<Optional<T>> optionalBinding = injector.getBinding(optionalKey); Visitor visitor = new Visitor(); OptionalBinderBinding<Optional<T>> optionalBinder = (OptionalBinderBinding<Optional<T>>) optionalBinding.acceptTargetVisitor(visitor); assertNotNull(optionalBinder); assertEquals(optionalKey, optionalBinder.getKey()); Binding<?> javaOptionalBinding = null; OptionalBinderBinding<?> javaOptionalBinder = null; if (HAS_JAVA_OPTIONAL) { javaOptionalBinding = injector.getBinding(javaOptionalKey); javaOptionalBinder = (OptionalBinderBinding<?>) javaOptionalBinding.acceptTargetVisitor(visitor); assertNotNull(javaOptionalBinder); assertEquals(javaOptionalKey, javaOptionalBinder.getKey()); } if (expectedDefault == null) { assertNull("did not expect a default binding", optionalBinder.getDefaultBinding()); if (HAS_JAVA_OPTIONAL) { assertNull("did not expect a default binding", javaOptionalBinder.getDefaultBinding()); } } else { assertTrue( "expectedDefault: " + expectedDefault + ", actualDefault: " + optionalBinder.getDefaultBinding(), matches(optionalBinder.getDefaultBinding(), expectedDefault)); if (HAS_JAVA_OPTIONAL) { assertTrue( "expectedDefault: " + expectedDefault + ", actualDefault: " + javaOptionalBinder.getDefaultBinding(), matches(javaOptionalBinder.getDefaultBinding(), expectedDefault)); } } if (expectedActual == null && expectedUserLinkedActual == null) { assertNull(optionalBinder.getActualBinding()); if (HAS_JAVA_OPTIONAL) { assertNull(javaOptionalBinder.getActualBinding()); } } else if (expectedActual != null) { assertTrue( "expectedActual: " + expectedActual + ", actualActual: " + optionalBinder.getActualBinding(), matches(optionalBinder.getActualBinding(), expectedActual)); if (HAS_JAVA_OPTIONAL) { assertTrue( "expectedActual: " + expectedActual + ", actualActual: " + javaOptionalBinder.getActualBinding(), matches(javaOptionalBinder.getActualBinding(), expectedActual)); } } else if (expectedUserLinkedActual != null) { assertTrue( "expectedUserLinkedActual: " + expectedUserLinkedActual + ", actualActual: " + optionalBinder.getActualBinding(), matches(optionalBinder.getActualBinding(), expectedUserLinkedActual)); if (HAS_JAVA_OPTIONAL) { assertTrue( "expectedUserLinkedActual: " + expectedUserLinkedActual + ", actualActual: " + javaOptionalBinder.getActualBinding(), matches(javaOptionalBinder.getActualBinding(), expectedUserLinkedActual)); } } Key<Optional<javax.inject.Provider<T>>> optionalJavaxProviderKey = keyType.ofType(RealOptionalBinder.optionalOfJavaxProvider(keyType.getTypeLiteral())); Key<?> javaOptionalJavaxProviderKey = HAS_JAVA_OPTIONAL ? keyType.ofType( RealOptionalBinder.javaOptionalOfJavaxProvider(keyType.getTypeLiteral())) : null; Key<Optional<Provider<T>>> optionalProviderKey = keyType.ofType(RealOptionalBinder.optionalOfProvider(keyType.getTypeLiteral())); Key<?> javaOptionalProviderKey = HAS_JAVA_OPTIONAL ? keyType.ofType(RealOptionalBinder.javaOptionalOfProvider(keyType.getTypeLiteral())) : null; boolean keyMatch = false; boolean optionalKeyMatch = false; boolean javaOptionalKeyMatch = false; boolean optionalJavaxProviderKeyMatch = false; boolean javaOptionalJavaxProviderKeyMatch = false; boolean optionalProviderKeyMatch = false; boolean javaOptionalProviderKeyMatch = false; boolean defaultMatch = false; boolean actualMatch = false; List<Object> otherOptionalBindings = Lists.newArrayList(); List<Binding> otherMatches = Lists.newArrayList(); for (Binding b : injector.getAllBindings().values()) { boolean contains = optionalBinder.containsElement(b); if (HAS_JAVA_OPTIONAL) { assertEquals(contains, javaOptionalBinder.containsElement(b)); } Object visited = b.acceptTargetVisitor(visitor); if (visited instanceof OptionalBinderBinding) { if (visited.equals(optionalBinder)) { assertTrue(contains); } else if (HAS_JAVA_OPTIONAL && visited.equals(javaOptionalBinder)) { assertTrue(contains); } else { otherOptionalBindings.add(visited); } } if (b.getKey().equals(keyType)) { // keyType might match because a user bound it // (which is possible in a purely absent OptionalBinder) assertEquals(expectedDefault != null || expectedActual != null, contains); if (contains) { keyMatch = true; } } else if (b.getKey().equals(optionalKey)) { assertTrue(contains); optionalKeyMatch = true; } else if (b.getKey().equals(javaOptionalKey)) { assertTrue(contains); javaOptionalKeyMatch = true; } else if (b.getKey().equals(optionalJavaxProviderKey)) { assertTrue(contains); optionalJavaxProviderKeyMatch = true; } else if (b.getKey().equals(javaOptionalJavaxProviderKey)) { assertTrue(contains); javaOptionalJavaxProviderKeyMatch = true; } else if (b.getKey().equals(optionalProviderKey)) { assertTrue(contains); optionalProviderKeyMatch = true; } else if (b.getKey().equals(javaOptionalProviderKey)) { assertTrue(contains); javaOptionalProviderKeyMatch = true; } else if (expectedDefault != null && matches(b, expectedDefault)) { assertTrue(contains); defaultMatch = true; } else if (expectedActual != null && matches(b, expectedActual)) { assertTrue(contains); actualMatch = true; } else if (contains) { otherMatches.add(b); } } assertEquals(otherMatches.toString(), 0, otherMatches.size()); // only expect a keymatch if either default or actual are set assertEquals(expectedDefault != null || expectedActual != null, keyMatch); assertTrue(optionalKeyMatch); assertTrue(optionalJavaxProviderKeyMatch); assertTrue(optionalProviderKeyMatch); assertEquals(HAS_JAVA_OPTIONAL, javaOptionalKeyMatch); assertEquals(HAS_JAVA_OPTIONAL, javaOptionalJavaxProviderKeyMatch); assertEquals(HAS_JAVA_OPTIONAL, javaOptionalProviderKeyMatch); assertEquals(expectedDefault != null, defaultMatch); assertEquals(expectedActual != null, actualMatch); assertEquals( "other OptionalBindings found: " + otherOptionalBindings, expectedOtherOptionalBindings, otherOptionalBindings.size()); } @SuppressWarnings({"unchecked", "rawtypes"}) private static <T> void optionalModuleTest( Key<T> keyType, Iterable<? extends Module> modules, int expectedOtherOptionalBindings, BindResult<?> expectedDefault, BindResult<?> expectedActual, BindResult<?> expectedUserLinkedActual) { if (expectedUserLinkedActual != null) { assertNull("cannot have actual if expecting user binding", expectedActual); assertNull("cannot have default if expecting user binding", expectedDefault); } Set<Element> elements = ImmutableSet.copyOf(Elements.getElements(modules)); Map<Key<?>, Binding<?>> indexed = index(elements); Key<Optional<T>> optionalKey = keyType.ofType(RealOptionalBinder.optionalOf(keyType.getTypeLiteral())); Key<?> javaOptionalKey = HAS_JAVA_OPTIONAL ? keyType.ofType(RealOptionalBinder.javaOptionalOf(keyType.getTypeLiteral())) : null; Visitor visitor = new Visitor(); OptionalBinderBinding<Optional<T>> optionalBinder = null; OptionalBinderBinding<?> javaOptionalBinder = null; Key<?> defaultKey = null; Key<?> actualKey = null; Binding optionalBinding = indexed.get(optionalKey); optionalBinder = (OptionalBinderBinding<Optional<T>>) optionalBinding.acceptTargetVisitor(visitor); if (HAS_JAVA_OPTIONAL) { Binding javaOptionalBinding = indexed.get(javaOptionalKey); javaOptionalBinder = (OptionalBinderBinding) javaOptionalBinding.acceptTargetVisitor(visitor); } // Locate the defaultKey & actualKey for (Element element : elements) { if (optionalBinder.containsElement(element) && element instanceof Binding) { Binding binding = (Binding) element; if (isSourceEntry(binding, RealOptionalBinder.Source.DEFAULT)) { defaultKey = binding.getKey(); } else if (isSourceEntry(binding, RealOptionalBinder.Source.ACTUAL)) { actualKey = binding.getKey(); } } } assertNotNull(optionalBinder); if (HAS_JAVA_OPTIONAL) { assertNotNull(javaOptionalBinder); } assertEquals(expectedDefault == null, defaultKey == null); assertEquals(expectedActual == null, actualKey == null); Key<Optional<javax.inject.Provider<T>>> optionalJavaxProviderKey = keyType.ofType(RealOptionalBinder.optionalOfJavaxProvider(keyType.getTypeLiteral())); Key<?> javaOptionalJavaxProviderKey = HAS_JAVA_OPTIONAL ? keyType.ofType( RealOptionalBinder.javaOptionalOfJavaxProvider(keyType.getTypeLiteral())) : null; Key<Optional<Provider<T>>> optionalProviderKey = keyType.ofType(RealOptionalBinder.optionalOfProvider(keyType.getTypeLiteral())); Key<?> javaOptionalProviderKey = HAS_JAVA_OPTIONAL ? keyType.ofType(RealOptionalBinder.javaOptionalOfProvider(keyType.getTypeLiteral())) : null; boolean keyMatch = false; boolean optionalKeyMatch = false; boolean javaOptionalKeyMatch = false; boolean optionalJavaxProviderKeyMatch = false; boolean javaOptionalJavaxProviderKeyMatch = false; boolean optionalProviderKeyMatch = false; boolean javaOptionalProviderKeyMatch = false; boolean defaultMatch = false; boolean actualMatch = false; List<Object> otherOptionalElements = Lists.newArrayList(); List<Element> otherContains = Lists.newArrayList(); List<Element> nonContainedElements = Lists.newArrayList(); for (Element element : elements) { boolean contains = optionalBinder.containsElement(element); if (HAS_JAVA_OPTIONAL) { assertEquals(contains, javaOptionalBinder.containsElement(element)); } if (!contains) { nonContainedElements.add(element); } Key key = null; Binding b = null; if (element instanceof Binding) { b = (Binding) element; key = b.getKey(); Object visited = b.acceptTargetVisitor(visitor); if (visited instanceof OptionalBinderBinding) { if (visited.equals(optionalBinder)) { assertTrue(contains); } else if (HAS_JAVA_OPTIONAL && visited.equals(javaOptionalBinder)) { assertTrue(contains); } else { otherOptionalElements.add(visited); } } } else if (element instanceof ProviderLookup) { key = ((ProviderLookup) element).getKey(); } if (key != null && key.equals(keyType)) { // keyType might match because a user bound it // (which is possible in a purely absent OptionalBinder) assertEquals(expectedDefault != null || expectedActual != null, contains); if (contains) { keyMatch = true; } } else if (key != null && key.equals(optionalKey)) { assertTrue(contains); optionalKeyMatch = true; } else if (key != null && key.equals(javaOptionalKey)) { assertTrue(contains); javaOptionalKeyMatch = true; } else if (key != null && key.equals(optionalJavaxProviderKey)) { assertTrue(contains); optionalJavaxProviderKeyMatch = true; } else if (key != null && key.equals(javaOptionalJavaxProviderKey)) { assertTrue(contains); javaOptionalJavaxProviderKeyMatch = true; } else if (key != null && key.equals(optionalProviderKey)) { assertTrue(contains); optionalProviderKeyMatch = true; } else if (key != null && key.equals(javaOptionalProviderKey)) { assertTrue(contains); javaOptionalProviderKeyMatch = true; } else if (key != null && key.equals(defaultKey)) { assertTrue(contains); if (b != null) { // otherwise it might just be a ProviderLookup into it assertTrue( "expected: " + expectedDefault + ", but was: " + b, matches(b, expectedDefault)); defaultMatch = true; } } else if (key != null && key.equals(actualKey)) { assertTrue(contains); if (b != null) { // otherwise it might just be a ProviderLookup into it assertTrue("expected: " + expectedActual + ", but was: " + b, matches(b, expectedActual)); actualMatch = true; } } else if (contains) { otherContains.add(element); } } // only expect a keymatch if either default or actual are set assertEquals(expectedDefault != null || expectedActual != null, keyMatch); assertTrue(optionalKeyMatch); assertTrue(optionalJavaxProviderKeyMatch); assertTrue(optionalProviderKeyMatch); assertEquals(HAS_JAVA_OPTIONAL, javaOptionalKeyMatch); assertEquals(HAS_JAVA_OPTIONAL, javaOptionalJavaxProviderKeyMatch); assertEquals(HAS_JAVA_OPTIONAL, javaOptionalProviderKeyMatch); assertEquals(expectedDefault != null, defaultMatch); assertEquals(expectedActual != null, actualMatch); assertEquals(otherContains.toString(), 0, otherContains.size()); assertEquals( "other OptionalBindings found: " + otherOptionalElements, expectedOtherOptionalBindings, otherOptionalElements.size()); // Validate that we can construct an injector out of the remaining bindings. Guice.createInjector(Elements.getModule(nonContainedElements)); } private static boolean isSourceEntry(Binding b, RealOptionalBinder.Source type) { switch (type) { case ACTUAL: return b.getKey().getAnnotation() instanceof RealOptionalBinder.Actual; case DEFAULT: return b.getKey().getAnnotation() instanceof RealOptionalBinder.Default; default: throw new IllegalStateException("invalid type: " + type); } } /** Returns the subset of elements that have keys, indexed by them. */ private static Map<Key<?>, Binding<?>> index(Iterable<Element> elements) { ImmutableMap.Builder<Key<?>, Binding<?>> builder = ImmutableMap.builder(); for (Element element : elements) { if (element instanceof Binding) { builder.put(((Binding) element).getKey(), (Binding) element); } } return builder.build(); } static <K, V> MapResult instance(K k, V v) { return new MapResult<K, V>(k, new BindResult<V>(INSTANCE, v, null)); } static <K, V> MapResult linked(K k, Class<? extends V> clazz) { return new MapResult<K, V>(k, new BindResult<V>(LINKED, null, Key.get(clazz))); } static <K, V> MapResult linked(K k, Key<? extends V> key) { return new MapResult<K, V>(k, new BindResult<V>(LINKED, null, key)); } static <K, V> MapResult providerInstance(K k, V v) { return new MapResult<K, V>(k, new BindResult<V>(PROVIDER_INSTANCE, v, null)); } static class MapResult<K, V> { private final K k; private final BindResult<V> v; MapResult(K k, BindResult<V> v) { this.k = k; this.v = v; } @Override public String toString() { return "entry[key[" + k + "],value[" + v + "]]"; } } private static boolean matches(Binding<?> item, BindResult<?> result) { switch (result.type) { case INSTANCE: if (item instanceof InstanceBinding && ((InstanceBinding) item).getInstance().equals(result.instance)) { return true; } break; case LINKED: if (item instanceof LinkedKeyBinding && ((LinkedKeyBinding) item).getLinkedKey().equals(result.key)) { return true; } break; case PROVIDER_INSTANCE: if (item instanceof ProviderInstanceBinding && Objects.equal( ((ProviderInstanceBinding) item).getUserSuppliedProvider().get(), result.instance)) { return true; } break; case PROVIDER_KEY: if (item instanceof ProviderKeyBinding && ((ProviderKeyBinding) item).getProviderKey().equals(result.key)) { return true; } break; } return false; } static <T> BindResult<T> instance(T t) { return new BindResult<T>(INSTANCE, t, null); } static <T> BindResult<T> linked(Class<? extends T> clazz) { return new BindResult<T>(LINKED, null, Key.get(clazz)); } static <T> BindResult<T> linked(Key<? extends T> key) { return new BindResult<T>(LINKED, null, key); } static <T> BindResult<T> providerInstance(T t) { return new BindResult<T>(PROVIDER_INSTANCE, t, null); } static <T> BindResult<T> providerKey(Key<T> key) { return new BindResult<T>(PROVIDER_KEY, null, key); } /** The kind of binding. */ static enum BindType { INSTANCE, LINKED, PROVIDER_INSTANCE, PROVIDER_KEY } /** The result of the binding. */ static class BindResult<T> { private final BindType type; private final Key<?> key; private final T instance; private BindResult(BindType type, T instance, Key<?> key) { this.type = type; this.instance = instance; this.key = key; } @Override public String toString() { switch (type) { case INSTANCE: return "instance[" + instance + "]"; case LINKED: return "linkedKey[" + key + "]"; case PROVIDER_INSTANCE: return "providerInstance[" + instance + "]"; case PROVIDER_KEY: return "providerKey[" + key + "]"; } return null; } } private static class Visitor<T> extends DefaultBindingTargetVisitor<T, Object> implements MultibindingsTargetVisitor<T, Object> { @Override public Object visit(MultibinderBinding<? extends T> multibinding) { return multibinding; } @Override public Object visit(MapBinderBinding<? extends T> mapbinding) { return mapbinding; } @Override public Object visit(OptionalBinderBinding<? extends T> optionalbinding) { return optionalbinding; } } }