From d814b12bd39172bc59fcefa5815bc3776b793177 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 7 May 2014 21:58:15 -0400 Subject: listbinder: Add an implementation of ListBinder. --- sangria-listbinder/pom.xml | 63 +++ .../listbinder/AnnotatedListBinderBuilder.java | 28 ++ .../tavianator/sangria/listbinder/ListBinder.java | 427 +++++++++++++++++++++ .../sangria/listbinder/ListBinderBuilder.java | 24 ++ .../sangria/listbinder/ListBinderErrors.java | 43 +++ .../tavianator/sangria/listbinder/ListElement.java | 44 +++ .../sangria/listbinder/package-info.java | 25 ++ .../sangria/listbinder/ListBinderTest.java | 210 ++++++++++ 8 files changed, 864 insertions(+) create mode 100644 sangria-listbinder/pom.xml create mode 100644 sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/AnnotatedListBinderBuilder.java create mode 100644 sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinder.java create mode 100644 sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinderBuilder.java create mode 100644 sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinderErrors.java create mode 100644 sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListElement.java create mode 100644 sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/package-info.java create mode 100644 sangria-listbinder/src/test/java/com/tavianator/sangria/listbinder/ListBinderTest.java (limited to 'sangria-listbinder') diff --git a/sangria-listbinder/pom.xml b/sangria-listbinder/pom.xml new file mode 100644 index 0000000..5b04e5e --- /dev/null +++ b/sangria-listbinder/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + + com.tavianator.sangria + sangria + 1.1-SNAPSHOT + + + sangria-listbinder + jar + Sangria ListBinder + A multi-binder with ordering guarantees + + + + com.tavianator.sangria + sangria-core + + + + com.google.inject + guice + + + + com.google.inject.extensions + guice-multibindings + + + + com.google.guava + guava + + + + com.google.code.findbugs + jsr305 + true + + + + junit + junit + test + + + + org.hamcrest + hamcrest-integration + test + + + + org.mockito + mockito-core + test + + + diff --git a/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/AnnotatedListBinderBuilder.java b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/AnnotatedListBinderBuilder.java new file mode 100644 index 0000000..594971c --- /dev/null +++ b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/AnnotatedListBinderBuilder.java @@ -0,0 +1,28 @@ +package com.tavianator.sangria.listbinder; + +import java.lang.annotation.Annotation; + +/** + * Fluent builder interface. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.1 + * @since 1.1 + */ +public interface AnnotatedListBinderBuilder extends ListBinderBuilder { + /** + * Make a binder for an annotated list type. + * + * @param annotationType The annotation type for the list. + * @return A fluent builder. + */ + ListBinderBuilder annotatedWith(Class annotationType); + + /** + * Make a binder for an annotated list type. + * + * @param annotation The annotation instance for the list. + * @return A fluent builder. + */ + ListBinderBuilder annotatedWith(Annotation annotation); +} diff --git a/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinder.java b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinder.java new file mode 100644 index 0000000..c28d758 --- /dev/null +++ b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinder.java @@ -0,0 +1,427 @@ +/**************************************************************************** + * 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.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.base.Objects; +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. + * + *

+ * {@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: + *

+ * + *
+ * ListBinder listBinder = ListBinder.build(binder(), String.class)
+ *         .withDefaultPriority();
+ * listBinder.addBinding().toInstance("a");
+ * listBinder.addBinding().toInstance("b");
+ * 
+ * + *

+ * This will create a binding for a {@code List}, which contains {@code "a"} followed by {@code "b"}. It also + * creates a binding for {@code List>} — this may be useful in more advanced cases to allow list + * elements to be lazily loaded. + *

+ * + *

To add an annotation to the list binding, simply write this:

+ * + *
+ * ListBinder listBinder = ListBinder.build(binder(), String.class)
+ *         .annotatedWith(Names.named("name"))
+ *         .withDefaultPriority();
+ * 
+ * + *

+ * and the created binding will be {@code @Named("name") List} instead. + *

+ * + *

+ * For large lists, it may be helpful to split up their specification across different modules. This is accomplished by + * specifying priorities for the {@link ListBinder}s when they are created. For example: + *

+ * + *
+ * // In some module
+ * ListBinder listBinder1 = ListBinder.build(binder(), String.class)
+ *         .withPriority(0);
+ * listBinder1.addBinding().toInstance("a");
+ * listBinder1.addBinding().toInstance("b");
+ *
+ * // ... some other module
+ * ListBinder listBinder2 = ListBinder.build(binder(), String.class)
+ *         .withPriority(1);
+ * listBinder2.addBinding().toInstance("c");
+ * listBinder2.addBinding().toInstance("d");
+ * 
+ * + *

+ * 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}. + *

+ * + * @param The type of the list element. + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.1 + * @since 1.1 + */ +public class ListBinder { + private static final Class[] SKIPPED_SOURCES = { + ListBinder.class, + BuilderImpl.class, + }; + + private final Binder binder; + private final Multibinder> multibinder; + private final Multibinder> errorMultibinder; + private final TypeLiteral entryType; + private final Key> listKey; + private final Key>> listOfProvidersKey; + private final Key>> setKey; + private final Key>> errorSetKey; + private final PotentialAnnotation potentialAnnotation; + private final Priority initialPriority; + private Priority priority; + + private ListBinder( + Binder binder, + TypeLiteral entryType, + PotentialAnnotation potentialAnnotation, + Priority initialPriority) { + this.binder = binder; + this.entryType = entryType; + + TypeLiteral> elementType = listElementOf(entryType); + TypeLiteral> 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 TypeLiteral> listElementOf(TypeLiteral type) { + return (TypeLiteral>)TypeLiteral.get(Types.newParameterizedType(ListElement.class, type.getType())); + } + + @SuppressWarnings("unchecked") + private static TypeLiteral> listBinderErrorsOf(TypeLiteral type) { + return (TypeLiteral>)TypeLiteral.get(Types.newParameterizedType(ListBinderErrors.class, type.getType())); + } + + /** + * {@link PotentialAnnotation.Visitor} that makes {@link Multibinder}s with the given annotation. + */ + private static class MultibinderMaker implements PotentialAnnotation.Visitor> { + private final Binder binder; + private final TypeLiteral type; + + MultibinderMaker(Binder binder, TypeLiteral type) { + this.binder = binder; + this.type = type; + } + + @Override + public Multibinder visitNoAnnotation() { + return Multibinder.newSetBinder(binder, type); + } + + @Override + public Multibinder visitAnnotationType(Class annotationType) { + return Multibinder.newSetBinder(binder, type, annotationType); + } + + @Override + public Multibinder 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 The type of the list element. + * @return A fluent builder. + */ + public static AnnotatedListBinderBuilder build(Binder binder, Class 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 The type of the list element. + * @return A fluent builder. + */ + public static AnnotatedListBinderBuilder build(Binder binder, TypeLiteral type) { + return new BuilderImpl<>(binder.skipSources(SKIPPED_SOURCES), type, PotentialAnnotation.none()); + } + + private static class BuilderImpl implements AnnotatedListBinderBuilder { + private final Binder binder; + private final TypeLiteral entryType; + private final PotentialAnnotation potentialAnnotation; + + BuilderImpl(Binder binder, TypeLiteral type, PotentialAnnotation potentialAnnotation) { + this.binder = binder; + this.entryType = type; + this.potentialAnnotation = potentialAnnotation; + } + + @Override + public ListBinderBuilder annotatedWith(Class annotationType) { + return new BuilderImpl<>(binder, entryType, potentialAnnotation.annotatedWith(annotationType)); + } + + @Override + public ListBinderBuilder annotatedWith(Annotation annotation) { + return new BuilderImpl<>(binder, entryType, potentialAnnotation.annotatedWith(annotation)); + } + + @Override + public ListBinder withDefaultPriority() { + return create(Priority.getDefault()); + } + + @Override + public ListBinder withPriority(int weight, int... weights) { + return create(Priority.create(weight, weights)); + } + + private ListBinder create(Priority priority) { + ListBinder 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( + 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>}. + */ + private static class ListOfProvidersProvider implements Provider>> { + private final Key>> setKey; + private final Key>> errorSetKey; + private final Priority priority; + private List> providers; + + ListOfProvidersProvider(ListBinder 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 messages = new ArrayList<>(); + + // Get the errors into a multimap by priority + Set> errorSet = injector.getInstance(errorSetKey); + ListMultimap> errorMap = ArrayListMultimap.create(); + for (ListBinderErrors errors : errorSet) { + errorMap.put(errors.priority, errors); + } + + // Check for duplicate priorities + List> ourPriorityErrors = errorMap.get(priority); + ListBinderErrors 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> set = injector.getInstance(setKey); + List> elements = new ArrayList<>(set); + Collections.sort(elements); + + this.providers = FluentIterable.from(elements) + .transform(new Function, Provider>() { + @Override + public Provider apply(ListElement input) { + return injector.getProvider(input.key); + } + }) + .toList(); + } + + @Override + public List> 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}, in terms of {@code List>}. + */ + private static class ListOfProvidersAdapter implements Provider> { + private final Key>> providerListKey; + private Provider>> provider; + + ListOfProvidersAdapter(Key>> providerListKey) { + this.providerListKey = providerListKey; + } + + @Inject + void inject(final Injector injector) { + this.provider = injector.getProvider(providerListKey); + } + + @Override + public List get() { + return FluentIterable.from(provider.get()) + .transform(new Function, T>() { + @Override + public T apply(Provider 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 Objects.hashCode(providerListKey); + } + } + + /** + * Add an entry to the list. + * + *

+ * 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}. + *

+ * + * @return A fluent binding builder. + */ + public LinkedBindingBuilder addBinding() { + Key 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); + } +} diff --git a/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinderBuilder.java b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinderBuilder.java new file mode 100644 index 0000000..5dae594 --- /dev/null +++ b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinderBuilder.java @@ -0,0 +1,24 @@ +package com.tavianator.sangria.listbinder; + +import com.tavianator.sangria.core.Priority; + +/** + * Fluent builder interface. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.1 + * @since 1.1 + */ +public interface ListBinderBuilder { + /** + * @return A {@link ListBinder} with the default priority. + * @see Priority + */ + ListBinder withDefaultPriority(); + + /** + * @return A {@link ListBinder} with the given priority. + * @see Priority + */ + ListBinder withPriority(int weight, int... weights); +} diff --git a/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinderErrors.java b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinderErrors.java new file mode 100644 index 0000000..dfd0c64 --- /dev/null +++ b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinderErrors.java @@ -0,0 +1,43 @@ +/**************************************************************************** + * 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.listbinder; + +import com.google.inject.Key; +import com.google.inject.spi.Message; + +import com.tavianator.sangria.core.Priority; + +/** + * Error holder for {@link ListBinder}s. + * + * @param Only used to allow different {@link Key}s. + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.1 + * @since 1.1 + */ +class ListBinderErrors { + final Priority priority; + final Message duplicateBindersError; + final Message conflictingDefaultExplicitError; + + ListBinderErrors(Priority priority, Message duplicateBindersError, Message conflictingDefaultExplicitError) { + this.priority = priority; + this.duplicateBindersError = duplicateBindersError; + this.conflictingDefaultExplicitError = conflictingDefaultExplicitError; + } +} diff --git a/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListElement.java b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListElement.java new file mode 100644 index 0000000..bb95674 --- /dev/null +++ b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListElement.java @@ -0,0 +1,44 @@ +/**************************************************************************** + * 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.listbinder; + +import com.google.inject.Key; + +import com.tavianator.sangria.core.Priority; + +/** + * An individual element in a ListBinder. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.1 + * @since 1.1 + */ +class ListElement implements Comparable> { + final Key key; + final Priority priority; + + ListElement(Key key, Priority priority) { + this.key = key; + this.priority = priority; + } + + @Override + public int compareTo(ListElement o) { + return priority.compareTo(o.priority); + } +} diff --git a/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/package-info.java b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/package-info.java new file mode 100644 index 0000000..1ed1af4 --- /dev/null +++ b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/package-info.java @@ -0,0 +1,25 @@ +/**************************************************************************** + * 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. * + ****************************************************************************/ + +/** + * {@code sangria-listbinder}: A multi-binder with guaranteed order. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.1 + * @since 1.1 + */ +package com.tavianator.sangria.listbinder; diff --git a/sangria-listbinder/src/test/java/com/tavianator/sangria/listbinder/ListBinderTest.java b/sangria-listbinder/src/test/java/com/tavianator/sangria/listbinder/ListBinderTest.java new file mode 100644 index 0000000..4c7c86b --- /dev/null +++ b/sangria-listbinder/src/test/java/com/tavianator/sangria/listbinder/ListBinderTest.java @@ -0,0 +1,210 @@ +/**************************************************************************** + * 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.listbinder; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.*; +import javax.inject.Provider; +import javax.inject.Qualifier; + +import com.google.inject.AbstractModule; +import com.google.inject.CreationException; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; +import com.google.inject.name.Names; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import com.tavianator.sangria.core.TypeLiterals; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +/** + * Tests for {@link ListBinder}. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.1 + * @since 1.1 + */ +public class ListBinderTest { + public @Rule ExpectedException thrown = ExpectedException.none(); + + @Retention(RetentionPolicy.RUNTIME) + @Qualifier + private @interface Simple { + } + + private static final TypeLiteral> LIST_OF_STRINGS = TypeLiterals.listOf(String.class); + private static final TypeLiteral>> LIST_OF_STRING_PROVIDERS = TypeLiterals.listOf(TypeLiterals.providerOf(String.class)); + + @Test + public void testBasicLists() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + ListBinder listBinder = ListBinder.build(binder(), String.class) + .withDefaultPriority(); + listBinder.addBinding().toInstance("a"); + listBinder.addBinding().toInstance("b"); + listBinder.addBinding().toInstance("c"); + + listBinder = ListBinder.build(binder(), String.class) + .annotatedWith(Simple.class) + .withDefaultPriority(); + listBinder.addBinding().toInstance("d"); + listBinder.addBinding().toInstance("e"); + listBinder.addBinding().toInstance("f"); + + listBinder = ListBinder.build(binder(), String.class) + .annotatedWith(Names.named("name")) + .withDefaultPriority(); + listBinder.addBinding().toInstance("g"); + listBinder.addBinding().toInstance("h"); + listBinder.addBinding().toInstance("i"); + } + }); + List list = injector.getInstance(Key.get(LIST_OF_STRINGS)); + assertThat(list, contains("a", "b", "c")); + + List> providers = injector.getInstance(Key.get(LIST_OF_STRING_PROVIDERS)); + assertThat(providers, hasSize(3)); + assertThat(providers.get(0).get(), equalTo("a")); + assertThat(providers.get(1).get(), equalTo("b")); + assertThat(providers.get(2).get(), equalTo("c")); + + list = injector.getInstance(Key.get(LIST_OF_STRINGS, Simple.class)); + assertThat(list, contains("d", "e", "f")); + + list = injector.getInstance(Key.get(LIST_OF_STRINGS, Names.named("name"))); + assertThat(list, contains("g", "h", "i")); + } + + @Test + public void testSplitBinders() { + Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + ListBinder listBinder = ListBinder.build(binder(), String.class) + .withPriority(1); + listBinder.addBinding().toInstance("c"); + listBinder.addBinding().toInstance("d"); + } + }, new AbstractModule() { + @Override + protected void configure() { + ListBinder listBinder = ListBinder.build(binder(), String.class) + .withPriority(0); + listBinder.addBinding().toInstance("a"); + listBinder.addBinding().toInstance("b"); + } + }); + List list = injector.getInstance(Key.get(LIST_OF_STRINGS)); + assertThat(list, contains("a", "b", "c", "d")); + } + + @Test + public void testConflictingDefaultPriorities() { + thrown.expect(CreationException.class); + thrown.expectMessage(containsString("2 errors")); + thrown.expectMessage(containsString("1) Duplicate ListBinder with default priority")); + thrown.expectMessage(containsString("2) Duplicate ListBinder with default priority")); + thrown.expectMessage(containsString("at com.tavianator.sangria.listbinder.ListBinderTest")); + + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + ListBinder.build(binder(), String.class) + .withDefaultPriority(); + } + }, new AbstractModule() { + @Override + protected void configure() { + ListBinder.build(binder(), String.class) + .withDefaultPriority(); + } + }); + } + + @Test + public void testConflictingExplicitPriorities() { + thrown.expect(CreationException.class); + thrown.expectMessage(containsString("2 errors")); + thrown.expectMessage(containsString("1) Duplicate ListBinder with priority [1]")); + thrown.expectMessage(containsString("2) Duplicate ListBinder with priority [1]")); + thrown.expectMessage(containsString("at com.tavianator.sangria.listbinder.ListBinderTest")); + + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + ListBinder.build(binder(), String.class) + .withPriority(1); + } + }, new AbstractModule() { + @Override + protected void configure() { + ListBinder.build(binder(), String.class) + .withPriority(1); + } + }); + } + + @Test + public void testConflictingDefaultAndExplicitPriorities() { + thrown.expect(CreationException.class); + thrown.expectMessage(containsString("2 errors")); + thrown.expectMessage(containsString(") ListBinder with default priority conflicts with ListBinder with explicit priority")); + thrown.expectMessage(containsString(") ListBinder with priority [1] conflicts with ListBinder with default priority")); + thrown.expectMessage(containsString("at com.tavianator.sangria.listbinder.ListBinderTest")); + + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + ListBinder.build(binder(), String.class) + .withDefaultPriority(); + } + }, new AbstractModule() { + @Override + protected void configure() { + ListBinder.build(binder(), String.class) + .withPriority(1); + } + }); + } + + @Test + public void testToString() { + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + ListBinder listBinder = ListBinder.build(binder(), String.class) + .annotatedWith(Names.named("name")) + .withDefaultPriority(); + assertThat(listBinder.toString(), equalTo("ListBinder annotated with @com.google.inject.name.Named(value=name) with default priority")); + + ListBinder objectListBinder = ListBinder.build(binder(), Object.class) + .withPriority(1, 2); + assertThat(objectListBinder.toString(), equalTo("ListBinder with priority [1, 2]")); + } + }); + } +} -- cgit v1.2.3