/****************************************************************************
* 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);
}
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof LazyProvider)) {
return false;
}
LazyProvider<?> other = (LazyProvider<?>) obj;
return key.equals(other.key);
}
@Override
public int hashCode() {
return key.hashCode();
}
}
}