PageRenderTime 3332ms CodeModel.GetById 93ms RepoModel.GetById 0ms app.codeStats 0ms

/extensions/testlib/src/com/google/inject/testing/fieldbinder/BoundFieldModule.java

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