View Javadoc

1   /*
2    * Grapht, an open source dependency injector.
3    * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
4    * Copyright 2010-2014 Regents of the University of Minnesota
5    *
6    * This program is free software; you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation; either version 2.1 of the
9    * License, or (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14   * details.
15   *
16   * You should have received a copy of the GNU General Public License along with
17   * this program; if not, write to the Free Software Foundation, Inc., 51
18   * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19   */
20  package org.grouplens.grapht;
21  
22  import com.google.common.collect.ArrayListMultimap;
23  import com.google.common.collect.HashMultimap;
24  import com.google.common.collect.ListMultimap;
25  import com.google.common.collect.SetMultimap;
26  import org.grouplens.grapht.BindingFunctionBuilder.RuleSet;
27  import org.grouplens.grapht.solver.BindRule;
28  import org.grouplens.grapht.solver.BindRules;
29  import org.grouplens.grapht.solver.RuleBasedBindingFunction;
30  import org.grouplens.grapht.reflect.Satisfactions;
31  import org.grouplens.grapht.context.ContextElements;
32  import org.grouplens.grapht.context.ContextMatcher;
33  import org.grouplens.grapht.context.ContextPattern;
34  import org.grouplens.grapht.reflect.Qualifiers;
35  import org.grouplens.grapht.reflect.internal.types.*;
36  import org.junit.Assert;
37  import org.junit.Ignore;
38  import org.junit.Test;
39  
40  import javax.inject.Provider;
41  import java.io.InputStream;
42  import java.io.OutputStream;
43  
44  import static org.junit.Assert.assertEquals;
45  import static org.junit.Assert.fail;
46  
47  public class BindingFunctionBuilderTest {
48      @Test
49      public void testCachePolicy() throws Exception {
50          doCachePolicyTest(CachePolicy.MEMOIZE);
51          doCachePolicyTest(CachePolicy.NEW_INSTANCE);
52          doCachePolicyTest(CachePolicy.NO_PREFERENCE);
53      }
54      
55      private void doCachePolicyTest(CachePolicy expectedPolicy) throws Exception {
56          BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
57          
58          if (expectedPolicy.equals(CachePolicy.MEMOIZE)) {
59              builder.getRootContext().bind(InterfaceA.class).shared().to(TypeA.class);
60          } else if (expectedPolicy.equals(CachePolicy.NEW_INSTANCE)) {
61              builder.getRootContext().bind(InterfaceA.class).unshared().to(TypeA.class);
62          } else {
63              builder.getRootContext().bind(InterfaceA.class).to(TypeA.class);
64          }
65          
66          // expected
67          ListMultimap<ContextMatcher,BindRule> expected = ArrayListMultimap.create();
68          expected.put(ContextPattern.any(),
69                       BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeA.class), expectedPolicy, false));
70          
71          assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
72      }
73      
74      @Test
75      public void testBindToType() throws Exception {
76          // Test that the fluent api creates type-to-type bind rules in 
77          // the root context
78          BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
79  
80          builder.getRootContext().bind(InterfaceA.class).to(TypeA.class);
81          
82          // expected
83          ListMultimap<ContextMatcher,BindRule> expected = ArrayListMultimap.create();
84          expected.put(ContextPattern.any(),
85                       BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeA.class), CachePolicy.NO_PREFERENCE, false));
86          
87          assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
88      }
89      
90      @Test
91      public void testBindToInstance() throws Exception {
92          // Test that the fluent api creates type-to-instance bind rules
93          // in the root context
94          BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
95  
96          TypeA a = new TypeA();
97          builder.getRootContext().bind(InterfaceA.class).to(a);
98          
99          // expected
100         ListMultimap<ContextMatcher,BindRule> expected = ArrayListMultimap.create();
101         expected.put(ContextPattern.any(),
102                      BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.instance(a), CachePolicy.NO_PREFERENCE, true));
103         
104         assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
105     }
106     
107     @Test
108     public void testBindToProviderType() throws Exception {
109         // Test that the fluent api creates type-to-provider type bind rules
110         // in the root context
111         BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
112 
113         builder.getRootContext().bind(InterfaceA.class).toProvider(ProviderA.class);
114         
115         // expected
116         ListMultimap<ContextMatcher,BindRule> expected = ArrayListMultimap.create();
117         expected.put(ContextPattern.any(),
118                      BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.providerType(ProviderA.class), CachePolicy.NO_PREFERENCE, true));
119         
120         assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
121     }
122     
123     @Test
124     public void testBindToProviderInstance() throws Exception {
125         // Test that the fluent api creates type-to-provider instance bind rules
126         // in the root context
127         BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
128 
129         ProviderA pa = new ProviderA();
130         builder.getRootContext().bind(InterfaceA.class).toProvider(pa);
131         
132         // expected
133         ListMultimap<ContextMatcher,BindRule> expected = ArrayListMultimap.create();
134         expected.put(ContextPattern.any(),
135                      BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.providerInstance(pa), CachePolicy.NO_PREFERENCE, true));
136         
137         assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
138     }
139 
140     @Test
141     public void testBindToSatisfaction() throws Exception {
142         // Test that the fluent api creates type-to-type bind rules in
143         // the root context
144         BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
145 
146         builder.getRootContext().bind(InterfaceA.class).toSatisfaction(Satisfactions.type(TypeA.class));
147 
148         // expected
149         ListMultimap<ContextMatcher,BindRule> expected = ArrayListMultimap.create();
150         expected.put(ContextPattern.any(),
151                      BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeA.class), CachePolicy.NO_PREFERENCE, true));
152 
153         assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
154     }
155 
156     @SuppressWarnings("unchecked")
157     @Test
158     public void testBindToWrongProvider() throws Exception {
159         // Test that we get an exception when binding to a provider of an incompatible type
160         // generics prevent this, but groovy bypasses it
161         BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
162         try {
163             builder.getRootContext()
164                    .bind((Class) InterfaceA.class)
165                    .toProvider(ProviderC.class);
166             fail("binding to incompatible provider should throw exception");
167         } catch (InvalidBindingException e) {
168             /* expected */
169         }
170     }
171 
172     @SuppressWarnings("unchecked")
173     @Test
174     public void testBindToBadProvider() throws Exception {
175         // Test that we get an exception when binding to a provider of an overly generic type
176         BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
177         try {
178             builder.getRootContext()
179                    .bind((Class) InputStream.class)
180                    .toProvider(new InstanceProvider("foo"));
181             fail("binding to bad provider should throw exception");
182         } catch (InvalidBindingException e) {
183             /* expected */
184         }
185     }
186     
187     @Test
188     public void testInjectorContextSpecificBindRules() throws Exception {
189         // Test that using contexts with the fluent api properly restricts
190         // created bind rules
191         BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
192 
193         builder.getRootContext().bind(InterfaceA.class).to(TypeA.class);
194         builder.getRootContext().in(TypeC.class).bind(InterfaceA.class).to(TypeB.class);
195         builder.getRootContext().in(RoleD.class, TypeC.class).bind(InterfaceB.class).to(TypeB.class);
196         
197         // expected
198         ListMultimap<ContextMatcher, BindRule> expected = ArrayListMultimap.create();
199         expected.put(ContextPattern.any(),
200                      BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeA.class), CachePolicy.NO_PREFERENCE, false));
201 
202         expected.put(ContextPattern.subsequence(ContextElements.matchType(TypeC.class, Qualifiers.matchDefault())),
203                      BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeB.class), CachePolicy.NO_PREFERENCE, false));
204         expected.put(ContextPattern.subsequence(ContextElements.matchType(TypeC.class, Qualifiers.match(RoleD.class))),
205                      BindRules.toSatisfaction(InterfaceB.class, Qualifiers.matchDefault(), Satisfactions.type(TypeB.class), CachePolicy.NO_PREFERENCE, false));
206         
207         assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
208     }
209     
210     @Test
211     public void testFinalBindRule() throws Exception {
212         // Test that type-to-type bind rules are properly terminated
213         BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
214 
215         builder.getRootContext().bind(InterfaceA.class).to(TypeA.class, false);
216         
217         // expected
218         ListMultimap<ContextMatcher, BindRule> expected = ArrayListMultimap.create();
219         expected.put(ContextPattern.any(),
220                      BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeA.class), CachePolicy.NO_PREFERENCE, true));
221         
222         assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
223     }
224     
225     @Test
226     public void testAnnotatedBindings() throws Exception {
227         // Test that bind rules properly record the qualifier they're bound with
228         BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
229 
230         builder.getRootContext().bind(InterfaceA.class).withQualifier(RoleD.class).to(TypeA.class);
231         
232         // expected
233         ListMultimap<ContextMatcher, BindRule> expected = ArrayListMultimap.create();
234         expected.put(ContextPattern.any(),
235                      BindRules.toSatisfaction(InterfaceA.class, Qualifiers.match(RoleD.class), Satisfactions.type(TypeA.class), CachePolicy.NO_PREFERENCE, false));
236         
237         assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
238     }
239 
240     @Test
241     public void testAnyQualifierBindings() throws Exception {
242         // Test that bind rules properly record the qualifier they're bound with
243         BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
244 
245         builder.getRootContext().bind(InterfaceA.class).withAnyQualifier().to(TypeA.class);
246 
247         // expected
248         ListMultimap<ContextMatcher, BindRule> expected = ArrayListMultimap.create();
249         expected.put(ContextPattern.any(),
250                      BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchAny(), Satisfactions.type(TypeA.class), CachePolicy.NO_PREFERENCE, false));
251 
252         assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
253     }
254     
255     @Test
256     public void testNamedBindings() throws Exception {
257         // Test that bind rules properly record the name they're bound with
258         BindingFunctionBuilder builder = new BindingFunctionBuilder(false);
259 
260         builder.getRootContext().bind(String.class).withQualifier(Names.named("test1")).to("hello world");
261         
262         // expected
263         ListMultimap<ContextMatcher, BindRule> expected = ArrayListMultimap.create();
264         expected.put(ContextPattern.any(),
265                      BindRules.toSatisfaction(String.class, Qualifiers.match(Names.named("test1")), Satisfactions.instance("hello world"), CachePolicy.NO_PREFERENCE, true));
266         
267         assertEqualBindings(expected, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
268     }
269     
270     @Test
271     public void testBindRuleGeneration() throws Exception {
272         // Test that bind rules are properly generated
273         BindingFunctionBuilder builder = new BindingFunctionBuilder(true);
274 
275         builder.getRootContext().bind(TypeA.class).to(TypeBp.class);
276         
277         // expected
278         ListMultimap<ContextMatcher, BindRule> explicit = ArrayListMultimap.create();
279         explicit.put(ContextPattern.any(),
280                      BindRules.toSatisfaction(TypeA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false));
281         ListMultimap<ContextMatcher, BindRule> superTypes = ArrayListMultimap.create();
282         superTypes.put(ContextPattern.any(),
283                        BindRules.toSatisfaction(InterfaceA.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false));
284         ListMultimap<ContextMatcher, BindRule> interTypes = ArrayListMultimap.create();
285         ContextMatcher m = ContextPattern.any();
286         interTypes.put(m, BindRules.toSatisfaction(TypeB.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false));
287         interTypes.put(m, BindRules.toSatisfaction(TypeBp.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false));
288         
289         assertEqualBindings(explicit, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
290         assertEqualBindings(superTypes, ((RuleBasedBindingFunction) builder.build(RuleSet.SUPER_TYPES)).getRules());
291         assertEqualBindings(interTypes, ((RuleBasedBindingFunction) builder.build(RuleSet.INTERMEDIATE_TYPES)).getRules());
292     }
293     
294     @Test
295     public void testBindRuleGenerationExcludesDefault() throws Exception {
296         // Test that bind rules are properly generated, and that
297         // customized default types are ignored
298         BindingFunctionBuilder builder = new BindingFunctionBuilder(true);
299         builder.addDefaultExclusion(TypeA.class); // this causes TypeA and InterfaceA to be excluded
300         
301         builder.getRootContext().bind(TypeB.class).to(TypeBp.class);
302         
303         // expected
304         ListMultimap<ContextMatcher, BindRule> explicit = ArrayListMultimap.create();
305         explicit.put(ContextPattern.any(),
306                      BindRules.toSatisfaction(TypeB.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false));
307         ListMultimap<ContextMatcher, BindRule> interTypes = ArrayListMultimap.create();
308         interTypes.put(ContextPattern.any(),
309                        BindRules.toSatisfaction(TypeBp.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false));
310         ListMultimap<ContextMatcher, BindRule> superTypes = ArrayListMultimap.create();
311         superTypes.put(ContextPattern.any(),
312                        BindRules.toSatisfaction(InterfaceB.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false));
313         
314         assertEqualBindings(explicit, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
315         assertEqualBindings(superTypes, ((RuleBasedBindingFunction) builder.build(RuleSet.SUPER_TYPES)).getRules());
316         assertEqualBindings(interTypes, ((RuleBasedBindingFunction) builder.build(RuleSet.INTERMEDIATE_TYPES)).getRules());
317     }
318     
319     @Test
320     public void testBindRuleGenerationWithBindingExclude() throws Exception {
321         // Test that bind rules are properly generated, taking into
322         // account per-binding exclusions
323         BindingFunctionBuilder builder = new BindingFunctionBuilder(true);
324         
325         builder.getRootContext().bind(TypeB.class).exclude(TypeA.class).to(TypeBp.class);
326         
327         // expected
328         ListMultimap<ContextMatcher, BindRule> explicit = ArrayListMultimap.create();
329         explicit.put(ContextPattern.any(),
330                      BindRules.toSatisfaction(TypeB.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false));
331         ListMultimap<ContextMatcher, BindRule> interTypes = ArrayListMultimap.create();
332         interTypes.put(ContextPattern.any(),
333                        BindRules.toSatisfaction(TypeBp.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false));
334         ListMultimap<ContextMatcher, BindRule> superTypes = ArrayListMultimap.create();
335         superTypes.put(ContextPattern.any(),
336                        BindRules.toSatisfaction(InterfaceB.class, Qualifiers.matchDefault(), Satisfactions.type(TypeBp.class), CachePolicy.NO_PREFERENCE, false));
337 
338         assertEqualBindings(explicit, ((RuleBasedBindingFunction) builder.build(RuleSet.EXPLICIT)).getRules());
339         assertEqualBindings(superTypes, ((RuleBasedBindingFunction) builder.build(RuleSet.SUPER_TYPES)).getRules());
340         assertEqualBindings(interTypes, ((RuleBasedBindingFunction) builder.build(RuleSet.INTERMEDIATE_TYPES)).getRules());
341     }
342 
343     @SuppressWarnings("unchecked")
344     @Test
345     public void testRejectInvalidBinding() {
346         BindingFunctionBuilder builder = new BindingFunctionBuilder(true);
347         // need to go to raw types so we don't get type-check errors
348         try {
349             builder.getRootContext().bind((Class) OutputStream.class).to(String.class);
350             fail("binding should have thrown an exception");
351         } catch (InvalidBindingException e) {
352             /* no-op */
353         }
354     }
355 
356     @SuppressWarnings("unchecked")
357     @Test
358     public void testRejectInvalidInstanceBinding() {
359         BindingFunctionBuilder builder = new BindingFunctionBuilder(true);
360         // need to go to raw types so we don't get type-check errors
361         try {
362             builder.getRootContext().bind((Class) OutputStream.class).to("wombat");
363             fail("binding should have thrown an exception");
364         } catch (InvalidBindingException e) {
365             /* no-op */
366         }
367     }
368     
369     private void assertEqualBindings(ListMultimap<ContextMatcher, BindRule> expected, ListMultimap<ContextMatcher, BindRule> actual) {
370         // This special assert is needed because the collection interface doesn't specify
371         // equality, but we want it to behave like set equality
372         Assert.assertEquals(expected.size(), actual.size());
373         SetMultimap eset = HashMultimap.create(expected);
374         SetMultimap aset = HashMultimap.create(actual);
375         assertEquals(eset, aset);
376     }
377     
378     // TypeBp is a TypeB, TypeA, InterfaceB, and InterfaceA
379     public static class TypeBp extends TypeB { }
380 
381     public static class InstanceProvider<T> implements Provider<T> {
382         private final T instance;
383 
384         public InstanceProvider(T obj) {
385             instance = obj;
386         }
387 
388         @Override
389         public T get() {
390             return instance;
391         }
392     }
393 }