From 1eeacfdd9d747c2add6957794e541e3a022218a7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 7 May 2014 21:32:35 -0400 Subject: core: Add PotentialAnnotation helper to store different annotation possibilities (none, type, insance). --- .../sangria/core/PotentialAnnotation.java | 241 +++++++++++++++++++++ .../sangria/core/PotentialAnnotationTest.java | 126 +++++++++++ 2 files changed, 367 insertions(+) create mode 100644 sangria-core/src/main/java/com/tavianator/sangria/core/PotentialAnnotation.java create mode 100644 sangria-core/src/test/java/com/tavianator/sangria/core/PotentialAnnotationTest.java (limited to 'sangria-core') 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 * + * * + * 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 The type to return. + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.1 + * @since 1.1 + */ + public interface Visitor { + /** + * 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 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 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 The type of the key to create. + * @return A {@link Key}. + */ + public Key getKey(Class 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 The type of the key to create. + * @return A {@link Key}. + */ + public abstract Key getKey(TypeLiteral type); + + /** + * Accept a {@link Visitor}. + * + * @param visitor The visitor to accept. + * @param The type for the visitor to return. + * @return The value produced by the visitor. + */ + public abstract T accept(Visitor visitor); + + @Override + public abstract String toString(); + + /** + * Implementation of {@link #none()}. + */ + private static class NoAnnotation extends PotentialAnnotation { + @Override + public PotentialAnnotation annotatedWith(Class annotationType) { + return new AnnotationType(annotationType); + } + + @Override + public PotentialAnnotation annotatedWith(Annotation annotation) { + return new AnnotationInstance(annotation); + } + + @Override + public boolean hasAnnotation() { + return false; + } + + @Override + public Key getKey(TypeLiteral type) { + return Key.get(type); + } + + @Override + public T accept(Visitor 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 annotationType; + + AnnotationType(Class annotationType) { + this.annotationType = annotationType; + } + + @Override + public boolean hasAnnotation() { + return true; + } + + @Override + public Key getKey(TypeLiteral type) { + return Key.get(type, annotationType); + } + + @Override + public T accept(Visitor 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 Key getKey(TypeLiteral type) { + return Key.get(type, annotation); + } + + @Override + public T accept(Visitor 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 * + * * + * 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() { })); + assertThat(none.annotatedWith(Simple.class).getKey(String.class), + equalTo(new Key(Simple.class) { })); + assertThat(none.annotatedWith(nameAnnotation).getKey(String.class), + equalTo(new Key(nameAnnotation) { })); + } + + @Test + public void testVisitor() { + PotentialAnnotation.Visitor visitor = new PotentialAnnotation.Visitor() { + @Override + public String visitNoAnnotation() { + return "none"; + } + + @Override + public String visitAnnotationType(Class 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 Matcher> equalTo(Key key) { + return Matchers.equalTo(key); + } + + private static Matcher equalTo(T object) { + return Matchers.equalTo(object); + } +} -- cgit v1.2.3