/**************************************************************************** * Sangria * * Copyright (C) 2014 Tavian Barnes * * * * 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 TypeLiteral> lazyOf(TypeLiteral type) { return (TypeLiteral>)TypeLiteral.get(Types.newParameterizedType(Lazy.class, type.getType())); } /** * See the EDSL examples at {@link Lazy}. */ public AnnotatedBindingBuilder bind(Class type) { return bind(TypeLiteral.get(type)); } /** * See the EDSL examples at {@link Lazy}. */ public AnnotatedBindingBuilder bind(TypeLiteral type) { AnnotatedBindingBuilder> lazyBinding = binder.bind(lazyOf(type)); return new LazyBindingBuilder<>(binder, type, lazyBinding, PotentialAnnotation.none()); } /** * Applies an annotation to an {@link AnnotatedBindingBuilder}. */ private static class BindingAnnotator implements PotentialAnnotation.Visitor> { private final AnnotatedBindingBuilder builder; BindingAnnotator(AnnotatedBindingBuilder builder) { this.builder = builder; } @Override public LinkedBindingBuilder visitNoAnnotation() { return builder; } @Override public LinkedBindingBuilder visitAnnotationType(Class annotationType) { return builder.annotatedWith(annotationType); } @Override public LinkedBindingBuilder visitAnnotationInstance(Annotation annotation) { return builder.annotatedWith(annotation); } } /** * See the EDSL examples at {@link Lazy}. */ public LinkedBindingBuilder bind(Key key) { TypeLiteral type = key.getTypeLiteral(); PotentialAnnotation potentialAnnotation = PotentialAnnotation.from(key); return potentialAnnotation.accept(new BindingAnnotator<>(bind(type))); } /** * Actual binder implementation. */ private static class LazyBindingBuilder implements AnnotatedBindingBuilder { private final Binder binder; private final TypeLiteral type; private final AnnotatedBindingBuilder> lazyBinding; private final PotentialAnnotation potentialAnnotation; LazyBindingBuilder( Binder binder, TypeLiteral type, AnnotatedBindingBuilder> lazyBinding, PotentialAnnotation potentialAnnotation) { this.binder = binder; this.type = type; this.lazyBinding = lazyBinding; this.potentialAnnotation = potentialAnnotation; } @Override public LinkedBindingBuilder annotatedWith(Class annotationType) { PotentialAnnotation newAnnotation = potentialAnnotation.annotatedWith(annotationType); Key key = newAnnotation.getKey(type); lazyBinding.annotatedWith(annotationType) .toProvider(new LazyProvider<>(binder.getProvider(key), key)); return new LazyBindingBuilder<>(binder, type, null, newAnnotation); } @Override public LinkedBindingBuilder annotatedWith(Annotation annotation) { PotentialAnnotation newAnnotation = potentialAnnotation.annotatedWith(annotation); Key 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 makeBinder() { return binder.bind(potentialAnnotation.getKey(type)); } @Override public ScopedBindingBuilder to(Class implementation) { return makeBinder().to(implementation); } @Override public ScopedBindingBuilder to(TypeLiteral implementation) { return makeBinder().to(implementation); } @Override public ScopedBindingBuilder to(Key targetKey) { return makeBinder().to(targetKey); } @Override public void toInstance(T instance) { makeBinder().toInstance(instance); } @Override public ScopedBindingBuilder toProvider(com.google.inject.Provider provider) { return makeBinder().toProvider(provider); } @Override public ScopedBindingBuilder toProvider(Provider provider) { return makeBinder().toProvider(provider); } @Override public ScopedBindingBuilder toProvider(Class> providerType) { return makeBinder().toProvider(providerType); } @Override public ScopedBindingBuilder toProvider(TypeLiteral> providerType) { return makeBinder().toProvider(providerType); } @Override public ScopedBindingBuilder toProvider(Key> providerKey) { return makeBinder().toProvider(providerKey); } @Override public ScopedBindingBuilder toConstructor(Constructor constructor) { return makeBinder().toConstructor(constructor); } @Override public ScopedBindingBuilder toConstructor(Constructor constructor, TypeLiteral type) { return makeBinder().toConstructor(constructor, type); } @Override public void in(Class scopeAnnotation) { makeBinder().in(scopeAnnotation); } @Override public void in(Scope scope) { makeBinder().in(scope); } @Override public void asEagerSingleton() { makeBinder().asEagerSingleton(); } } private static class LazyProvider implements LazyBinding, ProviderWithExtensionVisitor> { private final Provider provider; private final Key key; LazyProvider(Provider provider, Key key) { this.provider = provider; this.key = key; } @Override public Lazy get() { return new Lazy<>(provider); } @Override public Key getTargetKey() { return key; } @SuppressWarnings("unchecked") // B must be Lazy @Override public V acceptExtensionVisitor(BindingTargetVisitor visitor, ProviderInstanceBinding binding) { if (visitor instanceof LazyBindingVisitor) { return ((LazyBindingVisitor)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(); } } }