summaryrefslogtreecommitdiffstats
path: root/sangria-lazy
diff options
context:
space:
mode:
Diffstat (limited to 'sangria-lazy')
-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/LazyScopes.java59
-rw-r--r--sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazySingleton.java41
-rw-r--r--sangria-lazy/src/main/java/com/tavianator/sangria/lazy/SangriaLazyModule.java44
-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/LazySingletonTest.java83
-rw-r--r--sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazyTest.java243
11 files changed, 978 insertions, 0 deletions
diff --git a/sangria-lazy/pom.xml b/sangria-lazy/pom.xml
new file mode 100644
index 0000000..4840af3
--- /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>Lazy loading</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/LazyScopes.java b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyScopes.java
new file mode 100644
index 0000000..beb1294
--- /dev/null
+++ b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazyScopes.java
@@ -0,0 +1,59 @@
+/****************************************************************************
+ * 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;
+import com.google.inject.Provider;
+import com.google.inject.Scope;
+import com.google.inject.Scopes;
+
+/**
+ * Lazy scope implementations.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+public class LazyScopes {
+ /**
+ * Lazy version of {@link Scopes#SINGLETON}.
+ *
+ * @see LazySingleton
+ */
+ public static final Scope LAZY_SINGLETON = new Scope() {
+ public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
+ final Provider<T> singleton = Scopes.SINGLETON.scope(key, creator);
+
+ return new Provider<T>() {
+ public T get() {
+ return singleton.get();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s[%s]", creator, LAZY_SINGLETON);
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ return "LazyScopes.LAZY_SINGLETON";
+ }
+ };
+}
diff --git a/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazySingleton.java b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazySingleton.java
new file mode 100644
index 0000000..b8f0482
--- /dev/null
+++ b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/LazySingleton.java
@@ -0,0 +1,41 @@
+/****************************************************************************
+ * 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.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import javax.inject.Scope;
+import javax.inject.Singleton;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+/**
+ * Like {@link Singleton}, but always lazy.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+@Scope
+@Documented
+@Target({ TYPE, METHOD })
+@Retention(RUNTIME)
+public @interface LazySingleton {
+}
diff --git a/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/SangriaLazyModule.java b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/SangriaLazyModule.java
new file mode 100644
index 0000000..ff4a02a
--- /dev/null
+++ b/sangria-lazy/src/main/java/com/tavianator/sangria/lazy/SangriaLazyModule.java
@@ -0,0 +1,44 @@
+/****************************************************************************
+ * 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.AbstractModule;
+
+/**
+ * Module for lazy initialization features.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+public class SangriaLazyModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bindScope(LazySingleton.class, LazyScopes.LAZY_SINGLETON);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof SangriaLazyModule;
+ }
+
+ @Override
+ public int hashCode() {
+ return SangriaLazyModule.class.hashCode();
+ }
+}
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/LazySingletonTest.java b/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazySingletonTest.java
new file mode 100644
index 0000000..69fabce
--- /dev/null
+++ b/sangria-lazy/src/test/java/com/tavianator/sangria/lazy/LazySingletonTest.java
@@ -0,0 +1,83 @@
+/****************************************************************************
+ * 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;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.Stage;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * Tests for the {@link LazySingleton} scope.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.2
+ * @since 1.2
+ */
+public class LazySingletonTest {
+ @LazySingleton
+ private static class Scoped {
+ static final ThreadLocal<Integer> INSTANCES = new ThreadLocal<Integer>() {
+ @Override
+ protected Integer initialValue() {
+ return 0;
+ }
+ };
+
+ @Inject
+ Scoped() {
+ INSTANCES.set(INSTANCES.get() + 1);
+ }
+ }
+
+ @Test
+ public void testDevelopment() {
+ test(Stage.DEVELOPMENT, new SangriaLazyModule());
+ }
+
+ @Test
+ public void testProduction() {
+ test(Stage.PRODUCTION, new SangriaLazyModule());
+ }
+
+ @Test
+ public void testDuplicateModule() {
+ test(Stage.PRODUCTION, new SangriaLazyModule(), new SangriaLazyModule());
+ }
+
+ private void test(Stage stage, Module... modules) {
+ int before = Scoped.INSTANCES.get();
+
+ Injector injector = Guice.createInjector(stage, modules);
+ Provider<Scoped> provider = injector.getProvider(Scoped.class);
+ assertThat(Scoped.INSTANCES.get(), equalTo(before));
+
+ Scoped instance = provider.get();
+ assertThat(Scoped.INSTANCES.get(), equalTo(before + 1));
+
+ assertThat(provider.get(), sameInstance(instance));
+ assertThat(Scoped.INSTANCES.get(), equalTo(before + 1));
+ }
+}
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));
+ }
+}