diff options
author | lukes <lukes@google.com> | 2014-09-11 07:44:44 -0700 |
---|---|---|
committer | Sam Berlin <sameb@google.com> | 2014-09-24 14:42:10 -0400 |
commit | 43c3b683c900091c2a695c642e87728758bd6963 (patch) | |
tree | b22603bfc5c4fac572ae9cc182cffadb2caedf4d /extensions | |
parent | 117b2a6bfe7fe6a59578ef10626036ee5c4744dc (diff) | |
download | guice-43c3b683c900091c2a695c642e87728758bd6963.tar.gz |
Add a lazy option to @Bind.
Often I want to @Bind SettableProvider<Foo> provider, but i can't because
BoundFieldModule doesn't recognize it. I can work around via a custom provider
that just reads a class field, but i figured it would probably make more sense
just to add this feature to @Bind directly.
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=75291509
Diffstat (limited to 'extensions')
3 files changed, 93 insertions, 26 deletions
diff --git a/extensions/testlib/src/com/google/inject/testing/fieldbinder/Bind.java b/extensions/testlib/src/com/google/inject/testing/fieldbinder/Bind.java index 948b7112..6c75fc79 100644 --- a/extensions/testlib/src/com/google/inject/testing/fieldbinder/Bind.java +++ b/extensions/testlib/src/com/google/inject/testing/fieldbinder/Bind.java @@ -37,4 +37,12 @@ public @interface Bind { * rather than to the field's actual type. */ Class<?> to() default Bind.class; + + /** + * If true, {@link BoundFieldModule} will delay retrieving the field's value until injection time + * rather than eagerly fetching it at configure time. + * + * <p>This option is not supported with Provider valued fields. + */ + boolean lazy() default false; } diff --git a/extensions/testlib/src/com/google/inject/testing/fieldbinder/BoundFieldModule.java b/extensions/testlib/src/com/google/inject/testing/fieldbinder/BoundFieldModule.java index 7943e89c..c280848d 100644 --- a/extensions/testlib/src/com/google/inject/testing/fieldbinder/BoundFieldModule.java +++ b/extensions/testlib/src/com/google/inject/testing/fieldbinder/BoundFieldModule.java @@ -71,6 +71,9 @@ import java.lang.reflect.Type; * public class TestFoo { * // bind(new TypeLiteral{@code <List<Object>>}() {}).toInstance(listOfObjects); * {@literal @}Bind private List{@code <Object>} listOfObjects = Lists.of(); + * + * // bind(String.class).toProvider(new Provider() { public String get() { return userName; }}); + * {@literal @}Bind(lazy = true) private String userName; * * // bind(SuperClass.class).toInstance(aSubClass); * {@literal @}Bind(to = SuperClass.class) private SubClass aSubClass = new SubClass(); @@ -113,8 +116,11 @@ public final class BoundFieldModule implements Module { } private static class BoundFieldException extends RuntimeException { - BoundFieldException(String message) { - super(message); + private final Message message; + + BoundFieldException(Message message) { + super(message.getMessage()); + this.message = message; } } @@ -148,7 +154,7 @@ public final class BoundFieldModule implements Module { * {@link Number}, and {@code @Bind(to = Object.class) Provider<Number> one = new Integer(1);} * will be {@link Number}. * - * @see getNaturalFieldType + * @see #getNaturalFieldType */ final Optional<TypeLiteral<?>> naturalType; @@ -173,7 +179,7 @@ public final class BoundFieldModule implements Module { if (bindClass == Bind.class) { Preconditions.checkState(naturalType != null); if (!this.naturalType.isPresent()) { - addErrorAndThrow( + throwBoundFieldException( field, "Non parameterized Provider fields must have an explicit " + "binding class via @Bind(to = Foo.class)"); @@ -240,7 +246,7 @@ public final class BoundFieldModule implements Module { return Optional.absent(); } if (hasInject(field)) { - addErrorAndThrow( + throwBoundFieldException( field, "Fields annotated with both @Bind and @Inject are illegal."); } @@ -282,12 +288,12 @@ public final class BoundFieldModule implements Module { return com.google.inject.Provider.class == clazz || javax.inject.Provider.class == clazz; } - private void bindField(BoundFieldInfo fieldInfo) { + private void bindField(final BoundFieldInfo fieldInfo) { if (fieldInfo.naturalType.isPresent()) { Class<?> naturalRawType = fieldInfo.naturalType.get().getRawType(); Class<?> boundRawType = fieldInfo.boundType.getRawType(); if (!boundRawType.isAssignableFrom(naturalRawType)) { - addErrorAndThrow( + throwBoundFieldException( fieldInfo.field, "Requested binding type \"%s\" is not assignable from field binding type \"%s\"", boundRawType.getName(), @@ -307,37 +313,46 @@ public final class BoundFieldModule implements Module { @SuppressWarnings("unchecked") AnnotatedBindingBuilder<Object> binderUnsafe = (AnnotatedBindingBuilder<Object>) binder; - Object fieldValue = fieldInfo.getValue(); - - if (fieldValue == null) { - addErrorAndThrow( - fieldInfo.field, - "Binding to null values is not allowed. " - + "Use Providers.of(null) if this is your intended behavior.", - fieldInfo.field.getName()); - } - if (isTransparentProvider(fieldInfo.type.getRawType())) { + if (fieldInfo.bindAnnotation.lazy()) { + // We don't support this because it is confusing about when values are captured. + throwBoundFieldException(fieldInfo.field, + "'lazy' is incompatible with Provider valued fields"); + } // This is safe because we checked that the field's type is Provider above. @SuppressWarnings("unchecked") - Provider<?> fieldValueUnsafe = (Provider<?>) fieldValue; - + Provider<?> fieldValueUnsafe = (Provider<?>) getFieldValue(fieldInfo); binderUnsafe.toProvider(fieldValueUnsafe); + } else if (fieldInfo.bindAnnotation.lazy()) { + binderUnsafe.toProvider(new Provider<Object>() { + @Override public Object get() { + return getFieldValue(fieldInfo); + } + }); } else { - binderUnsafe.toInstance(fieldValue); + binderUnsafe.toInstance(getFieldValue(fieldInfo)); } } - private void addErrorAndThrow(Field field, String format, Object... args) { + private Object getFieldValue(final BoundFieldInfo fieldInfo) { + Object fieldValue = fieldInfo.getValue(); + if (fieldValue == null) { + throwBoundFieldException( + fieldInfo.field, + "Binding to null values is not allowed. " + + "Use Providers.of(null) if this is your intended behavior.", + fieldInfo.field.getName()); + } + return fieldValue; + } + + private void throwBoundFieldException(Field field, String format, Object... args) { Preconditions.checkNotNull(binder); String source = String.format( "%s field %s", field.getDeclaringClass().getName(), field.getName()); - Message messageObj = new Message(source, String.format(format, args)); - - binder.addError(messageObj); - throw new BoundFieldException(messageObj.getMessage()); + throw new BoundFieldException(new Message(source, String.format(format, args))); } @Override @@ -355,7 +370,8 @@ public final class BoundFieldModule implements Module { bindField(fieldInfoOpt.get()); } } catch (BoundFieldException e) { - // addErrorAndThrow already called addError, so do nothing + // keep going to try to collect as many errors as possible + binder.addError(e.message); } } currentClassType = diff --git a/extensions/testlib/test/com/google/inject/testing/fieldbinder/BoundFieldModuleTest.java b/extensions/testlib/test/com/google/inject/testing/fieldbinder/BoundFieldModuleTest.java index 768bf3ee..2db12153 100644 --- a/extensions/testlib/test/com/google/inject/testing/fieldbinder/BoundFieldModuleTest.java +++ b/extensions/testlib/test/com/google/inject/testing/fieldbinder/BoundFieldModuleTest.java @@ -27,8 +27,10 @@ import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Provider; +import com.google.inject.ProvisionException; import com.google.inject.name.Named; import com.google.inject.name.Names; +import com.google.inject.util.Providers; import junit.framework.TestCase; @@ -686,4 +688,45 @@ public class BoundFieldModuleTest extends TestCase { assertEquals(testValue1, injector.getInstance(Integer.class)); assertEquals(testValue2, injector.getInstance(Number.class)); } + + static final class LazyClass { + @Bind(lazy = true) Integer foo = 1; + } + + public void testFieldBound_lazy() { + LazyClass asProvider = new LazyClass(); + Injector injector = Guice.createInjector(BoundFieldModule.of(asProvider)); + assertEquals(1, injector.getInstance(Integer.class).intValue()); + asProvider.foo++; + assertEquals(2, injector.getInstance(Integer.class).intValue()); + } + + public void testFieldBound_lazy_rejectNull() { + LazyClass asProvider = new LazyClass(); + Injector injector = Guice.createInjector(BoundFieldModule.of(asProvider)); + assertEquals(1, injector.getInstance(Integer.class).intValue()); + asProvider.foo = null; + try { + injector.getInstance(Integer.class); + fail(); + } catch (ProvisionException e) { + assertContains(e.getMessage(), + "Binding to null values is not allowed. " + + "Use Providers.of(null) if this is your intended behavior."); + } + } + + static final class LazyProviderClass { + @Bind(lazy = true) Provider<Integer> foo = Providers.of(null); + } + + public void testFieldBoundAsProvider_rejectProvider() { + LazyProviderClass asProvider = new LazyProviderClass(); + try { + Guice.createInjector(BoundFieldModule.of(asProvider)); + fail(); + } catch (CreationException e) { + assertContains(e.getMessage(), "'lazy' is incompatible with Provider valued fields"); + } + } } |