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 org.apache.commons.lang3.ClassUtils;
23  import org.grouplens.grapht.BindingFunctionBuilder.RuleSet;
24  import org.grouplens.grapht.annotation.DefaultImplementation;
25  import org.grouplens.grapht.annotation.DefaultProvider;
26  import org.grouplens.grapht.context.ContextMatcher;
27  import org.grouplens.grapht.reflect.*;
28  import org.grouplens.grapht.solver.BindRuleBuilder;
29  import org.grouplens.grapht.solver.BindingFlag;
30  import org.grouplens.grapht.util.Preconditions;
31  import org.grouplens.grapht.util.Types;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  import javax.annotation.Nonnull;
36  import javax.annotation.Nullable;
37  import javax.inject.Provider;
38  import java.lang.annotation.Annotation;
39  import java.math.BigDecimal;
40  import java.math.BigInteger;
41  import java.util.HashMap;
42  import java.util.HashSet;
43  import java.util.Map;
44  import java.util.Map.Entry;
45  import java.util.Set;
46  
47  /**
48   * BindingImpl is the default implementation of Binding that is used by
49   * {@link BindingFunctionBuilder}.
50   * 
51   * @author <a href="http://grouplens.org">GroupLens Research</a>
52   * @param <T> The bindings source's type
53   */
54  class BindingImpl<T> implements Binding<T> {
55      private static final Logger logger = LoggerFactory.getLogger(BindingImpl.class);
56      
57      private final ContextImpl context;
58      private final Class<T> sourceType;
59      
60      private final Set<Class<?>> excludeTypes;
61      
62      private final QualifierMatcher qualifier;
63  
64      private final CachePolicy cachePolicy;
65      private final boolean fixed;
66  
67      public BindingImpl(ContextImpl context, Class<T> type) {
68          this(context, type, context.getBuilder().getDefaultExclusions(),
69               Qualifiers.matchDefault(),
70               CachePolicy.NO_PREFERENCE,
71               false);
72      }
73  
74      public BindingImpl(ContextImpl context, Class<T> type,
75                         Set<Class<?>> excludes, QualifierMatcher matcher, 
76                         CachePolicy cachePolicy, boolean fixed) {
77          this.context = context;
78          this.cachePolicy = cachePolicy;
79          sourceType = type;
80          excludeTypes = excludes;
81          qualifier = matcher;
82          this.fixed = fixed;
83      }
84  
85      @Override
86      public Binding<T> withQualifier(@Nonnull Class<? extends Annotation> qualifier) {
87          QualifierMatcher q = Qualifiers.match(qualifier);
88          return new BindingImpl<T>(context, sourceType, excludeTypes, q, cachePolicy, fixed);
89      }
90      
91      @Override
92      public Binding<T> withQualifier(@Nonnull Annotation annot) {
93          QualifierMatcher q = Qualifiers.match(annot);
94          return new BindingImpl<T>(context, sourceType, excludeTypes, q, cachePolicy, fixed);
95      }
96  
97      @Override
98      public Binding<T> withAnyQualifier() {
99          QualifierMatcher q = Qualifiers.matchAny();
100         return new BindingImpl<T>(context, sourceType, excludeTypes, q, cachePolicy, fixed);
101     }
102     
103     @Override
104     public Binding<T> unqualified() {
105         QualifierMatcher q = Qualifiers.matchNone();
106         return new BindingImpl<T>(context, sourceType, excludeTypes, q, cachePolicy, fixed);
107     }
108 
109     @Override
110     public Binding<T> exclude(@Nonnull Class<?> exclude) {
111         Preconditions.notNull("exclude type", exclude);
112         Set<Class<?>> excludes = new HashSet<Class<?>>(excludeTypes);
113         excludes.add(exclude);
114         return new BindingImpl<T>(context, sourceType, excludes, qualifier, cachePolicy, fixed);
115     }
116     
117     @Override
118     public Binding<T> shared() {
119         return new BindingImpl<T>(context, sourceType, excludeTypes, qualifier, CachePolicy.MEMOIZE, fixed);
120     }
121     
122     @Override
123     public Binding<T> unshared() {
124         return new BindingImpl<T>(context, sourceType, excludeTypes, qualifier, CachePolicy.NEW_INSTANCE, fixed);
125     }
126 
127     @Override
128     public Binding<T> fixed() {
129         return new BindingImpl<T>(context, sourceType, excludeTypes, qualifier, cachePolicy, true);
130     }
131 
132     @Override
133     public void to(@Nonnull Class<? extends T> impl, boolean chained) {
134         Preconditions.isAssignable(sourceType, impl);
135         if (logger.isWarnEnabled()) {
136             if (Types.shouldBeInstantiable(impl)
137                     && !Types.isInstantiable(impl)
138                     && impl.getAnnotation(DefaultProvider.class) == null
139                     && impl.getAnnotation(DefaultImplementation.class) == null) {
140                 logger.warn("Concrete type {} does not have an injectable or public default constructor, but probably should", impl);
141             }
142         }
143         
144         BindRuleBuilder brb = startRule();
145         if (Types.isInstantiable(impl)) {
146             brb.setSatisfaction(Satisfactions.type(impl));
147         } else {
148             brb.setImplementation(impl);
149         }
150         brb.setTerminal(!chained);
151         generateBindings(brb, impl);
152     }
153 
154     @Override
155     public void to(@Nonnull Class<? extends T> impl) {
156         to(impl, true);
157     }
158 
159     @Override
160     public void to(@Nullable T instance) {
161         if (instance == null) {
162             toNull();
163             return;
164         } else if (!(instance instanceof Number)
165                    && !ClassUtils.isPrimitiveWrapper(instance.getClass())
166                    && !sourceType.isInstance(instance)) {
167             String msg = String.format("%s is not an instance of %s",
168                                        instance, sourceType);
169             throw new InvalidBindingException(sourceType, msg);
170         }
171 
172         // Apply some type coercing if we're dealing with primitive types
173         Object coerced = coerce(instance);
174         Satisfaction s = Satisfactions.instance(coerced);
175         BindRuleBuilder brb = startRule().setSatisfaction(s);
176         generateBindings(brb, coerced.getClass());
177     }
178     
179     @Override
180     public void toProvider(@Nonnull Class<? extends Provider<? extends T>> provider) {
181         Satisfaction s = Satisfactions.providerType(provider);
182         BindRuleBuilder brb = startRule().setSatisfaction(s);
183         Class<?> provided;
184         try {
185             provided = Types.getProvidedType(provider);
186         } catch (IllegalArgumentException e) {
187             if (e.getMessage().endsWith("is generic")) {
188                 throw new InvalidBindingException(sourceType, "cannot bind to generic provider");
189             } else {
190                 throw e;
191             }
192         }
193         generateBindings(brb, provided);
194     }
195 
196     @Override
197     public void toProvider(@Nonnull Provider<? extends T> provider) {
198         Satisfaction s = Satisfactions.providerInstance(provider);
199         BindRuleBuilder brb = startRule().setSatisfaction(s);
200         Class<?> provided;
201         try {
202             provided = Types.getProvidedType(provider);
203         } catch (IllegalArgumentException e) {
204             if (e.getMessage().endsWith("is generic")) {
205                 throw new InvalidBindingException(sourceType, "cannot bind to generic provider");
206             } else {
207                 throw e;
208             }
209         }
210         generateBindings(brb, provided);
211     }
212 
213     @Override
214     public void toNull() {
215         toNull(sourceType);
216     }
217 
218     @Override
219     public void toNull(Class<? extends T> type) {
220         Satisfaction s = Satisfactions.nullOfType(type);
221         BindRuleBuilder brb = startRule().setSatisfaction(s);
222         generateBindings(brb, type);
223     }
224 
225     @Override
226     public void toSatisfaction(@Nonnull Satisfaction sat) {
227         Preconditions.notNull("satisfaction", sat);
228 
229         BindRuleBuilder brb = startRule().setSatisfaction(sat);
230         generateBindings(brb, sat.getErasedType());
231     }
232 
233     /**
234      * Generate bindings.
235      * @param brb A bind rule builder, completely populated except for its {@linkplain BindRuleBuilder#setDependencyType(Class) dependency type}.
236      * @param type The search type for {@link #generateBindPoints(Class)}.
237      */
238     private void generateBindings(BindRuleBuilder brb, Class<?> type) {
239         ContextMatcher matcher = context.getContextPattern();
240         BindingFunctionBuilder config = context.getBuilder();
241         if (config.getGenerateRules()) {
242             Map<Class<?>, RuleSet> bindPoints = generateBindPoints(type);
243             for (Entry<Class<?>, RuleSet> e: bindPoints.entrySet()) {
244                 config.addBindRule(e.getValue(), matcher, brb.setDependencyType(e.getKey()).build());
245             }
246         } else {
247             config.addBindRule(RuleSet.EXPLICIT, matcher, brb.setDependencyType(sourceType).build());
248         }
249     }
250 
251     /**
252      * Start building a bind rule.
253      * @return A bind rule builder, with the common configuration already applied.
254      */
255     private BindRuleBuilder startRule() {
256         BindRuleBuilder brb = new BindRuleBuilder();
257         brb.setQualifierMatcher(qualifier)
258            .setCachePolicy(cachePolicy)
259            .setTerminal(true);
260         if (fixed) {
261             brb.addFlag(BindingFlag.FIXED);
262         }
263         return brb;
264     }
265 
266     private Object coerce(Object in) {
267         Class<?> boxedSource = Types.box(sourceType);
268         if (Integer.class.equals(boxedSource)) {
269             // normalize to BigInteger and then cast to int
270             return Integer.valueOf(toBigInteger(in).intValue());
271         } else if (Short.class.equals(boxedSource)) {
272             // normalize to BigInteger and then cast to short
273             return Short.valueOf(toBigInteger(in).shortValue());
274         } else if (Byte.class.equals(boxedSource)) {
275             // normalize to BigInteger and then cast to byte
276             return Byte.valueOf(toBigInteger(in).byteValue());
277         } else if (Long.class.equals(boxedSource)) {
278             // normalize to BigInteger and then cast to long
279             return Long.valueOf(toBigInteger(in).longValue());
280         } else if (Float.class.equals(boxedSource)) {
281             // normalize to BigDecimal and then cast to float
282             return Float.valueOf(toBigDecimal(in).floatValue());
283         } else if (Double.class.equals(boxedSource)) {
284             // normalize to BigDecimal and then cast to double
285             return Double.valueOf(toBigDecimal(in).doubleValue());
286         } else if (BigDecimal.class.equals(boxedSource)) {
287             // normalize to BigDecimal
288             return toBigDecimal(in);
289         } else if (BigInteger.class.equals(boxedSource)) {
290             // normalize to BigInteger
291             return toBigInteger(in);
292         } else {
293             // don't perform any type coercion
294             return in;
295         }
296     }
297     
298     private BigDecimal toBigDecimal(Object in) {
299         // We assume in is a floating primitive boxed type, so its toString()
300         // converts its value to a form parsed by BigDecimal's constructor
301         return new BigDecimal(in.toString());
302     }
303     
304     private BigInteger toBigInteger(Object in) {
305         // We assume in is a discrete primitive boxed type, so its toString()
306         // converts its value to a textual form that can be parsed by 
307         // BigInteger's constructor
308         return new BigInteger(in.toString());
309     }
310     
311     private Map<Class<?>, RuleSet> generateBindPoints(Class<?> target) {
312         Map<Class<?>, RuleSet> bindPoints = new HashMap<Class<?>, RuleSet>();
313         // start the recursion up the type hierarchy, starting at the target type
314         recordTypes(Types.box(sourceType), target, bindPoints);
315         return bindPoints;
316     }
317     
318     private void recordTypes(Class<?> src, Class<?> type, Map<Class<?>, RuleSet> bindPoints) {
319         // check exclusions
320         if (type == null || excludeTypes.contains(type)) {
321             // the type is excluded, terminate recursion (this relies on Object
322             // being included in the exclude set)
323             return;
324         }
325         
326         RuleSet set;
327         if (type.equals(src)) {
328             // type is the source type, so this is the manual rule
329             set = RuleSet.EXPLICIT;
330         } else if (src.isAssignableFrom(type)) {
331             // type is a subclass of the source type, and a superclass
332             // of the target type
333             set = RuleSet.INTERMEDIATE_TYPES;
334         } else if (type.isAssignableFrom(src)) {
335             // type is a superclass of the source type, so it is also a superclass
336             // of the target type
337             set = RuleSet.SUPER_TYPES;
338         } else {
339             // type is a superclass of the target type, but not of the source type
340             // so we don't generate any bindings
341             return;
342         }
343         
344         // record the type's weight
345         bindPoints.put(type, set);
346         
347         // recurse to superclass and implemented interfaces
348         // - superclass is null for Object, interfaces, and primitives
349         // - interfaces holds implemented or extended interfaces depending on
350         //   if the type is a class or interface
351         recordTypes(src, type.getSuperclass(), bindPoints);
352         for (Class<?> i: type.getInterfaces()) {
353             recordTypes(src, i, bindPoints);
354         }
355     }
356 }