aboutsummaryrefslogtreecommitdiff
path: root/extensions
diff options
context:
space:
mode:
authorlukes <lukes@google.com>2014-09-11 07:44:44 -0700
committerSam Berlin <sameb@google.com>2014-09-24 14:42:10 -0400
commit43c3b683c900091c2a695c642e87728758bd6963 (patch)
treeb22603bfc5c4fac572ae9cc182cffadb2caedf4d /extensions
parent117b2a6bfe7fe6a59578ef10626036ee5c4744dc (diff)
downloadguice-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')
-rw-r--r--extensions/testlib/src/com/google/inject/testing/fieldbinder/Bind.java8
-rw-r--r--extensions/testlib/src/com/google/inject/testing/fieldbinder/BoundFieldModule.java68
-rw-r--r--extensions/testlib/test/com/google/inject/testing/fieldbinder/BoundFieldModuleTest.java43
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");
+ }
+ }
}