/****************************************************************************
* 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.listbinder;
import java.lang.annotation.Annotation;
import java.util.*;
import javax.inject.Inject;
import javax.inject.Provider;
import com.google.common.base.Function;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ListMultimap;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.CreationException;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.spi.Message;
import com.google.inject.util.Types;
import com.tavianator.sangria.core.PotentialAnnotation;
import com.tavianator.sangria.core.PrettyTypes;
import com.tavianator.sangria.core.Priority;
import com.tavianator.sangria.core.TypeLiterals;
import com.tavianator.sangria.core.UniqueAnnotations;
/**
* A multi-binder with guaranteed order.
*
* <p>
* {@link ListBinder} is much like {@link Multibinder}, except it provides a guaranteed iteration order, and binds a
* {@link List} instead of a {@link Set}. For example:
* </p>
*
* <pre>
* ListBinder<String> listBinder = ListBinder.build(binder(), String.class)
* .withDefaultPriority();
* listBinder.addBinding().toInstance("a");
* listBinder.addBinding().toInstance("b");
* </pre>
*
* <p>
* This will create a binding for a {@code List<String>}, which contains {@code "a"} followed by {@code "b"}. It also
* creates a binding for {@code List<Provider<String>>} — this may be useful in more advanced cases to allow list
* elements to be lazily loaded.
* </p>
*
* <p>To add an annotation to the list binding, simply write this:</p>
*
* <pre>
* ListBinder<String> listBinder = ListBinder.build(binder(), String.class)
* .annotatedWith(Names.named("name"))
* .withDefaultPriority();
* </pre>
*
* <p>
* and the created binding will be {@code @Named("name") List<String>} instead.
* </p>
*
* <p>
* For large lists, it may be helpful to split up their specification across different modules. This is accomplished by
* specifying <em>priorities</em> for the {@link ListBinder}s when they are created. For example:
* </p>
*
* <pre>
* // In some module
* ListBinder<String> listBinder1 = ListBinder.build(binder(), String.class)
* .withPriority(0);
* listBinder1.addBinding().toInstance("a");
* listBinder1.addBinding().toInstance("b");
*
* // ... some other module
* ListBinder<String> listBinder2 = ListBinder.build(binder(), String.class)
* .withPriority(1);
* listBinder2.addBinding().toInstance("c");
* listBinder2.addBinding().toInstance("d");
* </pre>
*
* <p>
* The generated list will contain {@code "a"}, {@code "b"}, {@code "c"}, {@code "d"}, in order. This happens because
* the first {@link ListBinder} had a smaller priority, so its entries come first. For more information about the
* priority system, see {@link Priority}.
* </p>
*
* @param <T> The type of the list element.
* @author Tavian Barnes (tavianator@tavianator.com)
* @version 1.1
* @since 1.1
*/
public class ListBinder<T> {
private static final Class<?>[] SKIPPED_SOURCES = {
ListBinder.class,
BuilderImpl.class,
};
private final Binder binder;
private final Multibinder<ListElement<T>> multibinder;
private final Multibinder<ListBinderErrors<T>> errorMultibinder;
private final TypeLiteral<T> entryType;
private final Key<List<T>> listKey;
private final Key<List<Provider<T>>> listOfProvidersKey;
private final Key<Set<ListElement<T>>> setKey;
private final Key<Set<ListBinderErrors<T>>> errorSetKey;
private final PotentialAnnotation potentialAnnotation;
private final Priority initialPriority;
private Priority priority;
private ListBinder(
Binder binder,
TypeLiteral<T> entryType,
PotentialAnnotation potentialAnnotation,
Priority initialPriority) {
this.binder = binder;
this.entryType = entryType;
TypeLiteral<ListElement<T>> elementType = listElementOf(entryType);
TypeLiteral<ListBinderErrors<T>> errorsType = listBinderErrorsOf(entryType);
this.listKey = potentialAnnotation.getKey(TypeLiterals.listOf(entryType));
this.listOfProvidersKey = potentialAnnotation.getKey(TypeLiterals.listOf(TypeLiterals.providerOf(entryType)));
this.setKey = potentialAnnotation.getKey(TypeLiterals.setOf(elementType));
this.errorSetKey = potentialAnnotation.getKey(TypeLiterals.setOf(errorsType));
this.multibinder = potentialAnnotation.accept(new MultibinderMaker<>(binder, elementType));
this.errorMultibinder = potentialAnnotation.accept(new MultibinderMaker<>(binder, errorsType));
this.potentialAnnotation = potentialAnnotation;
this.priority = this.initialPriority = initialPriority;
}
@SuppressWarnings("unchecked")
private static <T> TypeLiteral<ListElement<T>> listElementOf(TypeLiteral<T> type) {
return (TypeLiteral<ListElement<T>>)TypeLiteral.get(Types.newParameterizedType(ListElement.class, type.getType()));
}
@SuppressWarnings("unchecked")
private static <T> TypeLiteral<ListBinderErrors<T>> listBinderErrorsOf(TypeLiteral<T> type) {
return (TypeLiteral<ListBinderErrors<T>>)TypeLiteral.get(Types.newParameterizedType(ListBinderErrors.class, type.getType()));
}
/**
* {@link PotentialAnnotation.Visitor} that makes {@link Multibinder}s with the given annotation.
*/
private static class MultibinderMaker<T> implements PotentialAnnotation.Visitor<Multibinder<T>> {
private final Binder binder;
private final TypeLiteral<T> type;
MultibinderMaker(Binder binder, TypeLiteral<T> type) {
this.binder = binder;
this.type = type;
}
@Override
public Multibinder<T> visitNoAnnotation() {
return Multibinder.newSetBinder(binder, type);
}
@Override
public Multibinder<T> visitAnnotationType(Class<? extends Annotation> annotationType) {
return Multibinder.newSetBinder(binder, type, annotationType);
}
@Override
public Multibinder<T> visitAnnotationInstance(Annotation annotation) {
return Multibinder.newSetBinder(binder, type, annotation);
}
}
/**
* Start building a {@link ListBinder}.
*
* @param binder The current binder, usually {@link AbstractModule#binder()}.
* @param type The type of the list element.
* @param <T> The type of the list element.
* @return A fluent builder.
*/
public static <T> AnnotatedListBinderBuilder<T> build(Binder binder, Class<T> type) {
return build(binder, TypeLiteral.get(type));
}
/**
* Start building a {@link ListBinder}.
*
* @param binder The current binder, usually {@link AbstractModule#binder()}.
* @param type The type of the list element.
* @param <T> The type of the list element.
* @return A fluent builder.
*/
public static <T> AnnotatedListBinderBuilder<T> build(Binder binder, TypeLiteral<T> type) {
return new BuilderImpl<>(binder.skipSources(SKIPPED_SOURCES), type, PotentialAnnotation.none());
}
private static class BuilderImpl<T> implements AnnotatedListBinderBuilder<T> {
private final Binder binder;
private final TypeLiteral<T> entryType;
private final PotentialAnnotation potentialAnnotation;
BuilderImpl(Binder binder, TypeLiteral<T> type, PotentialAnnotation potentialAnnotation) {
this.binder = binder;
this.entryType = type;
this.potentialAnnotation = potentialAnnotation;
}
@Override
public ListBinderBuilder<T> annotatedWith(Class<? extends Annotation> annotationType) {
return new BuilderImpl<>(binder, entryType, potentialAnnotation.annotatedWith(annotationType));
}
@Override
public ListBinderBuilder<T> annotatedWith(Annotation annotation) {
return new BuilderImpl<>(binder, entryType, potentialAnnotation.annotatedWith(annotation));
}
@Override
public ListBinder<T> withDefaultPriority() {
return create(Priority.getDefault());
}
@Override
public ListBinder<T> withPriority(int weight, int... weights) {
return create(Priority.create(weight, weights));
}
private ListBinder<T> create(Priority priority) {
ListBinder<T> listBinder = new ListBinder<>(binder, entryType, potentialAnnotation, priority);
// Add the delayed errors
Message duplicateBindersError = new Message(PrettyTypes.format("Duplicate %s", listBinder));
Message conflictingDefaultExplicitError;
if (priority.isDefault()) {
conflictingDefaultExplicitError = new Message(PrettyTypes.format("%s conflicts with ListBinder with explicit priority", listBinder));
} else {
conflictingDefaultExplicitError = new Message(PrettyTypes.format("%s conflicts with ListBinder with default priority", listBinder));
}
listBinder.errorMultibinder.addBinding().toInstance(new ListBinderErrors<T>(
priority,
duplicateBindersError,
conflictingDefaultExplicitError));
// Set up the exposed bindings
binder.bind(listBinder.listOfProvidersKey)
.toProvider(new ListOfProvidersProvider<>(listBinder));
binder.bind(listBinder.listKey)
.toProvider(new ListOfProvidersAdapter<>(listBinder.listOfProvidersKey));
return listBinder;
}
}
/**
* Provider implementation for {@code List<Provider<T>>}.
*/
private static class ListOfProvidersProvider<T> implements Provider<List<Provider<T>>> {
private final Key<Set<ListElement<T>>> setKey;
private final Key<Set<ListBinderErrors<T>>> errorSetKey;
private final Priority priority;
private List<Provider<T>> providers;
ListOfProvidersProvider(ListBinder<T> listBinder) {
this.setKey = listBinder.setKey;
this.errorSetKey = listBinder.errorSetKey;
this.priority = listBinder.initialPriority;
}
@Inject
void inject(Injector injector) {
validate(injector);
initialize(injector);
}
private void validate(Injector injector) {
// Note that here we don't report all errors at once, correctness relies on Guice injecting even providers
// that get de-duplicated. This way, all errors are attached to the right source.
List<Message> messages = new ArrayList<>();
// Get the errors into a multimap by priority
Set<ListBinderErrors<T>> errorSet = injector.getInstance(errorSetKey);
ListMultimap<Priority, ListBinderErrors<T>> errorMap = ArrayListMultimap.create();
for (ListBinderErrors<T> errors : errorSet) {
errorMap.put(errors.priority, errors);
}
// Check for duplicate priorities
List<ListBinderErrors<T>> ourPriorityErrors = errorMap.get(priority);
ListBinderErrors<T> ourErrors = ourPriorityErrors.get(0);
if (ourPriorityErrors.size() > 1) {
messages.add(ourErrors.duplicateBindersError);
}
// Check for default and non-default priorities
if (errorMap.containsKey(Priority.getDefault()) && errorMap.keySet().size() > 1) {
messages.add(ourErrors.conflictingDefaultExplicitError);
}
if (!messages.isEmpty()) {
throw new CreationException(messages);
}
}
private void initialize(final Injector injector) {
Set<ListElement<T>> set = injector.getInstance(setKey);
List<ListElement<T>> elements = new ArrayList<>(set);
Collections.sort(elements);
this.providers = FluentIterable.from(elements)
.transform(new Function<ListElement<T>, Provider<T>>() {
@Override
public Provider<T> apply(ListElement<T> input) {
return injector.getProvider(input.key);
}
})
.toList();
}
@Override
public List<Provider<T>> get() {
return providers;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof ListOfProvidersProvider)) {
return false;
}
ListOfProvidersProvider<?> other = (ListOfProvidersProvider<?>)obj;
return setKey.equals(other.setKey);
}
@Override
public int hashCode() {
return setKey.hashCode();
}
}
/**
* Provider implementation for {@code List<T>}, in terms of {@code List<Provider<T>>}.
*/
private static class ListOfProvidersAdapter<T> implements Provider<List<T>> {
private final Key<List<Provider<T>>> providerListKey;
private Provider<List<Provider<T>>> provider;
ListOfProvidersAdapter(Key<List<Provider<T>>> providerListKey) {
this.providerListKey = providerListKey;
}
@Inject
void inject(final Injector injector) {
this.provider = injector.getProvider(providerListKey);
}
@Override
public List<T> get() {
return FluentIterable.from(provider.get())
.transform(new Function<Provider<T>, T>() {
@Override
public T apply(Provider<T> input) {
return input.get();
}
})
.toList();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof ListOfProvidersAdapter)) {
return false;
}
ListOfProvidersAdapter<?> other = (ListOfProvidersAdapter<?>)obj;
return providerListKey.equals(other.providerListKey);
}
@Override
public int hashCode() {
return providerListKey.hashCode();
}
}
/**
* Add an entry to the list.
*
* <p>
* The entry will be added in order for this {@link ListBinder} instance. Between different {@link ListBinder}s, the
* order is determined by the {@link ListBinder}'s {@link Priority}.
* </p>
*
* @return A fluent binding builder.
*/
public LinkedBindingBuilder<T> addBinding() {
Key<T> key = Key.get(entryType, UniqueAnnotations.create());
multibinder.addBinding().toInstance(new ListElement<>(key, priority));
priority = priority.next();
return binder.bind(key);
}
@Override
public String toString() {
return PrettyTypes.format("ListBinder<%s>%s with %s",
entryType,
(potentialAnnotation.hasAnnotation() ? " annotated with " + potentialAnnotation : ""),
initialPriority);
}
}