/** * 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.multibindings; import static com.google.inject.multibindings.MapBinder.entryOfProviderOf; import static com.google.inject.multibindings.MapBinder.mapOf; import static com.google.inject.multibindings.MapBinder.mapOfJavaxProviderOf; import static com.google.inject.multibindings.MapBinder.mapOfProviderOf; import static com.google.inject.multibindings.MapBinder.mapOfSetOfProviderOf; import static com.google.inject.multibindings.Multibinder.collectionOfJavaxProvidersOf; import static com.google.inject.multibindings.Multibinder.collectionOfProvidersOf; import static com.google.inject.multibindings.Multibinder.setOf; import static com.google.inject.multibindings.OptionalBinder.javaOptionalOfJavaxProvider; import static com.google.inject.multibindings.OptionalBinder.javaOptionalOfProvider; import static com.google.inject.multibindings.OptionalBinder.optionalOfJavaxProvider; import static com.google.inject.multibindings.OptionalBinder.optionalOfProvider; import static com.google.inject.multibindings.SpiUtils.BindType.INSTANCE; import static com.google.inject.multibindings.SpiUtils.BindType.LINKED; import static com.google.inject.multibindings.SpiUtils.BindType.PROVIDER_INSTANCE; import static com.google.inject.multibindings.SpiUtils.BindType.PROVIDER_KEY; import static com.google.inject.multibindings.SpiUtils.VisitType.BOTH; import static com.google.inject.multibindings.SpiUtils.VisitType.INJECTOR; import static com.google.inject.multibindings.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.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.multibindings.Indexer.IndexedBinding; import com.google.inject.multibindings.MapBinder.RealMapBinder.ProviderMapEntry; import com.google.inject.multibindings.OptionalBinder.Source; 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 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 void assertMapVisitor(Key mapKey, TypeLiteral keyType, TypeLiteral valueType, Iterable 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 void mapInjectorTest(Key mapKey, TypeLiteral keyType, TypeLiteral valueType, Iterable modules, boolean allowDuplicates, int expectedMapBindings, MapResult... results) { Injector injector = Guice.createInjector(modules); Visitor visitor = new Visitor(); Binding mapBinding = injector.getBinding(mapKey); MapBinderBinding mapbinder = (MapBinderBinding)mapBinding.acceptTargetVisitor(visitor); assertNotNull(mapbinder); assertEquals(keyType, mapbinder.getKeyTypeLiteral()); assertEquals(valueType, mapbinder.getValueTypeLiteral()); assertEquals(allowDuplicates, mapbinder.permitsDuplicates()); List>> entries = Lists.newArrayList(mapbinder.getEntries()); List mapResults = Lists.newArrayList(results); assertEquals("wrong entries, expected: " + mapResults + ", but was: " + entries, mapResults.size(), entries.size()); for(MapResult result : mapResults) { Map.Entry> found = null; for(Map.Entry> 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 mapOfSet = mapKey.ofType(mapOf(keyType, setOf(valueType))); Key setOfEntry = mapKey.ofType(setOf(entryOfProviderOf(keyType, valueType))); Key collectionOfProvidersOfEntryOfProvider = mapKey.ofType(collectionOfProvidersOf(entryOfProviderOf(keyType, valueType))); Key collectionOfJavaxProvidersOfEntryOfProvider = mapKey.ofType(collectionOfJavaxProvidersOf(entryOfProviderOf(keyType, valueType))); boolean entrySetMatch = false; boolean mapJavaxProviderMatch = false; boolean mapProviderMatch = false; boolean mapSetMatch = false; boolean mapSetProviderMatch = false; boolean collectionOfProvidersOfEntryOfProviderMatch = false; boolean collectionOfJavaxProvidersOfEntryOfProviderMatch = false; List otherMapBindings = Lists.newArrayList(); List otherMatches = Lists.newArrayList(); Multimap 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(setOfEntry)) { assertTrue(contains); entrySetMatch = true; // Validate that this binding is also a MultibinderBinding. assertTrue(b.acceptTargetVisitor(visitor) instanceof MultibinderBinding); } 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: " + otherMatches, expectedSize, sizeOfOther); assertTrue(entrySetMatch); assertTrue(mapProviderMatch); assertTrue(mapJavaxProviderMatch); assertTrue(collectionOfProvidersOfEntryOfProviderMatch); assertTrue(collectionOfJavaxProvidersOfEntryOfProviderMatch); assertEquals(allowDuplicates, mapSetMatch); assertEquals(allowDuplicates, mapSetProviderMatch); assertEquals("other MapBindings found: " + otherMapBindings, expectedMapBindings, otherMapBindings.size()); } @SuppressWarnings("unchecked") private static void mapModuleTest(Key mapKey, TypeLiteral keyType, TypeLiteral valueType, Iterable modules, boolean allowDuplicates, int expectedMapBindings, MapResult... results) { Set elements = ImmutableSet.copyOf(Elements.getElements(modules)); Visitor visitor = new Visitor(); MapBinderBinding mapbinder = null; Map, 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)((Binding)binding).acceptTargetVisitor(visitor); } } } assertNotNull(mapbinder); assertEquals(keyType, mapbinder.getKeyTypeLiteral()); assertEquals(valueType, mapbinder.getValueTypeLiteral()); List mapResults = Lists.newArrayList(results); Key mapOfProvider = mapKey.ofType(mapOfProviderOf(keyType, valueType)); Key mapOfJavaxProvider = mapKey.ofType(mapOfJavaxProviderOf(keyType, valueType)); Key mapOfSetOfProvider = mapKey.ofType(mapOfSetOfProviderOf(keyType, valueType)); Key mapOfSet = mapKey.ofType(mapOf(keyType, setOf(valueType))); Key setOfEntry = mapKey.ofType(setOf(entryOfProviderOf(keyType, valueType))); Key collectionOfProvidersOfEntry = mapKey.ofType(collectionOfProvidersOf(entryOfProviderOf(keyType, valueType))); Key collectionOfJavaxProvidersOfEntry = mapKey.ofType(collectionOfJavaxProvidersOf(entryOfProviderOf(keyType, valueType))); boolean entrySetMatch = false; boolean mapProviderMatch = false; boolean mapJavaxProviderMatch = false; boolean mapSetMatch = false; boolean mapSetProviderMatch = false; boolean collectionOfProvidersOfEntryMatch = false; boolean collectionOfJavaxProvidersOfEntryMatch = false; List otherMapBindings = Lists.newArrayList(); List otherMatches = Lists.newArrayList(); List otherElements = Lists.newArrayList(); Indexer indexer = new Indexer(null); Multimap 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(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(collectionOfProvidersOfEntry)) { matched = true; assertTrue(contains); collectionOfProvidersOfEntryMatch = true; } else if(key.equals(collectionOfJavaxProvidersOfEntry)) { matched = true; assertTrue(contains); collectionOfJavaxProvidersOfEntryMatch = 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: " + otherMatches, expectedSize, otherMatchesSize); assertTrue(entrySetMatch); assertTrue(mapProviderMatch); assertTrue(mapJavaxProviderMatch); assertTrue(collectionOfProvidersOfEntryMatch); assertTrue(collectionOfJavaxProvidersOfEntryMatch); assertEquals(allowDuplicates, mapSetMatch); assertEquals(allowDuplicates, mapSetProviderMatch); 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 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 void assertSetVisitor(Key> setKey, TypeLiteral elementType, Iterable 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 void setInjectorTest(Key> setKey, TypeLiteral elementType, Iterable 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> visitor = new Visitor>(); Binding> binding = injector.getBinding(setKey); MultibinderBinding> multibinder = (MultibinderBinding>)binding.acceptTargetVisitor(visitor); assertNotNull(multibinder); assertEquals(elementType, multibinder.getElementTypeLiteral()); assertEquals(allowDuplicates, multibinder.permitsDuplicates()); List> elements = Lists.newArrayList(multibinder.getElements()); List 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 setOfElements = new HashSet(multibinder.getElements()); Set setOfIndexed = Sets.newHashSet(); Indexer indexer = new Indexer(injector); for (Binding oneBinding : setOfElements) { setOfIndexed.add(oneBinding.acceptTargetVisitor(indexer)); } List otherMultibinders = Lists.newArrayList(); List 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 void setModuleTest(Key> setKey, TypeLiteral elementType, Iterable modules, boolean allowDuplicates, int otherMultibindings, BindResult... results) { Key collectionOfProvidersKey = setKey.ofType(collectionOfProvidersOf(elementType)); Key collectionOfJavaxProvidersKey = setKey.ofType(collectionOfJavaxProvidersOf(elementType)); List bindResults = Lists.newArrayList(results); List elements = Elements.getElements(modules); Visitor visitor = new Visitor(); MultibinderBinding> multibinder = null; for(Element element : elements) { if(element instanceof Binding && ((Binding)element).getKey().equals(setKey)) { multibinder = (MultibinderBinding>)((Binding)element).acceptTargetVisitor(visitor); break; } } assertNotNull(multibinder); assertEquals(elementType, multibinder.getElementTypeLiteral()); List otherMultibinders = Lists.newArrayList(); Set otherContains = new HashSet(); List otherElements = Lists.newArrayList(); int duplicates = 0; Set 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 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 void assertOptionalVisitor(Key keyType, Iterable 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 void optionalInjectorTest(Key keyType, Iterable 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> optionalKey = keyType.ofType(OptionalBinder.optionalOf(keyType.getTypeLiteral())); Key javaOptionalKey = HAS_JAVA_OPTIONAL ? keyType.ofType(OptionalBinder.javaOptionalOf(keyType.getTypeLiteral())) : null; Injector injector = Guice.createInjector(modules); Binding> optionalBinding = injector.getBinding(optionalKey); Visitor visitor = new Visitor(); OptionalBinderBinding> optionalBinder = (OptionalBinderBinding>) 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>> optionalJavaxProviderKey = keyType.ofType(optionalOfJavaxProvider(keyType.getTypeLiteral())); Key javaOptionalJavaxProviderKey = HAS_JAVA_OPTIONAL ? keyType.ofType(javaOptionalOfJavaxProvider(keyType.getTypeLiteral())) : null; Key>> optionalProviderKey = keyType.ofType(optionalOfProvider(keyType.getTypeLiteral())); Key javaOptionalProviderKey = HAS_JAVA_OPTIONAL ? keyType.ofType(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 otherOptionalBindings = Lists.newArrayList(); List 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 void optionalModuleTest(Key keyType, Iterable 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 elements = ImmutableSet.copyOf(Elements.getElements(modules)); Map, Binding> indexed = index(elements); Key> optionalKey = keyType.ofType(OptionalBinder.optionalOf(keyType.getTypeLiteral())); Key javaOptionalKey = HAS_JAVA_OPTIONAL ? keyType.ofType(OptionalBinder.javaOptionalOf(keyType.getTypeLiteral())) : null; Visitor visitor = new Visitor(); OptionalBinderBinding> optionalBinder = null; OptionalBinderBinding javaOptionalBinder = null; Key defaultKey = null; Key actualKey = null; Binding optionalBinding = indexed.get(optionalKey); optionalBinder = (OptionalBinderBinding>) 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, Source.DEFAULT)) { defaultKey = binding.getKey(); } else if (isSourceEntry(binding, Source.ACTUAL)) { actualKey = binding.getKey(); } } } assertNotNull(optionalBinder); if (HAS_JAVA_OPTIONAL) { assertNotNull(javaOptionalBinder); } assertEquals(expectedDefault == null, defaultKey == null); assertEquals(expectedActual == null, actualKey == null); Key>> optionalJavaxProviderKey = keyType.ofType(optionalOfJavaxProvider(keyType.getTypeLiteral())); Key javaOptionalJavaxProviderKey = HAS_JAVA_OPTIONAL ? keyType.ofType(javaOptionalOfJavaxProvider(keyType.getTypeLiteral())) : null; Key>> optionalProviderKey = keyType.ofType(optionalOfProvider(keyType.getTypeLiteral())); Key javaOptionalProviderKey = HAS_JAVA_OPTIONAL ? keyType.ofType(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 otherOptionalElements = Lists.newArrayList(); List otherContains = Lists.newArrayList(); List 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, Source type) { switch(type) { case ACTUAL: return b.getKey().getAnnotation() instanceof OptionalBinder.Actual; case DEFAULT: return b.getKey().getAnnotation() instanceof OptionalBinder.Default; default: throw new IllegalStateException("invalid type: " + type); } } /** Returns the subset of elements that have keys, indexed by them. */ private static Map, Binding> index(Iterable elements) { ImmutableMap.Builder, Binding> builder = ImmutableMap.builder(); for (Element element : elements) { if (element instanceof Binding) { builder.put(((Binding) element).getKey(), (Binding) element); } } return builder.build(); } static MapResult instance(K k, V v) { return new MapResult(k, new BindResult(INSTANCE, v, null)); } static MapResult linked(K k, Class clazz) { return new MapResult(k, new BindResult(LINKED, null, Key.get(clazz))); } static MapResult linked(K k, Key key) { return new MapResult(k, new BindResult(LINKED, null, key)); } static MapResult providerInstance(K k, V v) { return new MapResult(k, new BindResult(PROVIDER_INSTANCE, v, null)); } static class MapResult { private final K k; private final BindResult v; MapResult(K k, BindResult 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 BindResult instance(T t) { return new BindResult(INSTANCE, t, null); } static BindResult linked(Class clazz) { return new BindResult(LINKED, null, Key.get(clazz)); } static BindResult linked(Key key) { return new BindResult(LINKED, null, key); } static BindResult providerInstance(T t) { return new BindResult(PROVIDER_INSTANCE, t, null); } static BindResult providerKey(Key key) { return new BindResult(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 { 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 extends DefaultBindingTargetVisitor implements MultibindingsTargetVisitor { public Object visit(MultibinderBinding multibinding) { return multibinding; } public Object visit(MapBinderBinding mapbinding) { return mapbinding; } public Object visit(OptionalBinderBinding optionalbinding) { return optionalbinding; } } }