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