View Javadoc

1   /*
2    * Copyright 2007 Tim Peierls
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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     // Implementation classes
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 }