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