Added @ScopeAnnotation. Added Binder.addError(). Removed Scopes.DEFAULT. We now refer to this as 'no' scope.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@211 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/Binder.java b/src/com/google/inject/Binder.java
index eb9d47f..af7c0eb 100644
--- a/src/com/google/inject/Binder.java
+++ b/src/com/google/inject/Binder.java
@@ -89,4 +89,13 @@
    * Gets the current stage.
    */
   Stage currentStage();
+
+  /**
+   * Records an error message which will be presented to the user at a later
+   * time. Unlike throwing an exception, this enable us to continue
+   * configuring the container and discover more errors. Uses {@link
+   * String#format(String, Object[])} to insert the arguments into the
+   * message.
+   */
+  void addError(String message, Object... arguments);
 }
diff --git a/src/com/google/inject/BinderImpl.java b/src/com/google/inject/BinderImpl.java
index 659932d..17ed717 100644
--- a/src/com/google/inject/BinderImpl.java
+++ b/src/com/google/inject/BinderImpl.java
@@ -146,6 +146,12 @@
 
   public void bindScope(Class<? extends Annotation> annotationType,
       Scope scope) {
+    if (!Scopes.isScopeAnnotation(annotationType)) {
+      addError(source(), ErrorMessages.MISSING_SCOPE_ANNOTATION,
+          "@" + annotationType.getSimpleName());
+      // Go ahead and bind anyway so we don't get collateral errors.
+    }
+
     Scope existing = scopes.get(nonNull(annotationType, "annotation type"));
     if (existing != null) {
       addError(source(), ErrorMessages.DUPLICATE_SCOPES, existing,
@@ -156,6 +162,8 @@
     }
   }
 
+
+
   public <T> BindingBuilderImpl<T> bind(Key<T> key) {
     BindingBuilderImpl<T> builder = new BindingBuilderImpl<T>(this, key, source());
     bindingBuilders.add(builder);
@@ -212,6 +220,10 @@
     module.configure(this);
   }
 
+  public void addError(String message, Object... arguments) {
+    configurationErrorHandler.handle(source(), message, arguments);
+  }
+
   void addError(Object source, String message, Object... arguments) {
     configurationErrorHandler.handle(source, message, arguments);
   }
diff --git a/src/com/google/inject/BindingAnnotation.java b/src/com/google/inject/BindingAnnotation.java
index f5dc08b..664d95e 100644
--- a/src/com/google/inject/BindingAnnotation.java
+++ b/src/com/google/inject/BindingAnnotation.java
@@ -20,6 +20,7 @@
 import java.lang.annotation.Retention;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.*;
 
 /**
  * Annotates annotations which are used for binding. Only one such annotation
@@ -35,6 +36,6 @@
  *
  * @author crazybob@google.com (Bob Lee)
  */
-@Target({ ElementType.ANNOTATION_TYPE })
+@Target(ANNOTATION_TYPE)
 @Retention(RUNTIME)
 public @interface BindingAnnotation {}
diff --git a/src/com/google/inject/ContainerScoped.java b/src/com/google/inject/ContainerScoped.java
index 49968fe..84bacc9 100644
--- a/src/com/google/inject/ContainerScoped.java
+++ b/src/com/google/inject/ContainerScoped.java
@@ -29,4 +29,5 @@
  */
 @Target(ElementType.TYPE)
 @Retention(RUNTIME)
+@ScopeAnnotation
 public @interface ContainerScoped {}
diff --git a/src/com/google/inject/ErrorMessages.java b/src/com/google/inject/ErrorMessages.java
index 14ea69f..ad7e516 100644
--- a/src/com/google/inject/ErrorMessages.java
+++ b/src/com/google/inject/ErrorMessages.java
@@ -52,6 +52,9 @@
     }
   }
 
+  static final String MISSING_SCOPE_ANNOTATION = "Please annotate %s with"
+      + " @ScopeAnnotation.";
+
   static final String OPTIONAL_CONSTRUCTOR = "@Inject(optional=true) is"
       + " not allowed on constructors.";
 
