1 /*
2  * Copyright (C) 2014 Google Inc.
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 
17 package com.google.inject.testing.fieldbinder;
18 
19 import com.google.common.base.Optional;
20 import com.google.common.base.Preconditions;
21 import com.google.inject.Binder;
22 import com.google.inject.BindingAnnotation;
23 import com.google.inject.Module;
24 import com.google.inject.Provider;
25 import com.google.inject.TypeLiteral;
26 import com.google.inject.binder.AnnotatedBindingBuilder;
27 import com.google.inject.binder.LinkedBindingBuilder;
28 import com.google.inject.internal.Annotations;
29 import com.google.inject.internal.Nullability;
30 import com.google.inject.spi.Message;
31 import com.google.inject.util.Providers;
32 import java.lang.annotation.Annotation;
33 import java.lang.reflect.Field;
34 import java.lang.reflect.ParameterizedType;
35 import java.lang.reflect.Type;
36 
37 /**
38  * Automatically creates Guice bindings for fields in an object annotated with {@link Bind}.
39  *
40  * <p>This module is intended for use in tests to reduce the code needed to bind local fields
41  * (usually mocks) for injection.
42  *
43  * <p>The following rules are followed in determining how fields are bound using this module:
44  *
45  * <ul>
46  *   <li>For each {@link Bind} annotated field of an object and its superclasses, this module will
47  *       bind that field's type to that field's value at injector creation time. This includes both
48  *       instance and static fields.
49  *   <li>If {@link Bind#to} is specified, the field's value will be bound to the class specified by
50  *       {@link Bind#to} instead of the field's actual type.
51  *   <li>If {@link Bind#lazy} is true, this module will delay reading the value from the field until
52  *       injection time, allowing the field's value to be reassigned during the course of a test's
53  *       execution.
54  *   <li>If a {@link BindingAnnotation} or {@link javax.inject.Qualifier} is present on the field,
55  *       that field will be bound using that annotation via {@link
56  *       AnnotatedBindingBuilder#annotatedWith}. For example, {@code
57  *       bind(Foo.class).annotatedWith(BarAnnotation.class).toInstance(theValue)}. It is an error to
58  *       supply more than one {@link BindingAnnotation} or {@link javax.inject.Qualifier}.
59  *   <li>If the field is of type {@link Provider}, the field's value will be bound as a {@link
60  *       Provider} using {@link LinkedBindingBuilder#toProvider} to the provider's parameterized
61  *       type. For example, {@code Provider<Integer>} binds to {@link Integer}. Attempting to bind a
62  *       non-parameterized {@link Provider} without a {@link Bind#to} clause is an error.
63  * </ul>
64  *
65  * <p>Example use:
66  *
67  * <pre><code>
68  * public class TestFoo {
69  *   // bind(new TypeLiteral{@code <List<Object>>}() {}).toInstance(listOfObjects);
70  *   {@literal @}Bind private List{@code <Object>} listOfObjects = Lists.of();
71  *
72  *   // private String userName = "string_that_changes_over_time";
73  *   // bind(String.class).toProvider(new Provider() { public String get() { return userName; }});
74  *   {@literal @}Bind(lazy = true) private String userName;
75  *
76  *   // bind(SuperClass.class).toInstance(aSubClass);
77  *   {@literal @}Bind(to = SuperClass.class) private SubClass aSubClass = new SubClass();
78  *
79  *   // bind(String.class).annotatedWith(MyBindingAnnotation.class).toInstance(myString);
80  *   {@literal @}Bind
81  *   {@literal @}MyBindingAnnotation
82  *   private String myString = "hello";
83  *
84  *   // bind(Object.class).toProvider(myProvider);
85  *   {@literal @}Bind private Provider{@code <Object>} myProvider = getProvider();
86  *
87  *   {@literal @}Before public void setUp() {
88  *     Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this);
89  *   }
90  * }
91  * </code></pre>
92  *
93  * @see Bind
94  * @author eatnumber1@google.com (Russ Harmon)
95  */
96 public final class BoundFieldModule implements Module {
97   private final Object instance;
98 
99   // Note that binder is not initialized until configure() is called.
100   private Binder binder;
101 
BoundFieldModule(Object instance)102   private BoundFieldModule(Object instance) {
103     this.instance = instance;
104   }
105 
106   /**
107    * Create a BoundFieldModule which binds the {@link Bind} annotated fields of {@code instance}.
108    *
109    * @param instance the instance whose fields will be bound.
110    * @return a module which will bind the {@link Bind} annotated fields of {@code instance}.
111    */
of(Object instance)112   public static BoundFieldModule of(Object instance) {
113     return new BoundFieldModule(instance);
114   }
115 
116   private static class BoundFieldException extends RuntimeException {
117     private final Message message;
118 
BoundFieldException(Message message)119     BoundFieldException(Message message) {
120       super(message.getMessage());
121       this.message = message;
122     }
123   }
124 
125   private class BoundFieldInfo {
126     /** The field itself. */
127     final Field field;
128 
129     /**
130      * The actual type of the field.
131      *
132      * <p>For example, {@code @Bind(to = Object.class) Number one = new Integer(1);} will be {@link
133      * Number}.
134      */
135     final TypeLiteral<?> type;
136 
137     /** The {@link Bind} annotation which is present on the field. */
138     final Bind bindAnnotation;
139 
140     /**
141      * The type this field will bind to.
142      *
143      * <p>For example, {@code @Bind(to = Object.class) Number one = new Integer(1);} will be {@link
144      * Object} and {@code @Bind Number one = new Integer(1);} will be {@link Number}.
145      */
146     final TypeLiteral<?> boundType;
147 
148     /**
149      * The "natural" type of this field.
150      *
151      * <p>For example, {@code @Bind(to = Object.class) Number one = new Integer(1);} will be {@link
152      * Number}, and {@code @Bind(to = Object.class) Provider<Number> one = new Integer(1);} will be
153      * {@link Number}.
154      *
155      * @see #getNaturalFieldType
156      */
157     final Optional<TypeLiteral<?>> naturalType;
158 
BoundFieldInfo(Field field, Bind bindAnnotation, TypeLiteral<?> fieldType)159     BoundFieldInfo(Field field, Bind bindAnnotation, TypeLiteral<?> fieldType) {
160       this.field = field;
161       this.type = fieldType;
162       this.bindAnnotation = bindAnnotation;
163 
164       field.setAccessible(true);
165 
166       this.naturalType = getNaturalFieldType();
167       this.boundType = getBoundType();
168     }
169 
getBoundType()170     private TypeLiteral<?> getBoundType() {
171       Class<?> bindClass = bindAnnotation.to();
172       // Bind#to's default value is Bind.class which is used to represent that no explicit binding
173       // type is requested.
174       if (bindClass == Bind.class) {
175         Preconditions.checkState(naturalType != null);
176         if (!this.naturalType.isPresent()) {
177           throwBoundFieldException(
178               field,
179               "Non parameterized Provider fields must have an explicit "
180                   + "binding class via @Bind(to = Foo.class)");
181         }
182         return this.naturalType.get();
183       } else {
184         return TypeLiteral.get(bindClass);
185       }
186     }
187 
188     /**
189      * Retrieves the type this field binds to naturally.
190      *
191      * <p>A field's "natural" type specifically ignores the to() method on the @Bind annotation, is
192      * the parameterized type if the field's actual type is a parameterized {@link Provider}, is
193      * {@link Optional#absent()} if this field is a non-parameterized {@link Provider} and otherwise
194      * is the field's actual type.
195      *
196      * @return the type this field binds to naturally, or {@link Optional#absent()} if this field is
197      *     a non-parameterized {@link Provider}.
198      */
getNaturalFieldType()199     private Optional<TypeLiteral<?>> getNaturalFieldType() {
200       if (isTransparentProvider(type.getRawType())) {
201         Type providerType = type.getType();
202         if (providerType instanceof Class) {
203           return Optional.absent();
204         }
205         Preconditions.checkState(providerType instanceof ParameterizedType);
206         Type[] providerTypeArguments = ((ParameterizedType) providerType).getActualTypeArguments();
207         Preconditions.checkState(providerTypeArguments.length == 1);
208         return Optional.<TypeLiteral<?>>of(TypeLiteral.get(providerTypeArguments[0]));
209       } else {
210         return Optional.<TypeLiteral<?>>of(type);
211       }
212     }
213 
getValue()214     Object getValue() {
215       try {
216         return field.get(instance);
217       } catch (IllegalAccessException e) {
218         // Since we called setAccessible(true) on this field in the constructor, this is a
219         // programming error if it occurs.
220         throw new AssertionError(e);
221       }
222     }
223 
224     /** Returns whether a binding supports null values. */
allowsNull()225     boolean allowsNull() {
226       return !isTransparentProvider(type.getRawType())
227           && Nullability.allowsNull(field.getAnnotations());
228     }
229   }
230 
hasInject(Field field)231   private static boolean hasInject(Field field) {
232     return field.isAnnotationPresent(javax.inject.Inject.class)
233         || field.isAnnotationPresent(com.google.inject.Inject.class);
234   }
235 
236   /**
237    * Retrieve a {@link BoundFieldInfo}.
238    *
239    * <p>This returns a {@link BoundFieldInfo} if the field has a {@link Bind} annotation. Otherwise
240    * it returns {@link Optional#absent()}.
241    */
getBoundFieldInfo( TypeLiteral<?> containingClassType, Field field)242   private Optional<BoundFieldInfo> getBoundFieldInfo(
243       TypeLiteral<?> containingClassType, Field field) {
244     Bind bindAnnotation = field.getAnnotation(Bind.class);
245     if (bindAnnotation == null) {
246       return Optional.absent();
247     }
248     if (hasInject(field)) {
249       throwBoundFieldException(field, "Fields annotated with both @Bind and @Inject are illegal.");
250     }
251     return Optional.of(
252         new BoundFieldInfo(field, bindAnnotation, containingClassType.getFieldType(field)));
253   }
254 
verifyBindingAnnotations( Field field, AnnotatedBindingBuilder<?> annotatedBinder)255   private LinkedBindingBuilder<?> verifyBindingAnnotations(
256       Field field, AnnotatedBindingBuilder<?> annotatedBinder) {
257     LinkedBindingBuilder<?> binderRet = annotatedBinder;
258     for (Annotation annotation : field.getAnnotations()) {
259       Class<? extends Annotation> annotationType = annotation.annotationType();
260       if (Annotations.isBindingAnnotation(annotationType)) {
261         // not returning here ensures that annotatedWith will be called multiple times if this field
262         // has multiple BindingAnnotations, relying on the binder to throw an error in this case.
263         binderRet = annotatedBinder.annotatedWith(annotation);
264       }
265     }
266     return binderRet;
267   }
268 
269   /**
270    * Determines if {@code clazz} is a "transparent provider".
271    *
272    * <p>A transparent provider is a {@link com.google.inject.Provider} or {@link
273    * javax.inject.Provider} which binds to it's parameterized type when used as the argument to
274    * {@link Binder#bind}.
275    *
276    * <p>A {@link Provider} is transparent if the base class of that object is {@link Provider}. In
277    * other words, subclasses of {@link Provider} are not transparent. As a special case, if a {@link
278    * Provider} has no parameterized type but is otherwise transparent, then it is considered
279    * transparent.
280    *
281    * <p>Subclasses of {@link Provider} are not considered transparent in order to allow users to
282    * bind those subclasses directly, enabling them to inject the providers themselves.
283    */
isTransparentProvider(Class<?> clazz)284   private static boolean isTransparentProvider(Class<?> clazz) {
285     return com.google.inject.Provider.class == clazz || javax.inject.Provider.class == clazz;
286   }
287 
bindField(final BoundFieldInfo fieldInfo)288   private void bindField(final BoundFieldInfo fieldInfo) {
289     if (fieldInfo.naturalType.isPresent()) {
290       Class<?> naturalRawType = fieldInfo.naturalType.get().getRawType();
291       Class<?> boundRawType = fieldInfo.boundType.getRawType();
292       if (!boundRawType.isAssignableFrom(naturalRawType)) {
293         throwBoundFieldException(
294             fieldInfo.field,
295             "Requested binding type \"%s\" is not assignable from field binding type \"%s\"",
296             boundRawType.getName(),
297             naturalRawType.getName());
298       }
299     }
300 
301     AnnotatedBindingBuilder<?> annotatedBinder = binder.bind(fieldInfo.boundType);
302     LinkedBindingBuilder<?> binder = verifyBindingAnnotations(fieldInfo.field, annotatedBinder);
303 
304     // It's unfortunate that Field.get() just returns Object rather than the actual type (although
305     // that would be impossible) because as a result calling binder.toInstance or binder.toProvider
306     // is impossible to do without an unchecked cast. This is safe if fieldInfo.naturalType is
307     // present because compatibility is checked explicitly above, but is _unsafe_ if
308     // fieldInfo.naturalType is absent which occurrs when a non-parameterized Provider is used with
309     // @Bind(to = ...)
310     @SuppressWarnings("unchecked")
311     AnnotatedBindingBuilder<Object> binderUnsafe = (AnnotatedBindingBuilder<Object>) binder;
312 
313     if (isTransparentProvider(fieldInfo.type.getRawType())) {
314       if (fieldInfo.bindAnnotation.lazy()) {
315         binderUnsafe.toProvider(
316             new Provider<Object>() {
317               @Override
318               // @Nullable
319               public Object get() {
320                 // This is safe because we checked that the field's type is Provider above.
321                 @SuppressWarnings("unchecked")
322                 javax.inject.Provider<?> provider =
323                     (javax.inject.Provider<?>) getFieldValue(fieldInfo);
324                 return provider.get();
325               }
326             });
327       } else {
328         // This is safe because we checked that the field's type is Provider above.
329         @SuppressWarnings("unchecked")
330         javax.inject.Provider<?> fieldValueUnsafe =
331             (javax.inject.Provider<?>) getFieldValue(fieldInfo);
332         binderUnsafe.toProvider(fieldValueUnsafe);
333       }
334     } else if (fieldInfo.bindAnnotation.lazy()) {
335       binderUnsafe.toProvider(
336           new Provider<Object>() {
337             @Override
338             // @Nullable
339             public Object get() {
340               return getFieldValue(fieldInfo);
341             }
342           });
343     } else {
344       Object fieldValue = getFieldValue(fieldInfo);
345       if (fieldValue == null) {
346         binderUnsafe.toProvider(Providers.of(null));
347       } else {
348         binderUnsafe.toInstance(fieldValue);
349       }
350     }
351   }
352 
353   // @Nullable
354   /**
355    * Returns the field value to bind, throwing for non-{@code @Nullable} fields with null values,
356    * and for null "transparent providers".
357    */
getFieldValue(final BoundFieldInfo fieldInfo)358   private Object getFieldValue(final BoundFieldInfo fieldInfo) {
359     Object fieldValue = fieldInfo.getValue();
360     if (fieldValue == null && !fieldInfo.allowsNull()) {
361       if (isTransparentProvider(fieldInfo.type.getRawType())) {
362         throwBoundFieldException(
363             fieldInfo.field,
364             "Binding to null is not allowed. Use Providers.of(null) if this is your intended "
365                 + "behavior.",
366             fieldInfo.field.getName());
367       } else {
368         throwBoundFieldException(
369             fieldInfo.field,
370             "Binding to null values is only allowed for fields that are annotated @Nullable.",
371             fieldInfo.field.getName());
372       }
373     }
374     return fieldValue;
375   }
376 
throwBoundFieldException(Field field, String format, Object... args)377   private void throwBoundFieldException(Field field, String format, Object... args) {
378     Preconditions.checkNotNull(binder);
379     String source =
380         String.format("%s field %s", field.getDeclaringClass().getName(), field.getName());
381     throw new BoundFieldException(new Message(source, String.format(format, args)));
382   }
383 
384   @Override
configure(Binder binder)385   public void configure(Binder binder) {
386     binder = binder.skipSources(BoundFieldModule.class);
387     this.binder = binder;
388 
389     TypeLiteral<?> currentClassType = TypeLiteral.get(instance.getClass());
390     while (currentClassType.getRawType() != Object.class) {
391       for (Field field : currentClassType.getRawType().getDeclaredFields()) {
392         try {
393           Optional<BoundFieldInfo> fieldInfoOpt = getBoundFieldInfo(currentClassType, field);
394           if (fieldInfoOpt.isPresent()) {
395             bindField(fieldInfoOpt.get());
396           }
397         } catch (BoundFieldException e) {
398           // keep going to try to collect as many errors as possible
399           binder.addError(e.message);
400         }
401       }
402       currentClassType =
403           currentClassType.getSupertype(currentClassType.getRawType().getSuperclass());
404     }
405   }
406 }
407