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