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);
     }
   }