1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
49
50
51
52
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
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
235
236
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
253
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
270 return Integer.valueOf(toBigInteger(in).intValue());
271 } else if (Short.class.equals(boxedSource)) {
272
273 return Short.valueOf(toBigInteger(in).shortValue());
274 } else if (Byte.class.equals(boxedSource)) {
275
276 return Byte.valueOf(toBigInteger(in).byteValue());
277 } else if (Long.class.equals(boxedSource)) {
278
279 return Long.valueOf(toBigInteger(in).longValue());
280 } else if (Float.class.equals(boxedSource)) {
281
282 return Float.valueOf(toBigDecimal(in).floatValue());
283 } else if (Double.class.equals(boxedSource)) {
284
285 return Double.valueOf(toBigDecimal(in).doubleValue());
286 } else if (BigDecimal.class.equals(boxedSource)) {
287
288 return toBigDecimal(in);
289 } else if (BigInteger.class.equals(boxedSource)) {
290
291 return toBigInteger(in);
292 } else {
293
294 return in;
295 }
296 }
297
298 private BigDecimal toBigDecimal(Object in) {
299
300
301 return new BigDecimal(in.toString());
302 }
303
304 private BigInteger toBigInteger(Object in) {
305
306
307
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
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
320 if (type == null || excludeTypes.contains(type)) {
321
322
323 return;
324 }
325
326 RuleSet set;
327 if (type.equals(src)) {
328
329 set = RuleSet.EXPLICIT;
330 } else if (src.isAssignableFrom(type)) {
331
332
333 set = RuleSet.INTERMEDIATE_TYPES;
334 } else if (type.isAssignableFrom(src)) {
335
336
337 set = RuleSet.SUPER_TYPES;
338 } else {
339
340
341 return;
342 }
343
344
345 bindPoints.put(type, set);
346
347
348
349
350
351 recordTypes(src, type.getSuperclass(), bindPoints);
352 for (Class<?> i: type.getInterfaces()) {
353 recordTypes(src, i, bindPoints);
354 }
355 }
356 }