/* * Grapht, an open source dependency injector. * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt) * Copyright 2010-2014 Regents of the University of Minnesota * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.grouplens.grapht; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.SetMultimap; import org.grouplens.grapht.BindingFunctionBuilder.RuleSet; import org.grouplens.grapht.solver.BindRule; import org.grouplens.grapht.solver.BindRules; import org.grouplens.grapht.solver.RuleBasedBindingFunction; import org.grouplens.grapht.reflect.Satisfactions; import org.grouplens.grapht.context.ContextElements; import org.grouplens.grapht.context.ContextMatcher; import org.grouplens.grapht.context.ContextPattern; import org.grouplens.grapht.reflect.Qualifiers; import org.grouplens.grapht.reflect.internal.types.*; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import javax.inject.Provider; import java.io.InputStream; import java.io.OutputStream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class BindingFunctionBuilderTest { @Test public void testCachePolicy() throws Exception { doCachePolicyTest(CachePolicy.MEMOIZE); doCachePolicyTest(CachePolicy.NEW_INSTANCE); doCachePolicyTest(CachePolicy.NO_PREFERENCE); } private void doCachePolicyTest(CachePolicy expectedPolicy) throws Exception { BindingFunctionBuilder builder = new BindingFunctionBuilder(false); if (expectedPolicy.equals(CachePolicy.MEMOIZE)) { builder.getRootContext().bind(InterfaceA.class).shared().to(TypeA.class); } else if (expectedPolicy.equals(CachePolicy.NEW_INSTANCE)) { builder.getRootContext().bind(InterfaceA.class).unshared().to(TypeA.class); } else { builder.getRootContext().bind(InterfaceA.class).to(TypeA.class); } // expected ListMultimap<ContextMatcher,BindRule> expected = ArrayListMultimap.create(); expected.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeA.class), expectedPolicy, false)); assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); } @Test public void testBindToType() throws Exception { // Test that the fluent api creates type-to-type bind rules in // the root context BindingFunctionBuilder builder = new BindingFunctionBuilder(false); builder.getRootContext().bind(InterfaceA.class).to(TypeA.class); // expected ListMultimap<ContextMatcher,BindRule> expected = ArrayListMultimap.create(); expected.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeA.class), CachePolicy.NO_PREFERENCE, false)); assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); } @Test public void testBindToInstance() throws Exception { // Test that the fluent api creates type-to-instance bind rules // in the root context BindingFunctionBuilder builder = new BindingFunctionBuilder(false); TypeA a = new TypeA(); builder.getRootContext().bind(InterfaceA.class).to(a); // expected ListMultimap<ContextMatcher,BindRule> expected = ArrayListMultimap.create(); expected.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.instance(a), CachePolicy.NO_PREFERENCE, true)); assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); } @Test public void testBindToProviderType() throws Exception { // Test that the fluent api creates type-to-provider type bind rules // in the root context BindingFunctionBuilder builder = new BindingFunctionBuilder(false); builder.getRootContext().bind(InterfaceA.class).toProvider(ProviderA.class); // expected ListMultimap<ContextMatcher,BindRule> expected = ArrayListMultimap.create(); expected.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.providerType(ProviderA.class), CachePolicy.NO_PREFERENCE, true)); assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); } @Test public void testBindToProviderInstance() throws Exception { // Test that the fluent api creates type-to-provider instance bind rules // in the root context BindingFunctionBuilder builder = new BindingFunctionBuilder(false); ProviderA pa = new ProviderA(); builder.getRootContext().bind(InterfaceA.class).toProvider(pa); // expected ListMultimap<ContextMatcher,BindRule> expected = ArrayListMultimap.create(); expected.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.providerInstance(pa), CachePolicy.NO_PREFERENCE, true)); assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); } @Test public void testBindToSatisfaction() throws Exception { // Test that the fluent api creates type-to-type bind rules in // the root context BindingFunctionBuilder builder = new BindingFunctionBuilder(false); builder.getRootContext().bind(InterfaceA.class).toSatisfaction(Satisfactions.type(TypeA.class)); // expected ListMultimap<ContextMatcher,BindRule> expected = ArrayListMultimap.create(); expected.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeA.class), CachePolicy.NO_PREFERENCE, true)); assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); } @SuppressWarnings("unchecked") @Test public void testBindToWrongProvider() throws Exception { // Test that we get an exception when binding to a provider of an incompatible type // generics prevent this, but groovy bypasses it BindingFunctionBuilder builder = new BindingFunctionBuilder(false); try { builder.getRootContext() .bind((Class) InterfaceA.class) .toProvider(ProviderC.class); fail("binding to incompatible provider should throw exception"); } catch (InvalidBindingException e) { /* expected */ } } @SuppressWarnings("unchecked") @Test public void testBindToBadProvider() throws Exception { // Test that we get an exception when binding to a provider of an overly generic type BindingFunctionBuilder builder = new BindingFunctionBuilder(false); try { builder.getRootContext() .bind((Class) InputStream.class) .toProvider(new InstanceProvider("foo")); fail("binding to bad provider should throw exception"); } catch (InvalidBindingException e) { /* expected */ } } @Test public void testInjectorContextSpecificBindRules() throws Exception { // Test that using contexts with the fluent api properly restricts // created bind rules BindingFunctionBuilder builder = new BindingFunctionBuilder(false); builder.getRootContext().bind(InterfaceA.class).to(TypeA.class); builder.getRootContext().in(TypeC.class).bind(InterfaceA.class).to(TypeB.class); builder.getRootContext().in(RoleD.class, TypeC.class).bind(InterfaceB.class).to(TypeB.class); // expected ListMultimap<ContextMatcher, BindRule> expected = ArrayListMultimap.create(); expected.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeA.class), CachePolicy.NO_PREFERENCE, false)); expected.put(ContextPattern.subsequence(ContextElements.matchType(TypeC.class, Qualifiers.matchDefault())), BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeB.class), CachePolicy.NO_PREFERENCE, false)); expected.put(ContextPattern.subsequence(ContextElements.matchType(TypeC.class, Qualifiers.match(RoleD.class))), BindRules.toSatisfaction(InterfaceB.class, Qualifiers.matchDefault(), Satisfactions.type(TypeB.class), CachePolicy.NO_PREFERENCE, false)); assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); } @Test public void testFinalBindRule() throws Exception { // Test that type-to-type bind rules are properly terminated BindingFunctionBuilder builder = new BindingFunctionBuilder(false); builder.getRootContext().bind(InterfaceA.class).to(TypeA.class, false); // expected ListMultimap<ContextMatcher, BindRule> expected = ArrayListMultimap.create(); expected.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeA.class), CachePolicy.NO_PREFERENCE, true)); assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); } @Test public void testAnnotatedBindings() throws Exception { // Test that bind rules properly record the qualifier they're bound with BindingFunctionBuilder builder = new BindingFunctionBuilder(false); builder.getRootContext().bind(InterfaceA.class).withQualifier(RoleD.class).to(TypeA.class); // expected ListMultimap<ContextMatcher, BindRule> expected = ArrayListMultimap.create(); expected.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceA.class, Qualifiers.match(RoleD.class), Satisfactions.type(TypeA.class), CachePolicy.NO_PREFERENCE, false)); assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); } @Test public void testAnyQualifierBindings() throws Exception { // Test that bind rules properly record the qualifier they're bound with BindingFunctionBuilder builder = new BindingFunctionBuilder(false); builder.getRootContext().bind(InterfaceA.class).withAnyQualifier().to(TypeA.class); // expected ListMultimap<ContextMatcher, BindRule> expected = ArrayListMultimap.create(); expected.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchAny(), Satisfactions.type(TypeA.class), CachePolicy.NO_PREFERENCE, false)); assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); } @Test public void testNamedBindings() throws Exception { // Test that bind rules properly record the name they're bound with BindingFunctionBuilder builder = new BindingFunctionBuilder(false); builder.getRootContext().bind(String.class).withQualifier(Names.named("test1")).to("hello world"); // expected ListMultimap<ContextMatcher, BindRule> expected = ArrayListMultimap.create(); expected.put(ContextPattern.any(), BindRules.toSatisfaction(String.class, Qualifiers.match(Names.named("test1")), Satisfactions.instance("hello world"), CachePolicy.NO_PREFERENCE, true)); assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); } @Test public void testBindRuleGeneration() throws Exception { // Test that bind rules are properly generated BindingFunctionBuilder builder = new BindingFunctionBuilder(true); builder.getRootContext().bind(TypeA.class).to(TypeBp.class); // expected ListMultimap<ContextMatcher, BindRule> explicit = ArrayListMultimap.create(); explicit.put(ContextPattern.any(), BindRules.toSatisfaction(TypeA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false)); ListMultimap<ContextMatcher, BindRule> superTypes = ArrayListMultimap.create(); superTypes.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false)); ListMultimap<ContextMatcher, BindRule> interTypes = ArrayListMultimap.create(); ContextMatcher m = ContextPattern.any(); interTypes.put(m, BindRules.toSatisfaction(TypeB.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false)); interTypes.put(m, BindRules.toSatisfaction(TypeBp.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false)); assertEqualBindings(explicit, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); assertEqualBindings(superTypes, ((RuleBasedBindingFunction) builder.build(RuleSet.SUPER_TYPES)).getRules()); assertEqualBindings(interTypes, ((RuleBasedBindingFunction) builder.build(RuleSet.INTERMEDIATE_TYPES)).getRules()); } @Test public void testBindRuleGenerationExcludesDefault() throws Exception { // Test that bind rules are properly generated, and that // customized default types are ignored BindingFunctionBuilder builder = new BindingFunctionBuilder(true); builder.addDefaultExclusion(TypeA.class); // this causes TypeA and InterfaceA to be excluded builder.getRootContext().bind(TypeB.class).to(TypeBp.class); // expected ListMultimap<ContextMatcher, BindRule> explicit = ArrayListMultimap.create(); explicit.put(ContextPattern.any(), BindRules.toSatisfaction(TypeB.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false)); ListMultimap<ContextMatcher, BindRule> interTypes = ArrayListMultimap.create(); interTypes.put(ContextPattern.any(), BindRules.toSatisfaction(TypeBp.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false)); ListMultimap<ContextMatcher, BindRule> superTypes = ArrayListMultimap.create(); superTypes.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceB.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false)); assertEqualBindings(explicit, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); assertEqualBindings(superTypes, ((RuleBasedBindingFunction) builder.build(RuleSet.SUPER_TYPES)).getRules()); assertEqualBindings(interTypes, ((RuleBasedBindingFunction) builder.build(RuleSet.INTERMEDIATE_TYPES)).getRules()); } @Test public void testBindRuleGenerationWithBindingExclude() throws Exception { // Test that bind rules are properly generated, taking into // account per-binding exclusions BindingFunctionBuilder builder = new BindingFunctionBuilder(true); builder.getRootContext().bind(TypeB.class).exclude(TypeA.class).to(TypeBp.class); // expected ListMultimap<ContextMatcher, BindRule> explicit = ArrayListMultimap.create(); explicit.put(ContextPattern.any(), BindRules.toSatisfaction(TypeB.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false)); ListMultimap<ContextMatcher, BindRule> interTypes = ArrayListMultimap.create(); interTypes.put(ContextPattern.any(), BindRules.toSatisfaction(TypeBp.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false)); ListMultimap<ContextMatcher, BindRule> superTypes = ArrayListMultimap.create(); superTypes.put(ContextPattern.any(), BindRules.toSatisfaction(InterfaceB.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false)); assertEqualBindings(explicit, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules()); assertEqualBindings(superTypes, ((RuleBasedBindingFunction) builder.build(RuleSet.SUPER_TYPES)).getRules()); assertEqualBindings(interTypes, ((RuleBasedBindingFunction) builder.build(RuleSet.INTERMEDIATE_TYPES)).getRules()); } @SuppressWarnings("unchecked") @Test public void testRejectInvalidBinding() { BindingFunctionBuilder builder = new BindingFunctionBuilder(true); // need to go to raw types so we don't get type-check errors try { builder.getRootContext().bind((Class) OutputStream.class).to(String.class); fail("binding should have thrown an exception"); } catch (InvalidBindingException e) { /* no-op */ } } @SuppressWarnings("unchecked") @Test public void testRejectInvalidInstanceBinding() { BindingFunctionBuilder builder = new BindingFunctionBuilder(true); // need to go to raw types so we don't get type-check errors try { builder.getRootContext().bind((Class) OutputStream.class).to("wombat"); fail("binding should have thrown an exception"); } catch (InvalidBindingException e) { /* no-op */ } } private void assertEqualBindings(ListMultimap<ContextMatcher, BindRule> expected, ListMultimap<ContextMatcher, BindRule> actual) { // This special assert is needed because the collection interface doesn't specify // equality, but we want it to behave like set equality Assert.assertEquals(expected.size(), actual.size()); SetMultimap eset = HashMultimap.create(expected); SetMultimap aset = HashMultimap.create(actual); assertEquals(eset, aset); } // TypeBp is a TypeB, TypeA, InterfaceB, and InterfaceA public static class TypeBp extends TypeB { } public static class InstanceProvider<T> implements Provider<T> { private final T instance; public InstanceProvider(T obj) { instance = obj; } @Override public T get() { return instance; } } }