1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.directwebremoting.guice;
17
18 import com.google.inject.Binder;
19 import com.google.inject.Inject;
20 import com.google.inject.Injector;
21 import com.google.inject.Key;
22 import com.google.inject.Provider;
23
24 import java.lang.reflect.Constructor;
25 import java.lang.reflect.Method;
26 import java.lang.reflect.InvocationTargetException;
27 import static java.lang.reflect.Modifier.isStatic;
28
29 /**
30 * Binding utilities for creating ad hoc providers. These come in two flavors,
31 * constructor and factory method. In both cases, you can specify additional
32 * methods to inject after creation by chaining calls to
33 * {@link BindingProvider#injecting injecting(methodName, paramKeys)}.
34 * Each method has two variants, one with a {@code Binder} parameter and one
35 * without. The former reports construction errors via the binder (so more than
36 * one error can be reported), the latter throws a runtime exception immediately,
37 * preventing further error reporting.
38 *
39 * <p> Some examples:
40 * <pre>
41 * bind(SomeInterface.class)
42 * .toProvider(
43 * .fromConstructor(LegacyImpl.class,
44 * Key.get(String.class, MyAnnotation.class))
45 * .injecting("configure", Key.get(Configuration.class)));
46 * </pre>
47 * This binds {@code SomeInterface} to a provider that uses the
48 * {@code LegacyImpl} constructor with a single String parameter.
49 * That parameter is injected as if it had been marked with {@code @MyAnnotation}.
50 * It also calls the {@code LegacyImpl.configure} method with an injected
51 * instance of {@code Configuration}.
52 * <pre>
53 * bind(SomeInterface.class)
54 * .toProvider(
55 * .fromFactoryMethod(SomeInterface.class,
56 * LegacyFactory.class, "newSomeInterface",
57 * Key.get(String.class, MyAnnotation.class)));
58 * </pre>
59 * This binds {@code SomeInterface} to a provider that calls a factory method
60 * {@code newSomeInterface} of {@code LegacyFactory} with a single string parameter,
61 * which is injected as if it were marked with {@code @MyAnnotation}. If the
62 * method is not static, an instance of {@code LegacyFactory} is created by injection
63 * and used to call the method.
64 * @author Tim Peierls [tim at peierls dot net]
65 */
66 public class BindUtil
67 {
68 /**
69 * For fluent-style decoration with one or more method bindings when
70 * using {@link #fromConstructor}.
71 */
72 public interface BindingProvider<T> extends Provider<T>
73 {
74 /**
75 * Adds injection of a method defined by the given name and parameter
76 * types (specified as Guice keys) to this provider.
77 */
78 BindingProvider<T> injecting(String methodName, Key... paramKeys);
79 }
80
81 /**
82 * Creates a chainable provider that constructs an instance of the
83 * given type given a list of constructor parameter types, specified
84 * as Guice keys. Construction errors are thrown immediately.
85 */
86 public static <T> BindingProvider<T> fromConstructor(Class<T> type, final Key... keys)
87 {
88 return new ConstructorBindingProvider<T>(null, type, keys);
89 }
90
91 /**
92 * Creates a chainable provider that constructs an instance of the
93 * given type given a list of constructor parameter types, specified
94 * as Guice keys. Construction errors are passed to {@code binder}
95 * to be thrown at the end of all binding.
96 */
97 public static <T> BindingProvider<T> fromConstructor(Binder binder, Class<T> type, final Key... keys)
98 {
99 return new ConstructorBindingProvider<T>(binder, type, keys);
100 }
101
102 /**
103 * Creates a chainable provider that constructs an instance of {@code providedType}
104 * using a factory method defined by {@code factoryType}, {@code methodName},
105 * and a list of method parameter types specified as Guice keys. If the
106 * method is not static an instance of the factory type is created and injected.
107 * Construction errors are thrown immediately.
108 */
109 public static <T> BindingProvider<T> fromFactoryMethod(
110 Class<T> providedType, Class<?> factoryType, String methodName, final Key... keys)
111 {
112 return new FactoryMethodBindingProvider<T>(
113 null, providedType, Key.get(factoryType), methodName, keys);
114 }
115
116 /**
117 * Creates a chainable provider that constructs an instance of {@code providedType}
118 * using a factory method defined by {@code factoryType}, {@code methodName},
119 * and a list of method parameter types specified as Guice keys. If the
120 * method is not static an instance of the factory type is created and injected.
121 * Construction errors are passed to {@code binder} to be thrown at the end
122 * of all binding.
123 */
124 public static <T> BindingProvider<T> fromFactoryMethod(
125 Binder binder, Class<T> providedType,
126 Class<?> factoryType, String methodName, final Key... keys)
127 {
128 return new FactoryMethodBindingProvider<T>(
129 binder, providedType, Key.get(factoryType), methodName, keys);
130 }
131
132 /**
133 * Creates a chainable provider that constructs an instance of {@code providedType} by
134 * calling method {@code methodName} of the type in {@code factoryKey} with
135 * method parameter types specified as Guice keys. If the method is not static
136 * an instance is created and injected using the factory key.
137 * Construction errors are thrown immediately.
138 */
139 public static <T> BindingProvider<T> fromFactoryMethod(
140 Class<T> providedType, Key<?> factoryKey, String methodName, final Key... keys)
141 {
142 return new FactoryMethodBindingProvider<T>(
143 null, providedType, factoryKey, methodName, keys);
144 }
145
146 /**
147 * Creates a chainable provider that constructs an instance of {@code providedType} by
148 * calling method {@code methodName} of the type in {@code factoryKey} with
149 * method parameter types specified as Guice keys. If the method is not static
150 * an instance is created and injected using the factory key.
151 * Construction errors are passed to {@code binder} to be thrown at the end
152 * of all binding.
153 */
154 public static <T> BindingProvider<T> fromFactoryMethod(
155 Binder binder, Class<T> providedType,
156 Key<?> factoryKey, String methodName, final Key... keys)
157 {
158 return new FactoryMethodBindingProvider<T>(
159 binder, providedType, factoryKey, methodName, keys);
160 }
161
162
163
164
165
166
167 private static abstract class AbstractBindingProvider<T> implements BindingProvider<T>
168 {
169 protected AbstractBindingProvider(Binder binder, Class<T> type, Key... keys)
170 {
171 this.binder = binder;
172 this.type = type;
173 this.keys = keys;
174 }
175
176 public final T get()
177 {
178 return get(theInjector);
179 }
180
181 protected abstract T get(Injector injector);
182
183 public final BindingProvider<T> injecting(String methodName, Key... paramKeys)
184 {
185 return new MethodBindingProvider(this, type, methodName, paramKeys);
186 }
187
188 protected final Class[] getTypes()
189 {
190 Class[] types = new Class[keys.length];
191 int i = 0;
192 for (Key key : keys)
193 {
194 @SuppressWarnings("unchecked")
195 Class type = (Class) key.getTypeLiteral().getType();
196 types[i++] = type;
197 }
198 return types;
199 }
200
201 protected final Object[] getValues(Injector injector)
202 {
203 Object[] values = new Object[keys.length];
204 int i = 0;
205 for (Key key : keys)
206 {
207 Object param = injector.getInstance(key);
208 values[i++] = param;
209 }
210 return values;
211 }
212
213 protected final Binder binder;
214 protected final Class<T> type;
215 protected final Key[] keys;
216
217 /**
218 * Effectively immutable: Injected at end of bind-time,
219 * read-only thereafter, and there is (or should be) a
220 * happens-before edge between bind-time and subsequent reads.
221 */
222 @Inject private Injector theInjector;
223 }
224
225 private static class ConstructorBindingProvider<T> extends AbstractBindingProvider<T>
226 {
227 ConstructorBindingProvider(Binder binder, Class<T> type, Key... keys)
228 {
229 super(binder, type, keys);
230
231 Constructor<T> constructor = null;
232 try
233 {
234 constructor = type.getConstructor(getTypes());
235 }
236 catch (NoSuchMethodException e)
237 {
238 if (binder == null)
239 {
240 throw new IllegalArgumentException("no such constructor", e);
241 }
242 else
243 {
244 binder.addError(e);
245 }
246 }
247
248 this.constructor = constructor;
249 }
250
251 public T get(Injector injector)
252 {
253 try
254 {
255 return constructor.newInstance(getValues(injector));
256 }
257 catch (InstantiationException e)
258 {
259 throw new IllegalStateException(e);
260 }
261 catch (IllegalAccessException e)
262 {
263 throw new IllegalStateException(e);
264 }
265 catch (InvocationTargetException e)
266 {
267 throw new IllegalStateException(e);
268 }
269 }
270
271 private final Constructor<T> constructor;
272 }
273
274 private static class MethodBindingProvider<T> extends AbstractBindingProvider<T> {
275
276 MethodBindingProvider(AbstractBindingProvider<T> prev,
277 Class<T> type, String methodName, Key... keys)
278 {
279 super(prev.binder, type, keys);
280
281 Method method = null;
282 try
283 {
284 method = type.getMethod(methodName, getTypes());
285 }
286 catch (NoSuchMethodException e)
287 {
288 if (binder == null)
289 {
290 throw new IllegalArgumentException("no such method", e);
291 }
292 else
293 {
294 binder.addError(e);
295 }
296 }
297
298 this.prev = prev;
299 this.method = method;
300 }
301
302 public T get(Injector injector)
303 {
304 T target = prev.get(injector);
305 try
306 {
307 method.invoke(target, getValues(injector));
308 }
309 catch (IllegalAccessException e)
310 {
311 throw new IllegalStateException(e);
312 }
313 catch (InvocationTargetException e)
314 {
315 throw new IllegalStateException(e);
316 }
317 return target;
318 }
319
320 private final AbstractBindingProvider<T> prev;
321 private final Method method;
322 }
323
324
325 private static class FactoryMethodBindingProvider<T> extends AbstractBindingProvider<T>
326 {
327 FactoryMethodBindingProvider(Binder binder, Class<T> providedType,
328 Key<?> factoryKey, String methodName, Key... keys)
329 {
330 super(binder, providedType, keys);
331
332 Method method = null;
333 boolean isStaticFactory = false;
334 try
335 {
336 @SuppressWarnings("unchecked")
337 Class factoryType = (Class) factoryKey.getTypeLiteral().getType();
338 method = factoryType.getMethod(methodName, getTypes());
339 method.getReturnType().asSubclass(providedType);
340 }
341 catch (NoSuchMethodException e)
342 {
343 if (binder == null)
344 {
345 throw new IllegalArgumentException("no such method", e);
346 }
347 else
348 {
349 binder.addError(e);
350 }
351 }
352 catch (ClassCastException e)
353 {
354 if (binder == null)
355 {
356 throw new IllegalArgumentException("bad return type", e);
357 }
358 else
359 {
360 binder.addError(e);
361 }
362 }
363
364 this.method = method;
365 this.factoryKey = factoryKey;
366 }
367
368 public T get(Injector injector)
369 {
370 try
371 {
372 Object target = null;
373 if (!isStatic(method.getModifiers()))
374 {
375 target = injector.getInstance(factoryKey);
376 }
377 @SuppressWarnings("unchecked")
378 T result = (T) method.invoke(target, getValues(injector));
379 return result;
380 }
381 catch (IllegalAccessException e)
382 {
383 throw new IllegalStateException(e);
384 }
385 catch (InvocationTargetException e)
386 {
387 throw new IllegalStateException(e);
388 }
389 }
390
391 private final Method method;
392 private final Key<?> factoryKey;
393 }
394 }