summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pom.xml19
-rw-r--r--sangria-contextual/pom.xml2
-rw-r--r--sangria-core/pom.xml2
-rw-r--r--sangria-core/src/main/java/com/tavianator/sangria/core/DelayedError.java9
-rw-r--r--sangria-core/src/main/java/com/tavianator/sangria/core/PotentialAnnotation.java241
-rw-r--r--sangria-core/src/main/java/com/tavianator/sangria/core/Priority.java148
-rw-r--r--sangria-core/src/main/java/com/tavianator/sangria/core/TypeLiterals.java90
-rw-r--r--sangria-core/src/main/java/com/tavianator/sangria/core/package-info.java2
-rw-r--r--sangria-core/src/test/java/com/tavianator/sangria/core/DelayedErrorTest.java29
-rw-r--r--sangria-core/src/test/java/com/tavianator/sangria/core/PotentialAnnotationTest.java126
-rw-r--r--sangria-core/src/test/java/com/tavianator/sangria/core/PriorityTest.java100
-rw-r--r--sangria-core/src/test/java/com/tavianator/sangria/core/TypeLiteralsTest.java81
-rw-r--r--sangria-listbinder/pom.xml63
-rw-r--r--sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/AnnotatedListBinderBuilder.java28
-rw-r--r--sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinder.java427
-rw-r--r--sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinderBuilder.java24
-rw-r--r--sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinderErrors.java43
-rw-r--r--sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListElement.java44
-rw-r--r--sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/package-info.java25
-rw-r--r--sangria-listbinder/src/test/java/com/tavianator/sangria/listbinder/ListBinderTest.java210
-rw-r--r--sangria-log4j/pom.xml2
-rw-r--r--sangria-slf4j/pom.xml2
22 files changed, 1704 insertions, 13 deletions
diff --git a/pom.xml b/pom.xml
index 1d473a4..ae3ec41 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
<groupId>com.tavianator.sangria</groupId>
<artifactId>sangria</artifactId>
- <version>1.0</version>
+ <version>1.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Sangria</name>
@@ -51,25 +51,31 @@
<dependency>
<groupId>com.tavianator.sangria</groupId>
<artifactId>sangria-core</artifactId>
- <version>1.0</version>
+ <version>1.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.tavianator.sangria</groupId>
<artifactId>sangria-contextual</artifactId>
- <version>1.0</version>
+ <version>1.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.tavianator.sangria</groupId>
<artifactId>sangria-slf4j</artifactId>
- <version>1.0</version>
+ <version>1.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.tavianator.sangria</groupId>
<artifactId>sangria-log4j</artifactId>
- <version>1.0</version>
+ <version>1.1-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.tavianator.sangria</groupId>
+ <artifactId>sangria-listbinder</artifactId>
+ <version>1.1-SNAPSHOT</version>
</dependency>
<dependency>
@@ -193,6 +199,7 @@
<link>https://google-guice.googlecode.com/git/latest-javadoc/</link>
<link>http://docs.guava-libraries.googlecode.com/git-history/v16.0.1/javadoc/</link>
<link>http://slf4j.org/api/</link>
+ <link>http://logging.apache.org/log4j/2.x/log4j-api/apidocs/</link>
<link>http://jsr-305.googlecode.com/svn/trunk/javadoc/</link>
<link>http://junit.sourceforge.net/javadoc/</link>
<link>http://hamcrest.org/JavaHamcrest/javadoc/1.3/</link>
@@ -250,6 +257,7 @@
<link>https://google-guice.googlecode.com/git/latest-javadoc/</link>
<link>http://docs.guava-libraries.googlecode.com/git-history/v16.0.1/javadoc/</link>
<link>http://slf4j.org/api/</link>
+ <link>http://logging.apache.org/log4j/2.x/log4j-api/apidocs/</link>
<link>http://jsr-305.googlecode.com/svn/trunk/javadoc/</link>
<link>http://junit.sourceforge.net/javadoc/</link>
<link>http://hamcrest.org/JavaHamcrest/javadoc/1.3/</link>
@@ -279,6 +287,7 @@
<module>sangria-contextual</module>
<module>sangria-slf4j</module>
<module>sangria-log4j</module>
+ <module>sangria-listbinder</module>
</modules>
<profiles>
diff --git a/sangria-contextual/pom.xml b/sangria-contextual/pom.xml
index 02e928e..54d0a36 100644
--- a/sangria-contextual/pom.xml
+++ b/sangria-contextual/pom.xml
@@ -7,7 +7,7 @@
<parent>
<groupId>com.tavianator.sangria</groupId>
<artifactId>sangria</artifactId>
- <version>1.0</version>
+ <version>1.1-SNAPSHOT</version>
</parent>
<artifactId>sangria-contextual</artifactId>
diff --git a/sangria-core/pom.xml b/sangria-core/pom.xml
index 59a7c31..8c58c49 100644
--- a/sangria-core/pom.xml
+++ b/sangria-core/pom.xml
@@ -7,7 +7,7 @@
<parent>
<groupId>com.tavianator.sangria</groupId>
<artifactId>sangria</artifactId>
- <version>1.0</version>
+ <version>1.1-SNAPSHOT</version>
</parent>
<artifactId>sangria-core</artifactId>
diff --git a/sangria-core/src/main/java/com/tavianator/sangria/core/DelayedError.java b/sangria-core/src/main/java/com/tavianator/sangria/core/DelayedError.java
index d6f86a8..2aeabca 100644
--- a/sangria-core/src/main/java/com/tavianator/sangria/core/DelayedError.java
+++ b/sangria-core/src/main/java/com/tavianator/sangria/core/DelayedError.java
@@ -25,6 +25,8 @@ import com.google.inject.CreationException;
import com.google.inject.Injector;
import com.google.inject.spi.Message;
+import static com.google.common.base.Preconditions.*;
+
/**
* Similar to {@link Binder#addError(String, Object...)}, but can be canceled later. Useful for enforcing correct usage
* of fluent APIs.
@@ -35,6 +37,7 @@ import com.google.inject.spi.Message;
*/
public class DelayedError {
private Throwable error;
+ private boolean reported = false;
/**
* Create a {@link DelayedError}.
@@ -85,11 +88,13 @@ public class DelayedError {
* Cancel this error.
*/
public void cancel() {
- this.error = null;
+ checkState(!reported, "This error has already been reported");
+ error = null;
}
@Inject
- void inject(Injector injector) throws Throwable {
+ void reportErrors(Injector injector) throws Throwable {
+ reported = true;
if (error != null) {
throw error;
}
diff --git a/sangria-core/src/main/java/com/tavianator/sangria/core/PotentialAnnotation.java b/sangria-core/src/main/java/com/tavianator/sangria/core/PotentialAnnotation.java
new file mode 100644
index 0000000..302e5e1
--- /dev/null
+++ b/sangria-core/src/main/java/com/tavianator/sangria/core/PotentialAnnotation.java
@@ -0,0 +1,241 @@
+/****************************************************************************
+ * 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.core;
+
+import java.lang.annotation.Annotation;
+import java.util.*;
+
+import com.google.inject.CreationException;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+import com.google.inject.spi.Message;
+
+/**
+ * A record of stored annotations, perfect for builders with {@code annotatedWith()} methods.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.1
+ * @since 1.1
+ */
+public abstract class PotentialAnnotation {
+ /**
+ * A visitor interface to examine a {@link PotentialAnnotation}'s annotation, if it exists.
+ *
+ * @param <T> The type to return.
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.1
+ * @since 1.1
+ */
+ public interface Visitor<T> {
+ /**
+ * Called when there is no annotation.
+ *
+ * @return Any value.
+ */
+ T visitNoAnnotation();
+
+ /**
+ * Called when an annotation type is stored.
+ *
+ * @param annotationType The annotation type.
+ * @return Any value.
+ */
+ T visitAnnotationType(Class<? extends Annotation> annotationType);
+
+ /**
+ * Called when an annotation instance is stored.
+ *
+ * @param annotation The annotation instance.
+ * @return Any value.
+ */
+ T visitAnnotationInstance(Annotation annotation);
+ }
+
+ private static final PotentialAnnotation NONE = new NoAnnotation();
+
+ /**
+ * @return A {@link PotentialAnnotation} with no annotation.
+ */
+ public static PotentialAnnotation none() {
+ return NONE;
+ }
+
+ private PotentialAnnotation() {
+ }
+
+ /**
+ * Add an annotation.
+ *
+ * @param annotationType The annotation type to add.
+ * @return A new {@link PotentialAnnotation} associated with the given annotation type.
+ * @throws CreationException If an annotation is already present.
+ */
+ public PotentialAnnotation annotatedWith(Class<? extends Annotation> annotationType) {
+ throw annotationAlreadyPresent();
+ }
+
+ /**
+ * Add an annotation.
+ *
+ * @param annotation The annotation instance to add.
+ * @return A new {@link PotentialAnnotation} associated with the given annotation instance.
+ * @throws CreationException If an annotation is already present.
+ */
+ public PotentialAnnotation annotatedWith(Annotation annotation) {
+ throw annotationAlreadyPresent();
+ }
+
+ private CreationException annotationAlreadyPresent() {
+ Message message = new Message("An annotation was already present");
+ return new CreationException(Collections.singletonList(message));
+ }
+
+ /**
+ * @return Whether an annotation is present.
+ */
+ public abstract boolean hasAnnotation();
+
+ /**
+ * Create a {@link Key} with the given type and the stored annotation.
+ *
+ * @param type The type of the key to create.
+ * @param <T> The type of the key to create.
+ * @return A {@link Key}.
+ */
+ public <T> Key<T> getKey(Class<T> type) {
+ return getKey(TypeLiteral.get(type));
+ }
+
+ /**
+ * Create a {@link Key} with the given type and the stored annotation.
+ *
+ * @param type The type of the key to create.
+ * @param <T> The type of the key to create.
+ * @return A {@link Key}.
+ */
+ public abstract <T> Key<T> getKey(TypeLiteral<T> type);
+
+ /**
+ * Accept a {@link Visitor}.
+ *
+ * @param visitor The visitor to accept.
+ * @param <T> The type for the visitor to return.
+ * @return The value produced by the visitor.
+ */
+ public abstract <T> T accept(Visitor<T> visitor);
+
+ @Override
+ public abstract String toString();
+
+ /**
+ * Implementation of {@link #none()}.
+ */
+ private static class NoAnnotation extends PotentialAnnotation {
+ @Override
+ public PotentialAnnotation annotatedWith(Class<? extends Annotation> annotationType) {
+ return new AnnotationType(annotationType);
+ }
+
+ @Override
+ public PotentialAnnotation annotatedWith(Annotation annotation) {
+ return new AnnotationInstance(annotation);
+ }
+
+ @Override
+ public boolean hasAnnotation() {
+ return false;
+ }
+
+ @Override
+ public <T> Key<T> getKey(TypeLiteral<T> type) {
+ return Key.get(type);
+ }
+
+ @Override
+ public <T> T accept(Visitor<T> visitor) {
+ return visitor.visitNoAnnotation();
+ }
+
+ @Override
+ public String toString() {
+ return "[no annotation]";
+ }
+ }
+
+ /**
+ * Implementation of {@link #annotatedWith(Class)}.
+ */
+ private static class AnnotationType extends PotentialAnnotation {
+ private final Class<? extends Annotation> annotationType;
+
+ AnnotationType(Class<? extends Annotation> annotationType) {
+ this.annotationType = annotationType;
+ }
+
+ @Override
+ public boolean hasAnnotation() {
+ return true;
+ }
+
+ @Override
+ public <T> Key<T> getKey(TypeLiteral<T> type) {
+ return Key.get(type, annotationType);
+ }
+
+ @Override
+ public <T> T accept(Visitor<T> visitor) {
+ return visitor.visitAnnotationType(annotationType);
+ }
+
+ @Override
+ public String toString() {
+ return "@" + annotationType.getCanonicalName();
+ }
+ }
+
+ /**
+ * Implementation of {@link #annotatedWith(Annotation)}.
+ */
+ private static class AnnotationInstance extends PotentialAnnotation {
+ private final Annotation annotation;
+
+ AnnotationInstance(Annotation annotation) {
+ this.annotation = annotation;
+ }
+
+ @Override
+ public boolean hasAnnotation() {
+ return true;
+ }
+
+ @Override
+ public <T> Key<T> getKey(TypeLiteral<T> type) {
+ return Key.get(type, annotation);
+ }
+
+ @Override
+ public <T> T accept(Visitor<T> visitor) {
+ return visitor.visitAnnotationInstance(annotation);
+ }
+
+ @Override
+ public String toString() {
+ return annotation.toString();
+ }
+ }
+}
diff --git a/sangria-core/src/main/java/com/tavianator/sangria/core/Priority.java b/sangria-core/src/main/java/com/tavianator/sangria/core/Priority.java
new file mode 100644
index 0000000..a833883
--- /dev/null
+++ b/sangria-core/src/main/java/com/tavianator/sangria/core/Priority.java
@@ -0,0 +1,148 @@
+/****************************************************************************
+ * 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.core;
+
+import java.util.*;
+
+import com.google.common.collect.ComparisonChain;
+import com.google.common.primitives.Ints;
+
+/**
+ * A loosely-coupled, infinitely divisible priority/weight system.
+ *
+ * <p>
+ * This class implements an extensible priority system based on lexicographical ordering. In its simplest use, {@code
+ * Priority.create(0)} is ordered before {@code Priority.create(1)}, then {@code Priority.create(2)}, etc.
+ * </p>
+ *
+ * <p>
+ * To create a priority that is ordered between two existing ones, simply add another parameter: {@code
+ * Priority.create(1, 1)} comes after {@code Priority.create(1)}, but before {@code Priority.create(2)}. In this way,
+ * priorities can always be inserted anywhere in a sequence.
+ * </p>
+ *
+ * <p>
+ * The {@link #next()} method creates a priority that is ordered immediately following the current one, and is distinct
+ * from all priorities obtained by any other means. This provides a convenient way to order entire segments of lists.
+ * </p>
+ *
+ * <p>
+ * A special priority, obtained by {@code Priority.getDefault()}, sorts before all other priorities.
+ * </p>
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.1
+ * @since 1.1
+ */
+public class Priority implements Comparable<Priority> {
+ private static final Priority DEFAULT = new Priority(new int[0], 0);
+ private static final Comparator<int[]> COMPARATOR = Ints.lexicographicalComparator();
+
+ private final int[] weights;
+ private final int seq;
+
+ /**
+ * @return The default priority, which comes before all other priorities.
+ */
+ public static Priority getDefault() {
+ return DEFAULT;
+ }
+
+ /**
+ * Create a {@link Priority} with the given sequence.
+ *
+ * @param weight The first value of the weight sequence.
+ * @param weights An integer sequence. These sequences are sorted lexicographically, so {@code Priority.create(1)}
+ * sorts before {@code Priority.create(1, 1)}, which sorts before {@code Priority.create(2)}.
+ * @return A new {@link Priority}.
+ */
+ public static Priority create(int weight, int... weights) {
+ int[] newWeights = new int[weights.length + 1];
+ newWeights[0] = weight;
+ System.arraycopy(weights, 0, newWeights, 1, weights.length);
+ return new Priority(newWeights, 0);
+ }
+
+ private Priority(int[] weights, int seq) {
+ this.weights = weights;
+ this.seq = seq;
+ }
+
+ /**
+ * @return Whether this priority originated in a call to {@link #getDefault()}.
+ */
+ public boolean isDefault() {
+ return weights.length == 0;
+ }
+
+ /**
+ * @return A new {@link Priority} which immediately follows this one, and which is distinct from all other
+ * priorities obtained by {@link #create(int, int...)}.
+ */
+ public Priority next() {
+ return new Priority(weights, seq + 1);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof Priority)) {
+ return false;
+ }
+
+ Priority other = (Priority)obj;
+ return Arrays.equals(weights, other.weights)
+ && seq == other.seq;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(weights) + seq;
+ }
+
+ @Override
+ public int compareTo(Priority o) {
+ return ComparisonChain.start()
+ .compare(weights, o.weights, COMPARATOR)
+ .compare(seq, o.seq)
+ .result();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ if (weights.length == 0) {
+ builder.append("default priority");
+ } else {
+ builder.append("priority [");
+ for (int i = 0; i < weights.length; ++i) {
+ if (i != 0) {
+ builder.append(", ");
+ }
+ builder.append(weights[i]);
+ }
+ builder.append("]");
+ }
+ if (seq != 0) {
+ builder.append(" + ")
+ .append(seq);
+ }
+ return builder.toString();
+ }
+}
diff --git a/sangria-core/src/main/java/com/tavianator/sangria/core/TypeLiterals.java b/sangria-core/src/main/java/com/tavianator/sangria/core/TypeLiterals.java
new file mode 100644
index 0000000..ff42790
--- /dev/null
+++ b/sangria-core/src/main/java/com/tavianator/sangria/core/TypeLiterals.java
@@ -0,0 +1,90 @@
+/****************************************************************************
+ * 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.core;
+
+import java.util.*;
+
+import javax.inject.Provider;
+
+import com.google.inject.TypeLiteral;
+import com.google.inject.util.Types;
+
+/**
+ * Static utility functions for working with {@link TypeLiteral}s.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.1
+ * @since 1.1
+ */
+public class TypeLiterals {
+ private TypeLiterals() {
+ // Not for instantiating
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> TypeLiteral<List<T>> listOf(Class<T> type) {
+ return (TypeLiteral<List<T>>)TypeLiteral.get(Types.listOf(type));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> TypeLiteral<List<T>> listOf(TypeLiteral<T> type) {
+ return (TypeLiteral<List<T>>)TypeLiteral.get(Types.listOf(type.getType()));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> TypeLiteral<Set<T>> setOf(Class<T> type) {
+ return (TypeLiteral<Set<T>>)TypeLiteral.get(Types.setOf(type));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> TypeLiteral<Set<T>> setOf(TypeLiteral<T> type) {
+ return (TypeLiteral<Set<T>>)TypeLiteral.get(Types.setOf(type.getType()));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <K, V> TypeLiteral<Map<K, V>> mapOf(Class<K> keyType, Class<V> valueType) {
+ return (TypeLiteral<Map<K, V>>)TypeLiteral.get(Types.mapOf(keyType, valueType));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <K, V> TypeLiteral<Map<K, V>> mapOf(Class<K> keyType, TypeLiteral<V> valueType) {
+ return (TypeLiteral<Map<K, V>>)TypeLiteral.get(Types.mapOf(keyType, valueType.getType()));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <K, V> TypeLiteral<Map<K, V>> mapOf(TypeLiteral<K> keyType, Class<V> valueType) {
+ return (TypeLiteral<Map<K, V>>)TypeLiteral.get(Types.mapOf(keyType.getType(), valueType));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <K, V> TypeLiteral<Map<K, V>> mapOf(TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
+ return (TypeLiteral<Map<K, V>>)TypeLiteral.get(Types.mapOf(keyType.getType(), valueType.getType()));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> TypeLiteral<Provider<T>> providerOf(Class<T> type) {
+ // Can't use Types.providerOf() because we want to stick to JSR-330 Providers
+ return (TypeLiteral<Provider<T>>)TypeLiteral.get(Types.newParameterizedType(Provider.class, type));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> TypeLiteral<Provider<T>> providerOf(TypeLiteral<T> type) {
+ // Can't use Types.providerOf() because we want to stick to JSR-330 Providers
+ return (TypeLiteral<Provider<T>>)TypeLiteral.get(Types.newParameterizedType(Provider.class, type.getType()));
+ }
+}
diff --git a/sangria-core/src/main/java/com/tavianator/sangria/core/package-info.java b/sangria-core/src/main/java/com/tavianator/sangria/core/package-info.java
index 6d416d3..009aa5e 100644
--- a/sangria-core/src/main/java/com/tavianator/sangria/core/package-info.java
+++ b/sangria-core/src/main/java/com/tavianator/sangria/core/package-info.java
@@ -19,7 +19,7 @@
* {@code sangria-core}: Common code for Sangria.
*
* @author Tavian Barnes (tavianator@tavianator.com)
- * @version 1.0
+ * @version 1.1
* @since 1.0
*/
package com.tavianator.sangria.core;
diff --git a/sangria-core/src/test/java/com/tavianator/sangria/core/DelayedErrorTest.java b/sangria-core/src/test/java/com/tavianator/sangria/core/DelayedErrorTest.java
index 349dced..f27aea8 100644
--- a/sangria-core/src/test/java/com/tavianator/sangria/core/DelayedErrorTest.java
+++ b/sangria-core/src/test/java/com/tavianator/sangria/core/DelayedErrorTest.java
@@ -32,7 +32,7 @@ import static org.hamcrest.Matchers.*;
* Tests for {@link DelayedError}.
*
* @author Tavian Barnes (tavianator@tavianator.com)
- * @version 1.0
+ * @version 1.1
* @since 1.0
*/
public class DelayedErrorTest {
@@ -83,4 +83,31 @@ public class DelayedErrorTest {
}
});
}
+
+ @Test
+ public void testCancel() {
+ Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ DelayedError error = DelayedError.create(binder(), "Message");
+ error.cancel();
+ }
+ });
+ }
+
+ @Test
+ public void testLateCancel() {
+ final DelayedError[] errorHolder = new DelayedError[1];
+
+ Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ errorHolder[0] = DelayedError.create(binder(), "Message");
+ errorHolder[0].cancel();
+ }
+ });
+
+ thrown.expect(IllegalStateException.class);
+ errorHolder[0].cancel();
+ }
}
diff --git a/sangria-core/src/test/java/com/tavianator/sangria/core/PotentialAnnotationTest.java b/sangria-core/src/test/java/com/tavianator/sangria/core/PotentialAnnotationTest.java
new file mode 100644
index 0000000..c4ccc36
--- /dev/null
+++ b/sangria-core/src/test/java/com/tavianator/sangria/core/PotentialAnnotationTest.java
@@ -0,0 +1,126 @@
+/****************************************************************************
+ * 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.core;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import javax.inject.Qualifier;
+
+import com.google.inject.CreationException;
+import com.google.inject.Key;
+import com.google.inject.name.Names;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * Tests for {@link PotentialAnnotation}s.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.1
+ * @since 1.1
+ */
+public class PotentialAnnotationTest {
+ @Retention(RetentionPolicy.RUNTIME)
+ @Qualifier
+ private @interface Simple {
+ }
+
+ private final PotentialAnnotation none = PotentialAnnotation.none();
+ private final Annotation nameAnnotation = Names.named("name");
+
+ @Test
+ public void testHasAnnotation() {
+ assertThat(none.hasAnnotation(), is(false));
+ assertThat(none.annotatedWith(Simple.class).hasAnnotation(), is(true));
+ assertThat(none.annotatedWith(nameAnnotation).hasAnnotation(), is(true));
+ }
+
+ @Test(expected = CreationException.class)
+ public void testInvalidAnnotatedWithType() {
+ none.annotatedWith(Simple.class)
+ .annotatedWith(Simple.class);
+ }
+
+ @Test(expected = CreationException.class)
+ public void testInvalidAnnotatedWithInstance() {
+ none.annotatedWith(Names.named("name"))
+ .annotatedWith(Names.named("name"));
+ }
+
+ @Test
+ public void testGetKey() {
+ assertThat(none.getKey(String.class),
+ equalTo(new Key<String>() { }));
+ assertThat(none.annotatedWith(Simple.class).getKey(String.class),
+ equalTo(new Key<String>(Simple.class) { }));
+ assertThat(none.annotatedWith(nameAnnotation).getKey(String.class),
+ equalTo(new Key<String>(nameAnnotation) { }));
+ }
+
+ @Test
+ public void testVisitor() {
+ PotentialAnnotation.Visitor<String> visitor = new PotentialAnnotation.Visitor<String>() {
+ @Override
+ public String visitNoAnnotation() {
+ return "none";
+ }
+
+ @Override
+ public String visitAnnotationType(Class<? extends Annotation> annotationType) {
+ assertThat((Object)annotationType, equalTo((Object)Simple.class));
+ return "type";
+ }
+
+ @Override
+ public String visitAnnotationInstance(Annotation annotation) {
+ assertThat(annotation, equalTo(nameAnnotation));
+ return "instance";
+ }
+ };
+
+ assertThat(none.accept(visitor), equalTo("none"));
+ assertThat(none.annotatedWith(Simple.class).accept(visitor), equalTo("type"));
+ assertThat(none.annotatedWith(nameAnnotation).accept(visitor), equalTo("instance"));
+ }
+
+ @Test
+ public void testToString() {
+ assertThat(none.toString(),
+ equalTo("[no annotation]"));
+ assertThat(none.annotatedWith(Simple.class).toString(),
+ equalTo("@com.tavianator.sangria.core.PotentialAnnotationTest.Simple"));
+ assertThat(none.annotatedWith(nameAnnotation).toString(),
+ equalTo("@com.google.inject.name.Named(value=name)"));
+ }
+
+ /**
+ * Needed to avoid compilation error to to inferred type being anonymous class.
+ */
+ private static <T> Matcher<Key<T>> equalTo(Key<T> key) {
+ return Matchers.equalTo(key);
+ }
+
+ private static <T> Matcher<T> equalTo(T object) {
+ return Matchers.equalTo(object);
+ }
+}
diff --git a/sangria-core/src/test/java/com/tavianator/sangria/core/PriorityTest.java b/sangria-core/src/test/java/com/tavianator/sangria/core/PriorityTest.java
new file mode 100644
index 0000000..0869565
--- /dev/null
+++ b/sangria-core/src/test/java/com/tavianator/sangria/core/PriorityTest.java
@@ -0,0 +1,100 @@
+/****************************************************************************
+ * 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.core;
+
+import java.util.*;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * Tests for {@link Priority}s.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.1
+ * @since 1.1
+ */
+public class PriorityTest {
+ private final Priority defaultPriority = Priority.getDefault();
+ private final Priority one = Priority.create(1);
+ private final Priority oneTwo = Priority.create(1, 2);
+ private final Priority two = Priority.create(2);
+
+ @Test
+ public void testOrdering() {
+ List<Priority> list = Arrays.asList(
+ defaultPriority.next(),
+ two,
+ oneTwo.next(),
+ oneTwo,
+ two.next(),
+ defaultPriority,
+ defaultPriority.next().next(),
+ one,
+ one.next());
+ Collections.sort(list);
+ assertThat(list, contains(
+ defaultPriority,
+ defaultPriority.next(),
+ defaultPriority.next().next(),
+ one,
+ one.next(),
+ oneTwo,
+ oneTwo.next(),
+ two,
+ two.next()));
+
+ assertThat(defaultPriority, equalTo(Priority.getDefault()));
+ assertThat(defaultPriority.next(), equalTo(Priority.getDefault().next()));
+ assertThat(defaultPriority, not(equalTo(Priority.getDefault().next())));
+
+ assertThat(one, equalTo(Priority.create(1)));
+ assertThat(oneTwo, equalTo(Priority.create(1, 2)));
+ assertThat(two, equalTo(Priority.create(2)));
+
+ assertThat(oneTwo.hashCode(), equalTo(Priority.create(1, 2).hashCode()));
+ }
+
+ @Test
+ public void testIsDefault() {
+ assertThat(defaultPriority.isDefault(), is(true));
+ assertThat(defaultPriority.next().isDefault(), is(true));
+
+ assertThat(one.isDefault(), is(false));
+ assertThat(oneTwo.isDefault(), is(false));
+ assertThat(two.isDefault(), is(false));
+ assertThat(two.next().isDefault(), is(false));
+ }
+
+ @Test
+ public void testToString() {
+ assertThat(Priority.getDefault().toString(), equalTo("default priority"));
+ assertThat(Priority.getDefault().next().toString(), equalTo("default priority + 1"));
+ assertThat(Priority.getDefault().next().next().toString(), equalTo("default priority + 2"));
+
+ assertThat(Priority.create(1).toString(), equalTo("priority [1]"));
+ assertThat(Priority.create(1).next().toString(), equalTo("priority [1] + 1"));
+ assertThat(Priority.create(1).next().next().toString(), equalTo("priority [1] + 2"));
+
+ assertThat(Priority.create(1, 2).toString(), equalTo("priority [1, 2]"));
+ assertThat(Priority.create(1, 2).next().toString(), equalTo("priority [1, 2] + 1"));
+ assertThat(Priority.create(1, 2).next().next().toString(), equalTo("priority [1, 2] + 2"));
+ }
+}
diff --git a/sangria-core/src/test/java/com/tavianator/sangria/core/TypeLiteralsTest.java b/sangria-core/src/test/java/com/tavianator/sangria/core/TypeLiteralsTest.java
new file mode 100644
index 0000000..3e7cdb8
--- /dev/null
+++ b/sangria-core/src/test/java/com/tavianator/sangria/core/TypeLiteralsTest.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.core;
+
+import java.util.*;
+
+import javax.inject.Provider;
+
+import com.google.inject.TypeLiteral;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for {@link TypeLiterals}.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.1
+ * @since 1.1
+ */
+public class TypeLiteralsTest {
+ @Test
+ public void testListOf() {
+ assertThat(TypeLiterals.listOf(String.class),
+ equalTo(new TypeLiteral<List<String>>() { }));
+ assertThat(TypeLiterals.listOf(new TypeLiteral<Class<?>>() { }),
+ equalTo(new TypeLiteral<List<Class<?>>>() { }));
+ }
+
+ @Test
+ public void testSetOf() {
+ assertThat(TypeLiterals.setOf(String.class),
+ equalTo(new TypeLiteral<Set<String>>() { }));
+ assertThat(TypeLiterals.setOf(new TypeLiteral<Class<?>>() { }),
+ equalTo(new TypeLiteral<Set<Class<?>>>() { }));
+ }
+
+ @Test
+ public void testMapOf() {
+ assertThat(TypeLiterals.mapOf(String.class, String.class),
+ equalTo(new TypeLiteral<Map<String, String>>() { }));
+ assertThat(TypeLiterals.mapOf(String.class, new TypeLiteral<Class<?>>() { }),
+ equalTo(new TypeLiteral<Map<String, Class<?>>>() { }));
+ assertThat(TypeLiterals.mapOf(new TypeLiteral<Class<?>>() { }, String.class),
+ equalTo(new TypeLiteral<Map<Class<?>, String>>() { }));
+ assertThat(TypeLiterals.mapOf(new TypeLiteral<Class<?>>() { }, new TypeLiteral<Class<?>>() { }),
+ equalTo(new TypeLiteral<Map<Class<?>, Class<?>>>() { }));
+ }
+
+ @Test
+ public void testProviderOf() {
+ assertThat(TypeLiterals.providerOf(String.class),
+ equalTo(new TypeLiteral<Provider<String>>() { }));
+ assertThat(TypeLiterals.providerOf(new TypeLiteral<Class<?>>() { }),
+ equalTo(new TypeLiteral<Provider<Class<?>>>() { }));
+ }
+
+ /**
+ * Needed to avoid compilation error to to inferred type being anonymous class.
+ */
+ private static <T> Matcher<TypeLiteral<T>> equalTo(TypeLiteral<T> type) {
+ return Matchers.equalTo(type);
+ }
+}
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 @@
+<?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.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>sangria-listbinder</artifactId>
+ <packaging>jar</packaging>
+ <name>Sangria ListBinder</name>
+ <description>A multi-binder with ordering guarantees</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.inject.extensions</groupId>
+ <artifactId>guice-multibindings</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-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<T> extends ListBinderBuilder<T> {
+ /**
+ * Make a binder for an annotated list type.
+ *
+ * @param annotationType The annotation type for the list.
+ * @return A fluent builder.
+ */
+ ListBinderBuilder<T> annotatedWith(Class<? extends Annotation> annotationType);
+
+ /**
+ * Make a binder for an annotated list type.
+ *
+ * @param annotation The annotation instance for the list.
+ * @return A fluent builder.
+ */
+ ListBinderBuilder<T> 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..d813b7a
--- /dev/null
+++ b/sangria-listbinder/src/main/java/com/tavianator/sangria/listbinder/ListBinder.java
@@ -0,0 +1,427 @@
+/****************************************************************************
+ * 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.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.
+ *
+ * <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&lt;String&gt; 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&lt;String&gt;}, which contains {@code "a"} followed by {@code "b"}. It
+ * also creates a binding for {@code List&lt;Provider&lt;String&gt;&gt;} &mdash; 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&lt;String&gt; listBinder = ListBinder.build(binder(), String.class)
+ * .annotatedWith(Names.named("name"))
+ * .withDefaultPriority();
+ * </pre>
+ *
+ * <p>
+ * and the created binding will be {@code @Named("name") List&lt;String&gt;} 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&lt;String&gt; listBinder1 = ListBinder.build(binder(), String.class)
+ * .withPriority(0);
+ * listBinder1.addBinding().toInstance("a");
+ * listBinder1.addBinding().toInstance("b");
+ *
+ * // ... some other module
+ * ListBinder&lt;String&gt; 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&lt;Provider&lt;T&gt;&gt;}.
+ */
+ 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&lt;T&gt;}, in terms of {@code List&lt;Provider&lt;T&gt;&gt;}.
+ */
+ 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 Objects.hashCode(providerListKey);
+ }
+ }
+
+ /**
+ * 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);
+ }
+}
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<T> {
+ /**
+ * @return A {@link ListBinder} with the default priority.
+ * @see Priority
+ */
+ ListBinder<T> withDefaultPriority();
+
+ /**
+ * @return A {@link ListBinder} with the given priority.
+ * @see Priority
+ */
+ ListBinder<T> 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 <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 com.google.inject.Key;
+import com.google.inject.spi.Message;
+
+import com.tavianator.sangria.core.Priority;
+
+/**
+ * Error holder for {@link ListBinder}s.
+ *
+ * @param <T> Only used to allow different {@link Key}s.
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.1
+ * @since 1.1
+ */
+class ListBinderErrors<T> {
+ 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 <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 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<T> implements Comparable<ListElement<T>> {
+ final Key<T> key;
+ final Priority priority;
+
+ ListElement(Key<T> key, Priority priority) {
+ this.key = key;
+ this.priority = priority;
+ }
+
+ @Override
+ public int compareTo(ListElement<T> 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 <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. *
+ ****************************************************************************/
+
+/**
+ * {@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 <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.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<String>> LIST_OF_STRINGS = TypeLiterals.listOf(String.class);
+ private static final TypeLiteral<List<Provider<String>>> 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<String> 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<String> list = injector.getInstance(Key.get(LIST_OF_STRINGS));
+ assertThat(list, contains("a", "b", "c"));
+
+ List<Provider<String>> 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<String> listBinder = ListBinder.build(binder(), String.class)
+ .withPriority(1);
+ listBinder.addBinding().toInstance("c");
+ listBinder.addBinding().toInstance("d");
+ }
+ }, new AbstractModule() {
+ @Override
+ protected void configure() {
+ ListBinder<String> listBinder = ListBinder.build(binder(), String.class)
+ .withPriority(0);
+ listBinder.addBinding().toInstance("a");
+ listBinder.addBinding().toInstance("b");
+ }
+ });
+ List<String> 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<java.lang.String> with default priority"));
+ thrown.expectMessage(containsString("2) Duplicate ListBinder<java.lang.String> 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<java.lang.String> with priority [1]"));
+ thrown.expectMessage(containsString("2) Duplicate ListBinder<java.lang.String> 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<java.lang.String> with default priority conflicts with ListBinder with explicit priority"));
+ thrown.expectMessage(containsString(") ListBinder<java.lang.String> 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<String> listBinder = ListBinder.build(binder(), String.class)
+ .annotatedWith(Names.named("name"))
+ .withDefaultPriority();
+ assertThat(listBinder.toString(), equalTo("ListBinder<java.lang.String> annotated with @com.google.inject.name.Named(value=name) with default priority"));
+
+ ListBinder<Object> objectListBinder = ListBinder.build(binder(), Object.class)
+ .withPriority(1, 2);
+ assertThat(objectListBinder.toString(), equalTo("ListBinder<java.lang.Object> with priority [1, 2]"));
+ }
+ });
+ }
+}
diff --git a/sangria-log4j/pom.xml b/sangria-log4j/pom.xml
index 085a007..5dae892 100644
--- a/sangria-log4j/pom.xml
+++ b/sangria-log4j/pom.xml
@@ -7,7 +7,7 @@
<parent>
<groupId>com.tavianator.sangria</groupId>
<artifactId>sangria</artifactId>
- <version>1.0</version>
+ <version>1.1-SNAPSHOT</version>
</parent>
<artifactId>sangria-log4j</artifactId>
diff --git a/sangria-slf4j/pom.xml b/sangria-slf4j/pom.xml
index 46cec57..fc69f41 100644
--- a/sangria-slf4j/pom.xml
+++ b/sangria-slf4j/pom.xml
@@ -7,7 +7,7 @@
<parent>
<groupId>com.tavianator.sangria</groupId>
<artifactId>sangria</artifactId>
- <version>1.0</version>
+ <version>1.1-SNAPSHOT</version>
</parent>
<artifactId>sangria-slf4j</artifactId>