summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2014-09-14 23:56:51 -0400
committerTavian Barnes <tavianator@tavianator.com>2014-09-14 23:58:09 -0400
commitaf52e472b4f0b00b0d00c04b3fa54a79f94b9a49 (patch)
tree353fbadd308909d15dae5461d726e8704937d274
parentbc1de44c602192e59ff66d1a5d1f1e9cdcc4a2bb (diff)
downloadsangria-af52e472b4f0b00b0d00c04b3fa54a79f94b9a49.tar.xz
lazy: Implement Lazy<T>, very much like Dagger's.
-rw-r--r--pom.xml7
-rw-r--r--sangria-lazy/pom.xml58
-rw-r--r--sangria-lazy/src/main/java/com/tavianator/sangria/lazy/Lazy.java81
-rw-r--r--sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyBinder.java273
-rw-r--r--sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyBinding.java34
-rw-r--r--sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyBindingVisitor.java37
-rw-r--r--sangria-lazy/src/main/java/com/tavianator/sangria/lazy/package-info.java25
-rw-r--r--sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazyTest.java243
8 files changed, 758 insertions, 0 deletions
diff --git a/pom.xml b/pom.xml
index 5279ece..794108c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,6 +79,12 @@
</dependency>
<dependency>
+ <groupId>com.tavianator.sangria</groupId>
+ <artifactId>sangria-lazy</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.0-beta4</version>
@@ -288,6 +294,7 @@
<module>sangria-slf4j</module>
<module>sangria-log4j</module>
<module>sangria-listbinder</module>
+ <module>sangria-lazy</module>
</modules>
<profiles>
diff --git a/sangria-lazy/pom.xml b/sangria-lazy/pom.xml
new file mode 100644
index 0000000..49416bb
--- /dev/null
+++ b/sangria-lazy/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>com.tavianator.sangria</groupId>
+ <artifactId>sangria</artifactId>
+ <version>1.2-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>sangria-lazy</artifactId>
+ <packaging>jar</packaging>
+ <name>Sangria Lazy</name>
+ <description>Lazily-loaded dependencies</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.tavianator.sangria</groupId>
+ <artifactId>sangria-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-integration</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/Lazy.java b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/Lazy.java
new file mode 100644
index 0000000..adf531c
--- /dev/null
+++ b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/Lazy.java
@@ -0,0 +1,81 @@
+/****************************************************************************
+ * Sangria *
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); *
+ * you may not use this file except in compliance with the License. *
+ * You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ ****************************************************************************/
+
+package com.tavianator.sangria.lazy;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+/**
+ * A lazily-loaded dependency. Like a {@link Provider}, calling {@link #get()} will produce an instance of {@code T}.
+ * Unlike a {@link Provider}, the same instance will be returned for every future call to {@link #get()}. Different
+ * {@code Lazy} instances are independent and will return different instances from {@link #get()}.
+ *
+ * <p>
+ * {@link Lazy} works automatically for unqualified bindings, as long as just-in-time bindings are enabled. For
+ * qualified bindings, or if explicit bindings are requred, use {@link LazyBinder}:
+ * </p>
+ *
+ * <pre>
+ * // Either separately...
+ * bind(Dependency.class)
+ * .annotatedWith(Names.named("name"))
+ * .to(RealDependency.class);
+ *
+ * LazyBinder.create(binder())
+ * .bind(Dependency.class)
+ * .annotatedWith(Names.named("name"));
+ *
+ * // ... or in one go
+ * LazyBinder.create(binder())
+ * .bind(Dependency.class)
+ * .annotatedWith(Names.named("name"))
+ * .to(RealDependency.class);
+ *
+ * ...
+ *
+ * {@literal @}Inject {@literal @}Named("name") Lazy&lt;Dependency&gt; lazy;
+ * </pre>
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+public final class Lazy<T> {
+ private final Provider<T> provider;
+ private volatile T instance = null;
+
+ @Inject
+ Lazy(Provider<T> provider) {
+ this.provider = provider;
+ }
+
+ /**
+ * @return A lazily-produced value of type {@code T}.
+ */
+ public T get() {
+ // Double-checked locking
+ if (instance == null) {
+ synchronized (this) {
+ if (instance == null) {
+ instance = provider.get();
+ }
+ }
+ }
+ return instance;
+ }
+}
diff --git a/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyBinder.java b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyBinder.java
new file mode 100644
index 0000000..26d3848
--- /dev/null
+++ b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyBinder.java
@@ -0,0 +1,273 @@
+/****************************************************************************
+ * Sangria *
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); *
+ * you may not use this file except in compliance with the License. *
+ * You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ ****************************************************************************/
+
+package com.tavianator.sangria.lazy;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+
+import javax.inject.Provider;
+
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.Scope;
+import com.google.inject.TypeLiteral;
+import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.binder.ScopedBindingBuilder;
+import com.google.inject.spi.BindingTargetVisitor;
+import com.google.inject.spi.ProviderInstanceBinding;
+import com.google.inject.spi.ProviderWithExtensionVisitor;
+import com.google.inject.util.Types;
+
+import com.tavianator.sangria.core.PotentialAnnotation;
+
+/**
+ * Binder for {@link Lazy} instances.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+public class LazyBinder {
+ private static final Class<?>[] SKIPPED_SOURCES = {
+ LazyBinder.class,
+ BindingAnnotator.class,
+ LazyBindingBuilder.class,
+ };
+
+ private final Binder binder;
+
+ private LazyBinder(Binder binder) {
+ this.binder = binder;
+ }
+
+ /**
+ * Create a {@link LazyBinder}.
+ *
+ * @param binder The {@link Binder} to use.
+ * @return A {@link LazyBinder} instance.
+ */
+ public static LazyBinder create(Binder binder) {
+ return new LazyBinder(binder.skipSources(SKIPPED_SOURCES));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> TypeLiteral<Lazy<T>> lazyOf(TypeLiteral<T> type) {
+ return (TypeLiteral<Lazy<T>>)TypeLiteral.get(Types.newParameterizedType(Lazy.class, type.getType()));
+ }
+
+ /**
+ * See the EDSL examples at {@link Lazy}.
+ */
+ public <T> AnnotatedBindingBuilder<T> bind(Class<T> type) {
+ return bind(TypeLiteral.get(type));
+ }
+
+ /**
+ * See the EDSL examples at {@link Lazy}.
+ */
+ public <T> AnnotatedBindingBuilder<T> bind(TypeLiteral<T> type) {
+ AnnotatedBindingBuilder<Lazy<T>> lazyBinding = binder.bind(lazyOf(type));
+ return new LazyBindingBuilder<>(binder, type, lazyBinding, PotentialAnnotation.none());
+ }
+
+ /**
+ * Applies an annotation to an {@link AnnotatedBindingBuilder}.
+ */
+ private static class BindingAnnotator<T> implements PotentialAnnotation.Visitor<LinkedBindingBuilder<T>> {
+ private final AnnotatedBindingBuilder<T> builder;
+
+ BindingAnnotator(AnnotatedBindingBuilder<T> builder) {
+ this.builder = builder;
+ }
+
+ @Override
+ public LinkedBindingBuilder<T> visitNoAnnotation() {
+ return builder;
+ }
+
+ @Override
+ public LinkedBindingBuilder<T> visitAnnotationType(Class<? extends Annotation> annotationType) {
+ return builder.annotatedWith(annotationType);
+ }
+
+ @Override
+ public LinkedBindingBuilder<T> visitAnnotationInstance(Annotation annotation) {
+ return builder.annotatedWith(annotation);
+ }
+ }
+
+ /**
+ * See the EDSL examples at {@link Lazy}.
+ */
+ public <T> LinkedBindingBuilder<T> bind(Key<T> key) {
+ TypeLiteral<T> type = key.getTypeLiteral();
+ PotentialAnnotation potentialAnnotation = PotentialAnnotation.from(key);
+ return potentialAnnotation.accept(new BindingAnnotator<>(bind(type)));
+ }
+
+ /**
+ * Actual binder implementation.
+ */
+ private static class LazyBindingBuilder<T> implements AnnotatedBindingBuilder<T> {
+ private final Binder binder;
+ private final TypeLiteral<T> type;
+ private final AnnotatedBindingBuilder<Lazy<T>> lazyBinding;
+ private final PotentialAnnotation potentialAnnotation;
+
+ LazyBindingBuilder(
+ Binder binder,
+ TypeLiteral<T> type,
+ AnnotatedBindingBuilder<Lazy<T>> lazyBinding,
+ PotentialAnnotation potentialAnnotation) {
+ this.binder = binder;
+ this.type = type;
+ this.lazyBinding = lazyBinding;
+ this.potentialAnnotation = potentialAnnotation;
+ }
+
+ @Override
+ public LinkedBindingBuilder<T> annotatedWith(Class<? extends Annotation> annotationType) {
+ PotentialAnnotation newAnnotation = potentialAnnotation.annotatedWith(annotationType);
+ Key<T> key = newAnnotation.getKey(type);
+
+ lazyBinding.annotatedWith(annotationType)
+ .toProvider(new LazyProvider<>(binder.getProvider(key), key));
+
+ return new LazyBindingBuilder<>(binder, type, null, newAnnotation);
+ }
+
+ @Override
+ public LinkedBindingBuilder<T> annotatedWith(Annotation annotation) {
+ PotentialAnnotation newAnnotation = potentialAnnotation.annotatedWith(annotation);
+ Key<T> key = newAnnotation.getKey(type);
+
+ lazyBinding.annotatedWith(annotation)
+ .toProvider(new LazyProvider<>(binder.getProvider(key), key));
+
+ return new LazyBindingBuilder<>(binder, type, null, newAnnotation);
+ }
+
+ /**
+ * @return A binding builder for the underlying binding.
+ */
+ private LinkedBindingBuilder<T> makeBinder() {
+ return binder.bind(potentialAnnotation.getKey(type));
+ }
+
+ @Override
+ public ScopedBindingBuilder to(Class<? extends T> implementation) {
+ return makeBinder().to(implementation);
+ }
+
+ @Override
+ public ScopedBindingBuilder to(TypeLiteral<? extends T> implementation) {
+ return makeBinder().to(implementation);
+ }
+
+ @Override
+ public ScopedBindingBuilder to(Key<? extends T> targetKey) {
+ return makeBinder().to(targetKey);
+ }
+
+ @Override
+ public void toInstance(T instance) {
+ makeBinder().toInstance(instance);
+ }
+
+ @Override
+ public ScopedBindingBuilder toProvider(com.google.inject.Provider<? extends T> provider) {
+ return makeBinder().toProvider(provider);
+ }
+
+ @Override
+ public ScopedBindingBuilder toProvider(Provider<? extends T> provider) {
+ return makeBinder().toProvider(provider);
+ }
+
+ @Override
+ public ScopedBindingBuilder toProvider(Class<? extends Provider<? extends T>> providerType) {
+ return makeBinder().toProvider(providerType);
+ }
+
+ @Override
+ public ScopedBindingBuilder toProvider(TypeLiteral<? extends Provider<? extends T>> providerType) {
+ return makeBinder().toProvider(providerType);
+ }
+
+ @Override
+ public ScopedBindingBuilder toProvider(Key<? extends Provider<? extends T>> providerKey) {
+ return makeBinder().toProvider(providerKey);
+ }
+
+ @Override
+ public <S extends T> ScopedBindingBuilder toConstructor(Constructor<S> constructor) {
+ return makeBinder().toConstructor(constructor);
+ }
+
+ @Override
+ public <S extends T> ScopedBindingBuilder toConstructor(Constructor<S> constructor, TypeLiteral<? extends S> type) {
+ return makeBinder().toConstructor(constructor, type);
+ }
+
+ @Override
+ public void in(Class<? extends Annotation> scopeAnnotation) {
+ makeBinder().in(scopeAnnotation);
+ }
+
+ @Override
+ public void in(Scope scope) {
+ makeBinder().in(scope);
+ }
+
+ @Override
+ public void asEagerSingleton() {
+ makeBinder().asEagerSingleton();
+ }
+ }
+
+ private static class LazyProvider<T> implements LazyBinding<T>, ProviderWithExtensionVisitor<Lazy<T>> {
+ private final Provider<T> provider;
+ private final Key<T> key;
+
+ LazyProvider(Provider<T> provider, Key<T> key) {
+ this.provider = provider;
+ this.key = key;
+ }
+
+ @Override
+ public Lazy<T> get() {
+ return new Lazy<>(provider);
+ }
+
+ @Override
+ public Key<T> getTargetKey() {
+ return key;
+ }
+
+ @SuppressWarnings("unchecked") // B must be Lazy<T>
+ @Override
+ public <B, V> V acceptExtensionVisitor(BindingTargetVisitor<B, V> visitor, ProviderInstanceBinding<? extends B> binding) {
+ if (visitor instanceof LazyBindingVisitor) {
+ return ((LazyBindingVisitor<T, V>)visitor).visit(this);
+ } else {
+ return visitor.visit(binding);
+ }
+ }
+ }
+}
diff --git a/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyBinding.java b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyBinding.java
new file mode 100644
index 0000000..518ae57
--- /dev/null
+++ b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyBinding.java
@@ -0,0 +1,34 @@
+/****************************************************************************
+ * Sangria *
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); *
+ * you may not use this file except in compliance with the License. *
+ * You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ ****************************************************************************/
+
+package com.tavianator.sangria.lazy;
+
+import com.google.inject.Key;
+
+/**
+ * SPI for {@link LazyBinder} bindings.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+public interface LazyBinding<T> {
+ /**
+ * @return The key wrapped by the {@link Lazy Lazy&lt;T&gt;} binding.
+ */
+ Key<T> getTargetKey();
+}
diff --git a/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyBindingVisitor.java b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyBindingVisitor.java
new file mode 100644
index 0000000..ef95a0c
--- /dev/null
+++ b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyBindingVisitor.java
@@ -0,0 +1,37 @@
+/****************************************************************************
+ * Sangria *
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); *
+ * you may not use this file except in compliance with the License. *
+ * You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ ****************************************************************************/
+
+package com.tavianator.sangria.lazy;
+
+import com.google.inject.spi.BindingTargetVisitor;
+
+/**
+ * Visitor interface for the lazy binding SPI.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+public interface LazyBindingVisitor<T, V> extends BindingTargetVisitor<T, V> {
+ /**
+ * Visit a {@link LazyBinding}.
+ *
+ * @param binding The binding to visit.
+ * @return A value of type {@code V}.
+ */
+ V visit(LazyBinding<? extends T> binding);
+}
diff --git a/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/package-info.java b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/package-info.java
new file mode 100644
index 0000000..15b5b1a
--- /dev/null
+++ b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/package-info.java
@@ -0,0 +1,25 @@
+/****************************************************************************
+ * Sangria *
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); *
+ * you may not use this file except in compliance with the License. *
+ * You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ ****************************************************************************/
+
+/**
+ * Lazy loading.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+package com.tavianator.sangria.lazy;
diff --git a/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazyTest.java b/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazyTest.java
new file mode 100644
index 0000000..ff63c03
--- /dev/null
+++ b/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazyTest.java
@@ -0,0 +1,243 @@
+/****************************************************************************
+ * Sangria *
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); *
+ * you may not use this file except in compliance with the License. *
+ * You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ ****************************************************************************/
+
+package com.tavianator.sangria.lazy;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.*;
+import javax.inject.Inject;
+import javax.inject.Qualifier;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Binding;
+import com.google.inject.CreationException;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.spi.DefaultBindingTargetVisitor;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.Elements;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * Tests for {@link Lazy} injection.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+public class LazyTest {
+ @Retention(RetentionPolicy.RUNTIME)
+ @Qualifier
+ private @interface Simple {
+ }
+
+ private interface Abstract {
+ }
+
+ private static class Concrete implements Abstract {
+ static final ThreadLocal<Integer> INSTANCES = new ThreadLocal<Integer>() {
+ @Override
+ protected Integer initialValue() {
+ return 0;
+ }
+ };
+
+ @Inject
+ Concrete() {
+ INSTANCES.set(INSTANCES.get() + 1);
+ }
+ }
+
+ private static class HasConcrete {
+ @Inject Lazy<Concrete> lazy;
+ }
+
+ private static class HasQualifiedAbstract {
+ @Inject @Simple Lazy<Abstract> lazy;
+ }
+
+ @Test
+ public void testJustInTime() {
+ testHasConcrete(Guice.createInjector());
+ }
+
+ @Test
+ public void testExplicitBindings() {
+ testHasConcrete(Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ binder().requireExplicitBindings();
+
+ bind(Concrete.class);
+ LazyBinder.create(binder())
+ .bind(Concrete.class);
+ }
+ }));
+ }
+
+ private void testHasConcrete(Injector injector) {
+ int before = Concrete.INSTANCES.get();
+
+ HasConcrete hasConcrete = new HasConcrete();
+ injector.injectMembers(hasConcrete);
+ assertThat(Concrete.INSTANCES.get(), equalTo(before));
+
+ Concrete instance = hasConcrete.lazy.get();
+ assertThat(Concrete.INSTANCES.get(), equalTo(before + 1));
+
+ Concrete instance2 = hasConcrete.lazy.get();
+ assertThat(instance2, sameInstance(instance));
+ assertThat(Concrete.INSTANCES.get(), equalTo(before + 1));
+
+ HasConcrete hasConcrete2 = new HasConcrete();
+ injector.injectMembers(hasConcrete2);
+ assertThat(Concrete.INSTANCES.get(), equalTo(before + 1));
+
+ Concrete instance3 = hasConcrete2.lazy.get();
+ assertThat(instance3, not(sameInstance(instance)));
+ assertThat(Concrete.INSTANCES.get(), equalTo(before + 2));
+ }
+
+ @Test
+ public void testBindSeparately() {
+ testQualifiedAbstract(Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(Abstract.class)
+ .annotatedWith(Simple.class)
+ .to(Concrete.class);
+
+ LazyBinder.create(binder())
+ .bind(Abstract.class)
+ .annotatedWith(Simple.class);
+ }
+ }));
+ }
+
+ @Test
+ public void testBindTogether() {
+ testQualifiedAbstract(Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ LazyBinder.create(binder())
+ .bind(Abstract.class)
+ .annotatedWith(Simple.class)
+ .to(Concrete.class);
+ }
+ }));
+ }
+
+ private void testQualifiedAbstract(Injector injector) {
+ int before = Concrete.INSTANCES.get();
+
+ HasQualifiedAbstract hasQualifiedAbstract = new HasQualifiedAbstract();
+ injector.injectMembers(hasQualifiedAbstract);
+ assertThat(Concrete.INSTANCES.get(), equalTo(before));
+
+ Abstract instance = hasQualifiedAbstract.lazy.get();
+ assertThat(Concrete.INSTANCES.get(), equalTo(before + 1));
+
+ Abstract instance2 = hasQualifiedAbstract.lazy.get();
+ assertThat(instance2, sameInstance(instance2));
+ assertThat(Concrete.INSTANCES.get(), equalTo(before + 1));
+
+ HasQualifiedAbstract hasQualifiedAbstract2 = new HasQualifiedAbstract();
+ injector.injectMembers(hasQualifiedAbstract2);
+ assertThat(Concrete.INSTANCES.get(), equalTo(before + 1));
+
+ Abstract instance3 = hasQualifiedAbstract2.lazy.get();
+ assertThat(instance3, not(sameInstance(instance)));
+ assertThat(Concrete.INSTANCES.get(), equalTo(before + 2));
+ }
+
+ @Test(expected = CreationException.class)
+ public void testMissingBinding() {
+ Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ LazyBinder.create(binder())
+ .bind(Abstract.class);
+ }
+ });
+ }
+
+ @Test(expected = CreationException.class)
+ public void testMissingQualifiedBinding() {
+ Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ LazyBinder.create(binder())
+ .bind(Abstract.class)
+ .annotatedWith(Simple.class);
+ }
+ });
+ }
+
+ private static class TestVisitor<T> extends DefaultBindingTargetVisitor<T, Boolean> implements LazyBindingVisitor<T, Boolean> {
+ @Override
+ public Boolean visit(LazyBinding<? extends T> binding) {
+ assertThat(binding.getTargetKey().equals(new Key<Abstract>(Simple.class) { }), is(true));
+ return true;
+ }
+
+ @Override
+ protected Boolean visitOther(Binding<? extends T> binding) {
+ return false;
+ }
+ }
+
+ private <T> boolean visit(Binding<T> binding) {
+ return binding.acceptTargetVisitor(new TestVisitor<T>());
+ }
+
+ @Test
+ public void testExtensionSpi() {
+ Module module = new AbstractModule() {
+ @Override
+ protected void configure() {
+ // TODO: Expose the SPI for unqualified bindings
+ LazyBinder.create(binder())
+ .bind(Abstract.class)
+ .annotatedWith(Simple.class)
+ .to(Concrete.class);
+ }
+ };
+
+ List<Element> elements = Elements.getElements(module);
+
+ int passed = 0;
+ for (Element element : elements) {
+ if (element instanceof Binding) {
+ if (visit((Binding<?>)element)) {
+ ++passed;
+ }
+ }
+ }
+ assertThat(passed, equalTo(1));
+
+ Injector injector = Guice.createInjector(Elements.getModule(elements));
+ assertThat(visit(injector.getBinding(new Key<Lazy<Abstract>>(Simple.class) { })), is(true));
+ assertThat(visit(injector.getBinding(new Key<Abstract>(Simple.class) { })), is(false));
+ assertThat(visit(injector.getBinding(new Key<Concrete>() { })), is(false));
+ }
+}