diff options
Diffstat (limited to 'sangria-core/src/main/java')
5 files changed, 487 insertions, 3 deletions
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; |