summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sangria-core/src/main/java/com/tavianator/sangria/core/PotentialAnnotation.java241
-rw-r--r--sangria-core/src/test/java/com/tavianator/sangria/core/PotentialAnnotationTest.java126
2 files changed, 367 insertions, 0 deletions
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/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);
+ }
+}