Added checks to ensure annotations are retained at runtime.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@212 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/BinderImpl.java b/src/com/google/inject/BinderImpl.java
index 17ed717..6abf829 100644
--- a/src/com/google/inject/BinderImpl.java
+++ b/src/com/google/inject/BinderImpl.java
@@ -24,6 +24,8 @@
import com.google.inject.spi.SourceProviders;
import static com.google.inject.util.Objects.nonNull;
import com.google.inject.util.Stopwatch;
+import com.google.inject.util.StackTraceElements;
+import com.google.inject.util.Annotations;
import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
@@ -147,11 +149,16 @@
public void bindScope(Class<? extends Annotation> annotationType,
Scope scope) {
if (!Scopes.isScopeAnnotation(annotationType)) {
- addError(source(), ErrorMessages.MISSING_SCOPE_ANNOTATION,
- "@" + annotationType.getSimpleName());
+ addError(StackTraceElements.forType(annotationType),
+ ErrorMessages.MISSING_SCOPE_ANNOTATION);
// Go ahead and bind anyway so we don't get collateral errors.
}
+ if (!Annotations.isRetainedAtRuntime(annotationType)) {
+ addError(StackTraceElements.forType(annotationType),
+ ErrorMessages.MISSING_RUNTIME_RETENTION, source());
+ }
+
Scope existing = scopes.get(nonNull(annotationType, "annotation type"));
if (existing != null) {
addError(source(), ErrorMessages.DUPLICATE_SCOPES, existing,
@@ -162,10 +169,9 @@
}
}
-
-
public <T> BindingBuilderImpl<T> bind(Key<T> key) {
- BindingBuilderImpl<T> builder = new BindingBuilderImpl<T>(this, key, source());
+ BindingBuilderImpl<T> builder =
+ new BindingBuilderImpl<T>(this, key, source());
bindingBuilders.add(builder);
return builder;
}
diff --git a/src/com/google/inject/BindingBuilderImpl.java b/src/com/google/inject/BindingBuilderImpl.java
index 7667dc3..95097e2 100644
--- a/src/com/google/inject/BindingBuilderImpl.java
+++ b/src/com/google/inject/BindingBuilderImpl.java
@@ -5,6 +5,8 @@
import com.google.inject.binder.BindingScopeBuilder;
import com.google.inject.util.Objects;
import com.google.inject.util.ToStringBuilder;
+import com.google.inject.util.StackTraceElements;
+import com.google.inject.util.Annotations;
import java.lang.annotation.Annotation;
import java.util.logging.Logger;
@@ -44,7 +46,13 @@
if (this.key.hasAnnotationType()) {
binder.addError(source, ErrorMessages.ANNOTATION_ALREADY_SPECIFIED);
} else {
- this.key = Key.get(this.key.getType(), annotationType);
+ if (!Annotations.isRetainedAtRuntime(annotationType)) {
+ binder.addError(StackTraceElements.forType(annotationType),
+ ErrorMessages.MISSING_RUNTIME_RETENTION, binder.source());
+ }
+ else {
+ this.key = Key.get(this.key.getType(), annotationType);
+ }
}
return this;
}
@@ -53,8 +61,14 @@
if (this.key.hasAnnotationType()) {
binder.addError(source, ErrorMessages.ANNOTATION_ALREADY_SPECIFIED);
} else {
- // not test-covered?
- this.key = Key.get(this.key.getType(), annotation);
+ if (!Annotations.isRetainedAtRuntime(annotation.annotationType())) {
+ binder.addError(StackTraceElements.forType(annotation.annotationType()),
+ ErrorMessages.MISSING_RUNTIME_RETENTION, binder.source());
+ }
+ else {
+ // not test-covered?
+ this.key = Key.get(this.key.getType(), annotation);
+ }
}
return this;
}
diff --git a/src/com/google/inject/ErrorMessages.java b/src/com/google/inject/ErrorMessages.java
index ad7e516..d43c2b3 100644
--- a/src/com/google/inject/ErrorMessages.java
+++ b/src/com/google/inject/ErrorMessages.java
@@ -52,7 +52,10 @@
}
}
- static final String MISSING_SCOPE_ANNOTATION = "Please annotate %s with"
+ static final String MISSING_RUNTIME_RETENTION = "Please annotate with"
+ + " @Retention(RUNTIME). Bound at %s.";;
+
+ static final String MISSING_SCOPE_ANNOTATION = "Please annotate with"
+ " @ScopeAnnotation.";
static final String OPTIONAL_CONSTRUCTOR = "@Inject(optional=true) is"
diff --git a/src/com/google/inject/Key.java b/src/com/google/inject/Key.java
index 2cb1cf8..2c42c43 100644
--- a/src/com/google/inject/Key.java
+++ b/src/com/google/inject/Key.java
@@ -19,6 +19,7 @@
import static com.google.inject.util.Objects.nonNull;
import com.google.inject.util.StackTraceElements;
import com.google.inject.util.ToStringBuilder;
+import com.google.inject.util.Annotations;
import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import java.lang.reflect.Type;
@@ -357,8 +358,10 @@
*/
static AnnotationStrategy strategyFor(Annotation annotation) {
nonNull(annotation, "annotation");
- return isMarker(annotation.annotationType())
- ? new AnnotationTypeStrategy(annotation.annotationType(), annotation)
+ Class<? extends Annotation> annotationType = annotation.annotationType();
+ ensureRetainedAtRuntime(annotationType);
+ return isMarker(annotationType)
+ ? new AnnotationTypeStrategy(annotationType, annotation)
: new AnnotationInstanceStrategy(annotation);
}
@@ -368,14 +371,27 @@
static AnnotationStrategy strategyFor(
Class<? extends Annotation> annotationType) {
nonNull(annotationType, "annotation type");
+
if (!isMarker(annotationType)) {
throw new IllegalArgumentException(annotationType.getName()
+ " is not a marker annotation, i.e. it has attributes. Please"
+ " use an Annotation instance or a marker annotation instead.");
}
+
+ ensureRetainedAtRuntime(annotationType);
+
return new AnnotationTypeStrategy(annotationType, null);
}
+ private static void ensureRetainedAtRuntime(
+ Class<? extends Annotation> annotationType) {
+ if (!Annotations.isRetainedAtRuntime(annotationType)) {
+ throw new IllegalArgumentException(annotationType.getName()
+ + " is not retained at runtime."
+ + " Please annotate it with @Retention(RUNTIME).");
+ }
+ }
+
// this class not test-covered
static class AnnotationInstanceStrategy implements AnnotationStrategy {
diff --git a/src/com/google/inject/Scopes.java b/src/com/google/inject/Scopes.java
index e839581..a59066a 100644
--- a/src/com/google/inject/Scopes.java
+++ b/src/com/google/inject/Scopes.java
@@ -17,6 +17,8 @@
package com.google.inject;
import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Map;
import com.google.inject.util.StackTraceElements;
diff --git a/src/com/google/inject/util/Annotations.java b/src/com/google/inject/util/Annotations.java
new file mode 100644
index 0000000..d3f8609
--- /dev/null
+++ b/src/com/google/inject/util/Annotations.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2006 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.util;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Annotation utilities.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public class Annotations {
+
+ /**
+ * Returns true if the given annotation is retained at runtime.
+ */
+ public static boolean isRetainedAtRuntime(
+ Class<? extends Annotation> annotationType) {
+ Retention retention = annotationType.getAnnotation(Retention.class);
+ return !(retention == null || retention.value() != RetentionPolicy.RUNTIME);
+ }
+}
diff --git a/src/com/google/inject/util/LineNumbers.java b/src/com/google/inject/util/LineNumbers.java
index d3b8c24..c79a13f 100644
--- a/src/com/google/inject/util/LineNumbers.java
+++ b/src/com/google/inject/util/LineNumbers.java
@@ -34,7 +34,7 @@
private final Class cls;
private Map<String, Integer> lines = new HashMap<String, Integer>();
private String source;
- private int firstLine = 0;
+ private int firstLine = Integer.MAX_VALUE;
/**
* Reads line number information from the given class, if available.
@@ -84,7 +84,7 @@
* Gets the first line number.
*/
public int getFirstLine() {
- return firstLine;
+ return firstLine == Integer.MAX_VALUE ? 1 : firstLine;
}
private static String getKey(Member member) {
@@ -125,7 +125,7 @@
}
public void visitLineNumber(int line, Label start) {
- if (firstLine == 0) {
+ if (line < firstLine) {
firstLine = line;
}
diff --git a/src/com/google/inject/util/StackTraceElements.java b/src/com/google/inject/util/StackTraceElements.java
index d0c9512..c5b34c1 100644
--- a/src/com/google/inject/util/StackTraceElements.java
+++ b/src/com/google/inject/util/StackTraceElements.java
@@ -62,7 +62,7 @@
LineNumbers lineNumbers = lineNumbersCache.get(implementation);
return new StackTraceElement(
implementation.getName(),
- "<none>",
+ "",
lineNumbers.getSource(),
lineNumbers.getFirstLine());
}
diff --git a/test/com/google/inject/ErrorHandlingTest.java b/test/com/google/inject/ErrorHandlingTest.java
index eb8e520..a622c14 100644
--- a/test/com/google/inject/ErrorHandlingTest.java
+++ b/test/com/google/inject/ErrorHandlingTest.java
@@ -32,7 +32,7 @@
}
@Inject @Named("missing")
- static List<String> missing;
+ static List<String> missing = null;
static class Foo {
@Inject
@@ -67,6 +67,8 @@
static class TooManyScopes {
}
+ @interface BadScope {}
+
static class MyModule extends AbstractModule {
protected void configure() {
install(new ServletModule());
@@ -75,10 +77,10 @@
bind(Bar.class);
bind(Tee.class);
bind(new TypeLiteral<List<String>>() {});
- bind(String.class).annotatedWith(Names.named("foo")).in(
- Named.class);
+ bind(String.class).annotatedWith(Names.named("foo")).in(Named.class);
link(Key.get(Runnable.class)).to(Key.get(Runnable.class));
bind(TooManyScopes.class);
+ bindScope(BadScope.class, Scopes.CONTAINER);
requestStaticInjection(ErrorHandlingTest.class);
}
}