diff --git a/src/com/google/inject/ScopeAnnotation.java b/src/com/google/inject/ScopeAnnotation.java
new file mode 100644
index 0000000..85d0972
--- /dev/null
+++ b/src/com/google/inject/ScopeAnnotation.java
@@ -0,0 +1,42 @@
+/**
+ * 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;
+
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import static java.lang.annotation.RetentionPolicy.*;
+import static java.lang.annotation.ElementType.*;
+
+/**
+ * Annotates annotations which are used for scoping. Only one such annotation
+ * may apply to a single implementation class. You must also annotate scope
+ * annotations with {@code @Retention(RUNTIME)}. For example:
+ *
+ * <pre>
+ *   {@code @}Retention(RUNTIME)
+ *   {@code @}Target(TYPE)
+ *   {@code @}ScopeAnnotation
+ *   public {@code @}interface SessionScoped {}
+ * </pre>
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+@Target(ANNOTATION_TYPE)
+@Retention(RUNTIME)
+public @interface ScopeAnnotation {}
diff --git a/src/com/google/inject/Scopes.java b/src/com/google/inject/Scopes.java
index 41d814b..e839581 100644
--- a/src/com/google/inject/Scopes.java
+++ b/src/com/google/inject/Scopes.java
@@ -30,19 +30,6 @@
   private Scopes() {}
 
   /**
-   * The default scope, one instance per injection.
-   */
-  public static final Scope DEFAULT = new Scope() {
-    public <T> Locator<T> scope(Key<T> key, Locator<T> creator) {
-      return creator;
-    }
-
-    public String toString() {
-      return "Scopes.DEFAULT";
-    }
-  };
-
-  /**
    * One instance per container. Also see {@code @}{@link ContainerScoped}.
    */
   public static final Scope CONTAINER = new Scope() {
@@ -92,19 +79,31 @@
   static Scope getScopeForType(Class<?> implementation,
       Map<Class<? extends Annotation>, Scope> scopes,
       ErrorHandler errorHandler) {
-    Scope found = null;
+    Class<? extends Annotation> found = null;
     for (Annotation annotation : implementation.getAnnotations()) {
-      Scope scope = scopes.get(annotation.annotationType());
-      if (scope != null) {
+      if (isScopeAnnotation(annotation)) {
         if (found != null) {
-          errorHandler.handle(StackTraceElements.forType(implementation),
-              ErrorMessages.DUPLICATE_SCOPE_ANNOTATIONS, found, scope);
+          errorHandler.handle(
+              StackTraceElements.forType(implementation),
+              ErrorMessages.DUPLICATE_SCOPE_ANNOTATIONS,
+              "@" + found.getSimpleName(),
+              "@" + annotation.annotationType().getSimpleName()
+          );
         } else {
-          found = scope;
+          found = annotation.annotationType();
         }
       }
     }
-    return found;
+
+    return scopes.get(found);
+  }
+
+  static boolean isScopeAnnotation(Annotation annotation) {
+    return isScopeAnnotation(annotation.annotationType());
+  }
+
+  static boolean isScopeAnnotation(Class<? extends Annotation> annotationType) {
+    return annotationType.isAnnotationPresent(ScopeAnnotation.class);
   }
 
   /**
@@ -113,8 +112,8 @@
   static <T> InternalFactory<? extends T> scope(Key<T> key,
       ContainerImpl container, InternalFactory<? extends T> creator,
       Scope scope) {
-    // Default scope does nothing.
-    if (scope == null || scope == DEFAULT) {
+    // No scope does nothing.
+    if (scope == null) {
       return creator;
     }
     Locator<T> scoped = scope.scope(key,
diff --git a/src/com/google/inject/servlet/RequestScoped.java b/src/com/google/inject/servlet/RequestScoped.java
index c0d89fd..260af5d 100644
--- a/src/com/google/inject/servlet/RequestScoped.java
+++ b/src/com/google/inject/servlet/RequestScoped.java
@@ -20,6 +20,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import com.google.inject.ScopeAnnotation;
 
 /**
  * Apply this to implementation classes when you want one instance per request.
@@ -28,4 +29,5 @@
  */
 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
+@ScopeAnnotation
 public @interface RequestScoped {}
diff --git a/src/com/google/inject/servlet/SessionScoped.java b/src/com/google/inject/servlet/SessionScoped.java
index 781c270..28b7ad0 100644
--- a/src/com/google/inject/servlet/SessionScoped.java
+++ b/src/com/google/inject/servlet/SessionScoped.java
@@ -20,6 +20,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import com.google.inject.ScopeAnnotation;
 
 /**
  * Apply this to implementation classes when you want one instance per session.
@@ -29,4 +30,5 @@
  */
 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
+@ScopeAnnotation
 public @interface SessionScoped {}
diff --git a/test/com/google/inject/ScopesTest.java b/test/com/google/inject/ScopesTest.java
index 2d224b0..04590ee 100644
--- a/test/com/google/inject/ScopesTest.java
+++ b/test/com/google/inject/ScopesTest.java
@@ -17,6 +17,7 @@
 package com.google.inject;
 
 import junit.framework.TestCase;
+import com.google.inject.servlet.ServletScopes;
 
 /**
  * @author crazybob@google.com (Bob Lee)
@@ -40,8 +41,8 @@
     BinderImpl builder = new BinderImpl();
     BindingBuilderImpl<Singleton> bindingBuilder
         = builder.bind(Singleton.class);
-    bindingBuilder.in(Scopes.DEFAULT);
+    bindingBuilder.in(ServletScopes.REQUEST);
     builder.createContainer();
-    assertSame(Scopes.DEFAULT, bindingBuilder.scope);
+    assertSame(ServletScopes.REQUEST, bindingBuilder.scope);
   }
 }