diff options
Diffstat (limited to 'sangria-core')
7 files changed, 512 insertions, 0 deletions
diff --git a/sangria-core/pom.xml b/sangria-core/pom.xml new file mode 100644 index 0000000..d628eee --- /dev/null +++ b/sangria-core/pom.xml @@ -0,0 +1,53 @@ +<?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</groupId> + <artifactId>sangria</artifactId> + <version>1.0-SNAPSHOT</version> + </parent> + + <artifactId>sangria-core</artifactId> + <packaging>jar</packaging> + <name>Sangria Core</name> + <description>Common code for Sangria</description> + + <dependencies> + <dependency> + <groupId>com.google.inject</groupId> + <artifactId>guice</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-core/src/main/java/com/tavianator/sangria/core/DelayedError.java b/sangria-core/src/main/java/com/tavianator/sangria/core/DelayedError.java new file mode 100644 index 0000000..4fa9eab --- /dev/null +++ b/sangria-core/src/main/java/com/tavianator/sangria/core/DelayedError.java @@ -0,0 +1,91 @@ +/********************************************************************* + * Sangria * + * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> * + * * + * This library is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +package com.tavianator.sangria.core; + +import javax.inject.Inject; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Binder; +import com.google.inject.CreationException; +import com.google.inject.Injector; +import com.google.inject.spi.Message; + +/** + * Similar to {@link Binder#addError(String, Object...)}, but can be canceled later. Useful for enforcing correct usage + * of fluent APIs. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.0 + * @since 1.0 + */ +public class DelayedError { + private Throwable error; + + /** + * Create a {@link DelayedError}. + * + * @param binder The binder to attach the error to. + * @param message The format string for the message. + * @param args Arguments that will be passed to the format string. + * @return A {@link DelayedError} token that can be canceled later. + * @see Binder#addError(String, Object...) + */ + public static DelayedError create(Binder binder, String message, Object... args) { + return create(binder, new Message(PrettyTypes.format(message, args))); + } + + /** + * Create a {@link DelayedError}. + * + * @param binder The binder to attach the error to. + * @param t The {@link Throwable} that caused this potential error. + * @return A {@link DelayedError} token that can be canceled later. + * @see Binder#addError(Throwable) + */ + public static DelayedError create(Binder binder, Throwable t) { + DelayedError error = new DelayedError(t); + binder.skipSources(DelayedError.class) + .requestInjection(error); + return error; + } + + /** + * Create a {@link DelayedError}. + * + * @param binder The binder to attach the error to. + * @param message The error message. + * @return A {@link DelayedError} token that can be canceled later. + * @see Binder#addError(Message) + */ + public static DelayedError create(Binder binder, Message message) { + // Using CreationException allows Guice to extract the Message and format it nicely + return create(binder, new CreationException(ImmutableList.of(message))); + } + + private DelayedError(Throwable error) { + this.error = error; + } + + /** + * Cancel this error. + */ + public void cancel() { + this.error = null; + } + + @Inject + void inject(Injector injector) throws Throwable { + if (error != null) { + throw error; + } + } +} diff --git a/sangria-core/src/main/java/com/tavianator/sangria/core/PrettyTypes.java b/sangria-core/src/main/java/com/tavianator/sangria/core/PrettyTypes.java new file mode 100644 index 0000000..12721a4 --- /dev/null +++ b/sangria-core/src/main/java/com/tavianator/sangria/core/PrettyTypes.java @@ -0,0 +1,75 @@ +/********************************************************************* + * Sangria * + * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> * + * * + * This library is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +package com.tavianator.sangria.core; + +import com.google.inject.Key; + +/** + * Utility class for pretty-printing messages containing types. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.0 + * @since 1.0 + */ +public class PrettyTypes { + private PrettyTypes() { + // Not for instantiating + } + + /** + * Format a message. + * + * @param message The format string. + * @param args The format arguments, possibly containing {@link Class} or {@link Key} instances to be + * pretty-printed. + * @return A formatted message. + * @see String#format(String, Object...) + */ + public static String format(String message, Object... args) { + // This is like Guice's internal Errors.format() + Object[] prettyArgs = new Object[args.length]; + for (int i = 0; i < args.length; ++i) { + Object arg = args[i]; + Object prettyArg; + + if (arg instanceof Class) { + prettyArg = format((Class<?>)arg); + } else if (arg instanceof Key) { + prettyArg = format((Key<?>)arg); + } else { + prettyArg = arg; + } + + prettyArgs[i] = prettyArg; + } + + return String.format(message, prettyArgs); + } + + private static String format(Class<?> type) { + return type.getCanonicalName(); + } + + private static String format(Key<?> key) { + StringBuilder builder = new StringBuilder(key.getTypeLiteral().toString()); + if (key.getAnnotationType() != null) { + builder.append(" annotated with "); + if (key.getAnnotation() != null) { + builder.append(key.getAnnotation()); + } else { + builder.append("@") + .append(format(key.getAnnotationType())); + } + } + return builder.toString(); + } +} diff --git a/sangria-core/src/main/java/com/tavianator/sangria/core/UniqueAnnotations.java b/sangria-core/src/main/java/com/tavianator/sangria/core/UniqueAnnotations.java new file mode 100644 index 0000000..bfdfe6b --- /dev/null +++ b/sangria-core/src/main/java/com/tavianator/sangria/core/UniqueAnnotations.java @@ -0,0 +1,97 @@ +/********************************************************************* + * Sangria * + * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> * + * * + * This library is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +package com.tavianator.sangria.core; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.atomic.AtomicLong; +import javax.inject.Qualifier; + +import com.google.common.annotations.VisibleForTesting; + +/** + * Re-implementation of Guice's internal UniqueAnnotations utility. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.0 + * @since 1.0 + */ +public class UniqueAnnotations { + private static final AtomicLong SEQUENCE = new AtomicLong(); + + private UniqueAnnotations() { + // Not for instantiating + } + + @Retention(RetentionPolicy.RUNTIME) + @Qualifier + @VisibleForTesting + @interface UniqueAnnotation { + long value(); + } + + /** + * Actual implementation of {@link UniqueAnnotation}. + */ + @SuppressWarnings("ClassExplicitlyAnnotation") + private static class UniqueAnnotationImpl implements UniqueAnnotation { + private final long value; + + UniqueAnnotationImpl(long value) { + this.value = value; + } + + @Override + public long value() { + return value; + } + + public Class<? extends Annotation> annotationType() { + return UniqueAnnotation.class; + } + + @Override + public String toString() { + return "@" + UniqueAnnotation.class.getName() + "(value=" + value + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (!(obj instanceof UniqueAnnotation)) { + return false; + } + + UniqueAnnotation other = (UniqueAnnotation)obj; + return value == other.value(); + } + + @Override + public int hashCode() { + return (127*"value".hashCode()) ^ Long.valueOf(value).hashCode(); + } + } + + /** + * @return An {@link Annotation} that will be unequal to every other annotation. + */ + public static Annotation create() { + return create(SEQUENCE.getAndIncrement()); + } + + @VisibleForTesting + static Annotation create(long value) { + return new UniqueAnnotationImpl(value); + } +} 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 new file mode 100644 index 0000000..500b314 --- /dev/null +++ b/sangria-core/src/test/java/com/tavianator/sangria/core/DelayedErrorTest.java @@ -0,0 +1,80 @@ +/********************************************************************* + * Sangria * + * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> * + * * + * This library is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +package com.tavianator.sangria.core; + +import com.google.inject.AbstractModule; +import com.google.inject.CreationException; +import com.google.inject.Guice; +import com.google.inject.Key; +import com.google.inject.spi.Message; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.hamcrest.Matchers.*; + +/** + * Tests for {@link DelayedError}. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.0 + * @since 1.0 + */ +public class DelayedErrorTest { + public @Rule ExpectedException thrown = ExpectedException.none(); + + @Test + public void testFormatString() { + thrown.expect(CreationException.class); + thrown.expectMessage("Test java.lang.String"); + + // We want the messages from our CreationException to get absorbed into the top-level exception + thrown.expectMessage(not(containsString("CreationException"))); + + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + DelayedError.create(binder(), "Test %s", new Key<String>() { }); + } + }); + } + + @Test + public void testMessage() { + thrown.expect(CreationException.class); + thrown.expectMessage("the message"); + thrown.expectMessage("at the source"); + thrown.expectMessage(not(containsString("CreationException"))); + + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + DelayedError.create(binder(), new Message("the source", "the message")); + } + }); + } + + @Test + public void testThrowable() { + final Throwable cause = new IllegalStateException(); + + thrown.expect(CreationException.class); + thrown.expectCause(is(cause)); + + Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + DelayedError.create(binder(), cause); + } + }); + } +} diff --git a/sangria-core/src/test/java/com/tavianator/sangria/core/PrettyTypesTest.java b/sangria-core/src/test/java/com/tavianator/sangria/core/PrettyTypesTest.java new file mode 100644 index 0000000..c50163b --- /dev/null +++ b/sangria-core/src/test/java/com/tavianator/sangria/core/PrettyTypesTest.java @@ -0,0 +1,66 @@ +/********************************************************************* + * Sangria * + * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> * + * * + * This library is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +package com.tavianator.sangria.core; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.*; +import javax.inject.Qualifier; + +import com.google.inject.Key; +import com.google.inject.TypeLiteral; +import com.google.inject.name.Names; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +/** + * Tests for {@link PrettyTypes}. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.0 + * @since 1.0 + */ +public class PrettyTypesTest { + @Retention(RetentionPolicy.RUNTIME) + @Qualifier + private @interface Simple { + } + + @Test + public void testClasses() { + assertThat(PrettyTypes.format("Class is %s", PrettyTypesTest.class), + equalTo("Class is com.tavianator.sangria.core.PrettyTypesTest")); + + assertThat(PrettyTypes.format("Class is %s", Simple.class), + equalTo("Class is com.tavianator.sangria.core.PrettyTypesTest.Simple")); + } + + @Test + public void testTypeLiterals() { + assertThat(PrettyTypes.format("TypeLiteral is %s", new TypeLiteral<List<String>>() { }), + equalTo("TypeLiteral is java.util.List<java.lang.String>")); + } + + @Test + public void testKeys() { + assertThat(PrettyTypes.format("Key is %s", new Key<List<String>>() { }), + equalTo("Key is java.util.List<java.lang.String>")); + + assertThat(PrettyTypes.format("Key is %s", new Key<List<String>>(Names.named("test")) { }), + equalTo("Key is java.util.List<java.lang.String> annotated with @com.google.inject.name.Named(value=test)")); + + assertThat(PrettyTypes.format("Key is %s", new Key<List<String>>(Simple.class) { }), + equalTo("Key is java.util.List<java.lang.String> annotated with @com.tavianator.sangria.core.PrettyTypesTest.Simple")); + } +} diff --git a/sangria-core/src/test/java/com/tavianator/sangria/core/UniqueAnnotationsTest.java b/sangria-core/src/test/java/com/tavianator/sangria/core/UniqueAnnotationsTest.java new file mode 100644 index 0000000..f08d834 --- /dev/null +++ b/sangria-core/src/test/java/com/tavianator/sangria/core/UniqueAnnotationsTest.java @@ -0,0 +1,50 @@ +/********************************************************************* + * Sangria * + * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> * + * * + * This library is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +package com.tavianator.sangria.core; + +import java.lang.annotation.Annotation; + +import org.junit.Test; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +/** + * Tests for {@link UniqueAnnotations}. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.0 + * @since 1.0 + */ +@UniqueAnnotations.UniqueAnnotation(100) +public class UniqueAnnotationsTest { + @Test + public void testUniqueness() { + Annotation a1 = UniqueAnnotations.create(); + Annotation a2 = UniqueAnnotations.create(); + + assertThat(a1, equalTo(a1)); + assertThat(a2, equalTo(a2)); + + assertThat(a1, not(equalTo(a2))); + assertThat(a2, not(equalTo(a1))); + } + + @Test + public void testEquality() { + Annotation real = getClass().getAnnotation(UniqueAnnotations.UniqueAnnotation.class); + Annotation fake = UniqueAnnotations.create(100); + + assertThat(real, equalTo(fake)); + assertThat(fake, equalTo(real)); + } +} |