Revision: e39158525475
Author: Christian Edward Gruber <cgru...@google.com>
Date: Thu May 16 11:00:54 2013
Log: Change Key so that it upgrades Annotation classes where all
methods have default values into an instance of the Annotation with the
defaults as values, so that:
@Retention(RUNTIME)
@BindingAnnotation @interface AllDefaults {
int hasDefault() default 1;
}
@AllDefaults class Foo {}
void testKey() {
assertEquals(Key.get(Foo.class,
Foo.class.getAnnotation(AllDefaults.class)),
Key.get(Foo.class, AllDefaults.class));
}
Also adds an option to "require exact binding annotations", which disables
the error-prone fallback built into Guice whereby a binding for @Named Foo
can substitute for @Named("foo") Foo if the latter doesn't exist but the
former does.
-----------------
Manually Synced.
COMMIT=45600016
http://code.google.com/p/google-guice/source/detail?r=e39158525475
Added:
/core/src/com/google/inject/spi/RequireExactBindingAnnotationsOption.java
Modified:
/core/src/com/google/inject/Binder.java
/core/src/com/google/inject/Key.java
/core/src/com/google/inject/internal/Annotations.java
/core/src/com/google/inject/internal/InjectorImpl.java
/core/src/com/google/inject/internal/InjectorOptionsProcessor.java
/core/src/com/google/inject/spi/DefaultElementVisitor.java
/core/src/com/google/inject/spi/ElementVisitor.java
/core/src/com/google/inject/spi/Elements.java
/core/test/com/google/inject/BindingAnnotationTest.java
/core/test/com/google/inject/KeyTest.java
/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java
/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java
=======================================
--- /dev/null
+++
/core/src/com/google/inject/spi/RequireExactBindingAnnotationsOption.java
Thu May 16 11:00:54 2013
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.inject.spi;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.inject.Binder;
+
+/**
+ * A request to require exact binding annotations.
+ *
+ * @author sa...@google.com (Sam Berlin)
+ * @since 4.0
+ */
+public final class RequireExactBindingAnnotationsOption implements Element
{
+ private final Object source;
+
+ RequireExactBindingAnnotationsOption(Object source) {
+ this.source = checkNotNull(source, "source");
+ }
+
+ public Object getSource() {
+ return source;
+ }
+
+ public void applyTo(Binder binder) {
+ binder.withSource(getSource()).requireExactBindingAnnotations();
+ }
+
+ public <T> T acceptVisitor(ElementVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+}
=======================================
--- /core/src/com/google/inject/Binder.java Thu May 31 16:54:04 2012
+++ /core/src/com/google/inject/Binder.java Thu May 16 11:00:54 2013
@@ -483,4 +483,14 @@
* @since 4.0
*/
void requireAtInjectOnConstructors();
+
+ /**
+ * Requires that Guice finds an exactly matching binding annotation.
This disables the
+ * error-prone feature in Guice where it can substitute a binding for
+ * <code>{@literal @}Named Foo</code> when attempting to inject
+ * <code>{@literal @}Named("foo") Foo</code>.
+ *
+ * @since 4.0
+ */
+ void requireExactBindingAnnotations();
}
=======================================
--- /core/src/com/google/inject/Key.java Thu Jul 7 17:34:16 2011
+++ /core/src/com/google/inject/Key.java Thu May 16 11:00:54 2013
@@ -18,6 +18,8 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.inject.internal.Annotations.generateAnnotation;
+import static com.google.inject.internal.Annotations.isAllDefaultMethods;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.MoreTypes;
@@ -350,10 +352,15 @@
* Gets the strategy for an annotation type.
*/
static AnnotationStrategy strategyFor(Class<? extends Annotation>
annotationType) {
+ annotationType = Annotations.canonicalizeIfNamed(annotationType);
+ if (isAllDefaultMethods(annotationType)) {
+ return strategyFor(generateAnnotation(annotationType));
+ }
+
checkNotNull(annotationType, "annotation type");
ensureRetainedAtRuntime(annotationType);
ensureIsBindingAnnotation(annotationType);
- return new
AnnotationTypeStrategy(Annotations.canonicalizeIfNamed(annotationType),
null);
+ return new AnnotationTypeStrategy(annotationType, null);
}
=======================================
--- /core/src/com/google/inject/internal/Annotations.java Thu Jul 7
17:34:16 2011
+++ /core/src/com/google/inject/internal/Annotations.java Thu May 16
11:00:54 2013
@@ -17,7 +17,12 @@
package com.google.inject.internal;
import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Joiner.MapJoiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MapMaker;
+import com.google.common.collect.Maps;
import com.google.inject.BindingAnnotation;
import com.google.inject.Key;
import com.google.inject.ScopeAnnotation;
@@ -29,7 +34,10 @@
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
@@ -49,6 +57,115 @@
public static boolean isMarker(Class<? extends Annotation>
annotationType) {
return annotationType.getDeclaredMethods().length == 0;
}
+
+ public static boolean isAllDefaultMethods(Class<? extends Annotation>
annotationType) {
+ boolean hasMethods = false;
+ for (Method m : annotationType.getDeclaredMethods()) {
+ hasMethods = true;
+ if (m.getDefaultValue() == null) {
+ return false;
+ }
+ }
+ return hasMethods;
+ }
+
+ private static final Map<Class<? extends Annotation>, Annotation> cache =
+ new MapMaker().weakKeys().makeComputingMap(
+ new Function<Class<? extends Annotation>, Annotation>() {
+ @Override
+ public Annotation apply(Class<? extends Annotation> input) {
+ return generateAnnotationImpl(input);
+ }
+ });
+
+ /**
+ * Generates an Annotation for the annotation class. Requires that the
annotation is all
+ * optionals.
+ */
+ public static <T extends Annotation> T generateAnnotation(Class<T>
annotationType) {
+ Preconditions.checkState(
+ isAllDefaultMethods(annotationType), "%s is not all default
methods", annotationType);
+ return (T)cache.get(annotationType);
+ }
+
+ private static <T extends Annotation> T generateAnnotationImpl(final
Class<T> annotationType) {
+ final Map<String, Object> members = resolveMembers(annotationType);
+ return annotationType.cast(Proxy.newProxyInstance(
+ annotationType.getClassLoader(),
+ new Class<?>[] { annotationType },
+ new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args)
throws Exception {
+ String name = method.getName();
+ if (name.equals("annotationType")) {
+ return annotationType;
+ } else if (name.equals("toString")) {
+ return annotationToString(annotationType, members);
+ } else if (name.equals("hashCode")) {
+ return annotationHashCode(annotationType, members);
+ } else if (name.equals("equals")) {
+ return annotationEquals(annotationType, members, args[0]);
+ } else {
+ return members.get(name);
+ }
+ }
+ }));
+ }
+
+ private static ImmutableMap<String, Object> resolveMembers(
+ Class<? extends Annotation> annotationType) {
+ ImmutableMap.Builder<String, Object> result = ImmutableMap.builder();
+ for (Method method : annotationType.getDeclaredMethods()) {
+ result.put(method.getName(), method.getDefaultValue());
+ }
+ return result.build();
+ }
+
+ /** Implements {@link Annotation#equals}. */
+ private static boolean annotationEquals(Class<? extends Annotation> type,
+ Map<String, Object> members, Object other) throws Exception {
+ if (!type.isInstance(other)) {
+ return false;
+ }
+ for (Method method : type.getDeclaredMethods()) {
+ String name = method.getName();
+ if (!Arrays.deepEquals(
+ new Object[] {method.invoke(other)}, new Object[]
{members.get(name)})) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** Implements {@link Annotation#hashCode}. */
+ private static int annotationHashCode(Class<? extends Annotation> type,
+ Map<String, Object> members) throws Exception {
+ int result = 0;
+ for (Method method : type.getDeclaredMethods()) {
+ String name = method.getName();
+ Object value = members.get(name);
+ result += (127 * name.hashCode()) ^ (Arrays.deepHashCode(new
Object[] {value}) - 31);
+ }
+ return result;
+ }
+
+ private static final MapJoiner JOINER =
Joiner.on(", ").withKeyValueSeparator("=");
+
+ private static final Function<Object, String> DEEP_TO_STRING_FN = new
Function<Object, String>() {
+ @Override
+ public String apply(Object arg) {
+ String s = Arrays.deepToString(new Object[] {arg});
+ return s.substring(1, s.length() - 1); // cut off brackets
+ }
+ };
+
+ /** Implements {@link Annotation#toString}. */
+ private static String annotationToString(Class<? extends Annotation>
type,
+ Map<String, Object> members) throws Exception {
+ StringBuilder sb = new
StringBuilder().append("@").append(type.getName()).append("(");
+ JOINER.appendTo(sb, Maps.transformValues(members, DEEP_TO_STRING_FN));
+ return sb.append(")").toString();
+ }
/**
* Returns true if the given annotation is retained at runtime.
=======================================
--- /core/src/com/google/inject/internal/InjectorImpl.java Thu May 31
16:54:04 2012
+++ /core/src/com/google/inject/internal/InjectorImpl.java Thu May 16
11:00:54 2013
@@ -72,13 +72,15 @@
final boolean jitDisabled;
final boolean disableCircularProxies;
final boolean atInjectRequired;
+ final boolean exactBindingAnnotationsRequired;
InjectorOptions(Stage stage, boolean jitDisabled, boolean
disableCircularProxies,
- boolean atInjectRequired) {
+ boolean atInjectRequired, boolean exactBindingAnnotationsRequired)
{
this.stage = stage;
this.jitDisabled = jitDisabled;
this.disableCircularProxies = disableCircularProxies;
this.atInjectRequired = atInjectRequired;
+ this.exactBindingAnnotationsRequired =
exactBindingAnnotationsRequired;
}
@Override
@@ -88,6 +90,7 @@
.add("jitDisabled", jitDisabled)
.add("disableCircularProxies", disableCircularProxies)
.add("atInjectRequired", atInjectRequired)
+ .add("exactBindingAnnotationsRequired",
exactBindingAnnotationsRequired)
.toString();
}
}
@@ -853,7 +856,7 @@
// If the key has an annotation...
if (key.getAnnotationType() != null) {
// Look for a binding without annotation attributes or return null.
- if (key.hasAttributes()) {
+ if (key.hasAttributes() && !options.exactBindingAnnotationsRequired)
{
try {
Errors ignored = new Errors();
return getBindingOrThrow(key.withoutAttributes(), ignored,
JitLimitation.NO_JIT);
=======================================
--- /core/src/com/google/inject/internal/InjectorOptionsProcessor.java Thu
May 31 16:54:04 2012
+++ /core/src/com/google/inject/internal/InjectorOptionsProcessor.java Thu
May 16 11:00:54 2013
@@ -23,6 +23,7 @@
import com.google.inject.internal.InjectorImpl.InjectorOptions;
import com.google.inject.spi.DisableCircularProxiesOption;
import com.google.inject.spi.RequireAtInjectOnConstructorsOption;
+import com.google.inject.spi.RequireExactBindingAnnotationsOption;
import com.google.inject.spi.RequireExplicitBindingsOption;
/**
@@ -35,6 +36,7 @@
private boolean disableCircularProxies = false;
private boolean jitDisabled = false;
private boolean atInjectRequired = false;
+ private boolean exactBindingAnnotationsRequired = false;
InjectorOptionsProcessor(Errors errors) {
super(errors);
@@ -57,6 +59,12 @@
atInjectRequired = true;
return true;
}
+
+ @Override
+ public Boolean visit(RequireExactBindingAnnotationsOption option) {
+ exactBindingAnnotationsRequired = true;
+ return true;
+ }
InjectorOptions getOptions(Stage stage, InjectorOptions parentOptions) {
checkNotNull(stage, "stage must be set");
@@ -65,14 +73,16 @@
stage,
jitDisabled,
disableCircularProxies,
- atInjectRequired);
+ atInjectRequired,
+ exactBindingAnnotationsRequired);
} else {
checkState(stage == parentOptions.stage, "child & parent stage don't
match");
return new InjectorOptions(
stage,
jitDisabled || parentOptions.jitDisabled,
disableCircularProxies || parentOptions.disableCircularProxies,
- atInjectRequired || parentOptions.atInjectRequired);
+ atInjectRequired || parentOptions.atInjectRequired,
+ exactBindingAnnotationsRequired ||
parentOptions.exactBindingAnnotationsRequired);
}
}
=======================================
--- /core/src/com/google/inject/spi/DefaultElementVisitor.java Thu May 31
16:54:04 2012
+++ /core/src/com/google/inject/spi/DefaultElementVisitor.java Thu May 16
11:00:54 2013
@@ -98,4 +98,8 @@
public V visit(RequireAtInjectOnConstructorsOption option) {
return visitOther(option);
}
+
+ public V visit(RequireExactBindingAnnotationsOption option) {
+ return visitOther(option);
+ }
}
=======================================
--- /core/src/com/google/inject/spi/ElementVisitor.java Thu May 31 16:54:04
2012
+++ /core/src/com/google/inject/spi/ElementVisitor.java Thu May 16 11:00:54
2013
@@ -113,4 +113,11 @@
* @since 4.0
*/
V visit(RequireAtInjectOnConstructorsOption option);
+
+ /**
+ * Visit a require exact binding annotations command.
+ *
+ * @since 4.0
+ */
+ V visit(RequireExactBindingAnnotationsOption option);
}
=======================================
--- /core/src/com/google/inject/spi/Elements.java Thu May 31 16:54:04 2012
+++ /core/src/com/google/inject/spi/Elements.java Thu May 16 11:00:54 2013
@@ -319,6 +319,10 @@
public void requireAtInjectOnConstructors() {
elements.add(new RequireAtInjectOnConstructorsOption(getSource()));
}
+
+ public void requireExactBindingAnnotations() {
+ elements.add(new RequireExactBindingAnnotationsOption(getSource()));
+ }
public void expose(Key<?> key) {
exposeInternal(key);
=======================================
--- /core/test/com/google/inject/BindingAnnotationTest.java Thu Jul 7
17:34:16 2011
+++ /core/test/com/google/inject/BindingAnnotationTest.java Thu May 16
11:00:54 2013
@@ -29,27 +29,75 @@
*/
public class BindingAnnotationTest extends TestCase {
- public void testAnnotationWithValueMatchesKeyWithTypeOnly() throws
- CreationException {
+ public void testAnnotationWithValueMatchesKeyWithTypeOnly() throws
CreationException {
Injector c = Guice.createInjector(new AbstractModule() {
+ @Override
protected void configure() {
bindConstant().annotatedWith(Blue.class).to("foo");
- bind(Foo.class);
+ bind(BlueFoo.class);
}
});
- Foo foo = c.getInstance(Foo.class);
+ BlueFoo foo = c.getInstance(BlueFoo.class);
assertEquals("foo", foo.s);
}
+
+ public void testRequireExactAnnotationsDisablesFallback() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ binder().requireExactBindingAnnotations();
+ bindConstant().annotatedWith(Blue.class).to("foo");
+ bind(BlueFoo.class);
+ }
+ });
+ fail();
+ } catch (CreationException expected) {
+ assertContains(expected.getMessage(), "No implementation for
java.lang.String annotated with",
+ "BindingAnnotationTest$Blue(value=5) was bound",
+ "at " +
BindingAnnotationTest.class.getName(), ".configure(BindingAnnotationTest.java:");
+ }
+ }
+
+ public void testRequireExactAnnotationsDoesntBreakIfDefaultsExist() {
+ Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ binder().requireExactBindingAnnotations();
+ bindConstant().annotatedWith(Red.class).to("foo");
+ bind(RedFoo.class);
+ }
+ }).getInstance(RedFoo.class);
+ }
+
+ public void testRequireExactAnnotationsRequireAllOptionals() {
+ try {
+ Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ binder().requireExactBindingAnnotations();
+ bindConstant().annotatedWith(Color.class).to("foo");
+ bind(ColorFoo.class);
+ }
+ });
+ fail();
+ } catch (CreationException expected) {
+ assertContains(expected.getMessage(), "No implementation for
java.lang.String annotated with",
+ "BindingAnnotationTest$Color",
+ "at " +
BindingAnnotationTest.class.getName(), ".configure(BindingAnnotationTest.java:");
+ }
+ }
public void testAnnotationWithValueThatDoesntMatch() {
try {
Guice.createInjector(new AbstractModule() {
+ @Override
protected void configure() {
bindConstant().annotatedWith(createBlue(6)).to("six");
bind(String.class).toInstance("bar");
- bind(Foo.class);
+ bind(BlueFoo.class);
}
});
fail();
@@ -60,15 +108,39 @@
}
}
- static class Foo {
+ static class BlueFoo {
@Inject @Blue(5) String s;
}
+
+ static class RedFoo {
+ @Inject @Red String s;
+ }
+
+ static class ColorFoo {
+ @Inject @Color(b=2) String s;
+ }
@Retention(RUNTIME)
@BindingAnnotation
@interface Blue {
int value();
}
+
+ @Retention(RUNTIME)
+ @BindingAnnotation
+ @interface Red {
+ int r() default 42;
+ int g() default 42;
+ int b() default 42;
+ }
+
+ @Retention(RUNTIME)
+ @BindingAnnotation
+ @interface Color {
+ int r() default 0;
+ int g() default 0;
+ int b();
+ }
public Blue createBlue(final int value) {
return new Blue() {
=======================================
--- /core/test/com/google/inject/KeyTest.java Thu Jul 7 17:34:16 2011
+++ /core/test/com/google/inject/KeyTest.java Thu May 16 11:00:54 2013
@@ -203,4 +203,47 @@
class HasTypeParameters<A, B extends List<A> & Runnable, C extends
Runnable> {
A a; B b; C c;
}
+
+ public void testKeysWithDefaultAnnotations() {
+ AllDefaults allDefaults =
HasAnnotations.class.getAnnotation(AllDefaults.class);
+ assertEquals(Key.get(Foo.class, allDefaults), Key.get(Foo.class,
AllDefaults.class));
+
+ Marker marker = HasAnnotations.class.getAnnotation(Marker.class);
+ assertEquals(Key.get(Foo.class, marker), Key.get(Foo.class,
Marker.class));
+
+ Key<?> noDefaults = Key.get(Foo.class, NoDefaults.class);
+ assertNull(noDefaults.getAnnotation());
+ assertEquals(NoDefaults.class, noDefaults.getAnnotationType());
+
+ Key<?> someDefaults = Key.get(Foo.class, SomeDefaults.class);
+ assertNull(someDefaults.getAnnotation());
+ assertEquals(SomeDefaults.class, someDefaults.getAnnotationType());
+ }
+
+ @Retention(RUNTIME)
+ @BindingAnnotation @interface AllDefaults {
+ int v1() default 1;
+ String v2() default "foo";
+ }
+
+ @Retention(RUNTIME)
+ @BindingAnnotation @interface SomeDefaults {
+ int v1() default 1;
+ String v2() default "foo";
+ Class<?> clazz();
+ }
+
+ @Retention(RUNTIME)
+ @BindingAnnotation @interface NoDefaults {
+ int value();
+ }
+
+ @Retention(RUNTIME)
+ @BindingAnnotation @interface Marker {
+ }
+
+ @AllDefaults
+ @Marker
+ class HasAnnotations {}
+
}
=======================================
---
/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java
Wed May 15 19:11:47 2013
+++
/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java
Thu May 16 11:00:54 2013
@@ -41,6 +41,7 @@
import com.google.inject.internal.BytecodeGen;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
+import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.internal.util.Classes;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
@@ -576,7 +577,7 @@
final Key<?> returnType = data.returnType;
// We ignore any pre-existing binding annotation.
- final Key<?> assistedReturnType = Key.get(returnType.getTypeLiteral(),
Assisted.class);
+ final Key<?> returnKey = Key.get(returnType.getTypeLiteral(),
UniqueAnnotations.create());
Module assistedModule = new AbstractModule() {
@Override @SuppressWarnings("unchecked") // raw keys are necessary
for the args array and return value
@@ -601,7 +602,7 @@
// but if it isn't, we'll end up throwing a fairly good error
// message for the user.
if(constructor != null) {
- binder.bind(assistedReturnType)
+ binder.bind(returnKey)
.toConstructor(constructor,
(TypeLiteral)data.implementationType)
.in(Scopes.NO_SCOPE); // make sure we erase any scope on the
implementation type
}
@@ -609,7 +610,7 @@
};
Injector forCreate = injector.createChildInjector(assistedModule);
- Binding binding = forCreate.getBinding(assistedReturnType);
+ Binding binding = forCreate.getBinding(returnKey);
// If we have providers cached in data, cache the binding for future
optimizations.
if(data.optimized) {
data.cachedBinding = binding;
=======================================
---
/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java
Sun May 27 10:38:30 2012
+++
/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java
Thu May 16 11:00:54 2013
@@ -1040,5 +1040,31 @@
Color getColor() { return injector.getInstance(Key.get(Color.class,
FactoryProvider2.DEFAULT_ANNOTATION)); }
}
+ public void testReturnValueMatchesParamValue() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ public void configure() {
+ install(new FactoryModuleBuilder().build(Delegater.Factory.class));
+ }
+ });
+ Delegater delegate = new Delegater();
+ Delegater user =
injector.getInstance(Delegater.Factory.class).create(delegate);
+ assertSame(delegate, user.delegate);
+ }
+
+ static class Delegater {
+ interface Factory {
+ Delegater create(Delegater delegate);
+ }
+
+ private final Delegater delegate;
+ @Inject Delegater(@Assisted Delegater delegater) {
+ this.delegate = delegater;
+ }
+
+ Delegater() {
+ this.delegate = null;
+ }
+ }
}
--
You received this message because you are subscribed to the Google Groups
"google-guice-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to google-guice-dev+unsubscr...@googlegroups.com.
To post to this group, send email to google-guice-dev@googlegroups.com.
Visit this group at http://groups.google.com/group/google-guice-dev?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.