Add ServletScopes.isRequestScoped.

Revision created by MOE tool push_codebase.
MOE_MIGRATION=4084
diff --git a/common.xml b/common.xml
index 6401b21..d3ab4ed 100644
--- a/common.xml
+++ b/common.xml
@@ -44,10 +44,6 @@
 
     <property name="Export-Package" value="!${module}.internal.*,${module}.*;version=${api.version}"/>
 
-    <condition property="Eclipse-ExtensibleAPI" value="true">
-      <equals arg1="${module}" arg2="com.google.inject"/>
-    </condition>
-
     <condition property="Import-Package" value="!com.google.inject.*,*" else="!${module}.*,${imports},*">
       <istrue value="${fragment}"/>
     </condition>
diff --git a/core/pom.xml b/core/pom.xml
index 9ac81be..3df788b 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -98,11 +98,6 @@
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <configuration>
-          <instructions>
-            <Eclipse-ExtensibleAPI>true</Eclipse-ExtensibleAPI>
-          </instructions>
-        </configuration>
       </plugin>
       <!--
        | Remove duplicate jarjar'd LICENSE and NOTICE
diff --git a/extensions/grapher/test/com/google/inject/grapher/AbstractInjectorGrapherTest.java b/extensions/grapher/test/com/google/inject/grapher/AbstractInjectorGrapherTest.java
index b5ee53f..8d90f4c 100644
--- a/extensions/grapher/test/com/google/inject/grapher/AbstractInjectorGrapherTest.java
+++ b/extensions/grapher/test/com/google/inject/grapher/AbstractInjectorGrapherTest.java
@@ -26,6 +26,7 @@
 import com.google.inject.Key;
 import com.google.inject.Provider;
 import com.google.inject.spi.InjectionPoint;
+import com.google.testing.testsize.MediumTest;
 
 import junit.framework.TestCase;
 
@@ -40,6 +41,7 @@
  *
  * @author bojand@google.com (Bojan Djordjevic)
  */
+@MediumTest
 public class AbstractInjectorGrapherTest extends TestCase {
   private static final String TEST_STRING = "test";
 
diff --git a/extensions/servlet/src/com/google/inject/servlet/ServletScopes.java b/extensions/servlet/src/com/google/inject/servlet/ServletScopes.java
index acc7229..156e9c6 100644
--- a/extensions/servlet/src/com/google/inject/servlet/ServletScopes.java
+++ b/extensions/servlet/src/com/google/inject/servlet/ServletScopes.java
@@ -19,12 +19,18 @@
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
+import com.google.inject.Binding;
+import com.google.inject.Injector;
 import com.google.inject.Key;
 import com.google.inject.OutOfScopeException;
 import com.google.inject.Provider;
 import com.google.inject.Scope;
 import com.google.inject.Scopes;
+import com.google.inject.internal.LinkedBindingImpl;
+import com.google.inject.spi.BindingScopingVisitor;
+import com.google.inject.spi.ExposedBinding;
 
+import java.lang.annotation.Annotation;
 import java.util.Map;
 import java.util.concurrent.Callable;
 
@@ -47,6 +53,14 @@
       Key.get(HttpServletResponse.class),
       new Key<Map<String, String[]>>(RequestParameters.class) {});
 
+  /**
+   * A threadlocal scope map for non-http request scopes. The {@link #REQUEST}
+   * scope falls back to this scope map if no http request is available, and
+   * requires {@link #scopeRequest} to be called as an alertnative.
+   */
+  private static final ThreadLocal<Map<String, Object>> requestScopeContext
+      = new ThreadLocal<Map<String, Object>>();
+
   /** A sentinel attribute value representing null. */
   enum NullObject { INSTANCE }
 
@@ -233,12 +247,59 @@
   }
 
   /**
-   * A threadlocal scope map for non-http request scopes. The {@link #REQUEST}
-   * scope falls back to this scope map if no http request is available, and
-   * requires {@link #scopeRequest} to be called as an alertnative.
+   * Returns true if {@code binding} is request-scoped. If the binding is a
+   * {@link com.google.inject.spi.LinkedKeyBinding linked key binding} and
+   * belongs to an injector (i. e. it was retrieved via
+   * {@link Injector#getBinding Injector.getBinding()}), then this method will
+   * also return true if the target binding is request-scoped.
    */
-  private static final ThreadLocal<Map<String, Object>> requestScopeContext
-      = new ThreadLocal<Map<String, Object>>();
+  public static boolean isRequestScoped(Binding<?> binding) {
+    do {
+      boolean requestScoped = binding.acceptScopingVisitor(new BindingScopingVisitor<Boolean>() {
+        @Override
+        public Boolean visitNoScoping() {
+          return false;
+        }
+
+        @Override
+        public Boolean visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
+          return scopeAnnotation == RequestScoped.class;
+        }
+
+        @Override
+        public Boolean visitScope(Scope scope) {
+          return scope == ServletScopes.REQUEST;
+        }
+
+        @Override
+        public Boolean visitEagerSingleton() {
+          return false;
+        }
+      });
+
+      if (requestScoped) {
+        return true;
+      }
+
+      if (binding instanceof LinkedBindingImpl) {
+        LinkedBindingImpl<?> linkedBinding = (LinkedBindingImpl<?>) binding;
+        Injector injector = linkedBinding.getInjector();
+        if (injector != null) {
+          binding = injector.getBinding(linkedBinding.getLinkedKey());
+          continue;
+        }
+      } else if (binding instanceof ExposedBinding) {
+        ExposedBinding<?> exposedBinding = (ExposedBinding<?>) binding;
+        Injector injector = exposedBinding.getPrivateElements().getInjector();
+        if (injector != null) {
+          binding = injector.getBinding(exposedBinding.getKey());
+          continue;
+        }
+      }
+
+      return false;
+    } while (true);
+  }
 
   /**
    * Scopes the given callable inside a request scope. This is not the same
diff --git a/extensions/servlet/test/com/google/inject/servlet/ServletScopesTest.java b/extensions/servlet/test/com/google/inject/servlet/ServletScopesTest.java
new file mode 100644
index 0000000..0ad40e3
--- /dev/null
+++ b/extensions/servlet/test/com/google/inject/servlet/ServletScopesTest.java
@@ -0,0 +1,203 @@
+/**
+ * Copyright (C) 2011 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.servlet;
+
+import static com.google.inject.name.Names.named;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binding;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.PrivateModule;
+import com.google.inject.Provides;
+import com.google.inject.ScopeAnnotation;
+import com.google.inject.Scopes;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.Elements;
+import com.google.inject.spi.PrivateElements;
+import com.google.inject.util.Providers;
+
+import junit.framework.TestCase;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests for {@link ServletScopes}.
+ *
+ * @author forster@google.com (Mike Forster)
+ */
+public class ServletScopesTest extends TestCase {
+  public void testIsRequestScopedPositive() {
+    final Key<String> a = Key.get(String.class, named("A"));
+    final Key<String> b = Key.get(String.class, named("B"));
+    final Key<String> c = Key.get(String.class, named("C"));
+    final Key<String> d = Key.get(String.class, named("D"));
+    final Key<Object> e = Key.get(Object.class, named("E"));
+    final Key<String> f = Key.get(String.class, named("F"));
+    final Key<String> g = Key.get(String.class, named("G"));
+
+    Module requestScopedBindings = new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(a).to(b);
+        bind(b).to(c);
+        bind(c).toProvider(Providers.of("c")).in(ServletScopes.REQUEST);
+        bind(d).toProvider(Providers.of("d")).in(RequestScoped.class);
+        bind(e).to(AnnotatedRequestScopedClass.class);
+        install(new PrivateModule() {
+          @Override
+          protected void configure() {
+            bind(f).toProvider(Providers.of("f")).in(RequestScoped.class);
+            expose(f);
+          }
+        });
+      }
+
+      @Provides
+      @Named("G")
+      @RequestScoped
+      String provideG() {
+        return "g";
+      }
+    };
+
+    @SuppressWarnings("unchecked") // we know the module contains only bindings
+    List<Element> moduleBindings = Elements.getElements(requestScopedBindings);
+    ImmutableMap<Key<?>, Binding<?>> map = indexBindings(moduleBindings);
+    // linked bindings are not followed by modules
+    assertFalse(ServletScopes.isRequestScoped(map.get(a)));
+    assertFalse(ServletScopes.isRequestScoped(map.get(b)));
+    assertTrue(ServletScopes.isRequestScoped(map.get(c)));
+    assertTrue(ServletScopes.isRequestScoped(map.get(d)));
+    // annotated classes are not followed by modules
+    assertFalse(ServletScopes.isRequestScoped(map.get(e)));
+    assertTrue(ServletScopes.isRequestScoped(map.get(f)));
+    assertTrue(ServletScopes.isRequestScoped(map.get(g)));
+
+    Injector injector = Guice.createInjector(requestScopedBindings, new ServletModule());
+    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(a)));
+    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(b)));
+    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(c)));
+    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(d)));
+    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(e)));
+    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(f)));
+    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(g)));
+  }
+
+  public void testIsRequestScopedNegative() {
+    final Key<String> a = Key.get(String.class, named("A"));
+    final Key<String> b = Key.get(String.class, named("B"));
+    final Key<String> c = Key.get(String.class, named("C"));
+    final Key<String> d = Key.get(String.class, named("D"));
+    final Key<String> e = Key.get(String.class, named("E"));
+    final Key<String> f = Key.get(String.class, named("F"));
+    final Key<String> g = Key.get(String.class, named("G"));
+    final Key<String> h = Key.get(String.class, named("H"));
+    final Key<String> i = Key.get(String.class, named("I"));
+    final Key<String> j = Key.get(String.class, named("J"));
+
+    Module requestScopedBindings = new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(a).to(b);
+        bind(b).to(c);
+        bind(c).toProvider(Providers.of("c")).in(Scopes.NO_SCOPE);
+        bind(d).toInstance("d");
+        bind(e).toProvider(Providers.of("e")).asEagerSingleton();
+        bind(f).toProvider(Providers.of("f")).in(Scopes.SINGLETON);
+        bind(g).toProvider(Providers.of("g")).in(Singleton.class);
+        bind(h).toProvider(Providers.of("h")).in(CustomScoped.class);
+        bindScope(CustomScoped.class, Scopes.NO_SCOPE);
+        install(new PrivateModule() {
+          @Override
+          protected void configure() {
+            bind(i).toProvider(Providers.of("i")).in(CustomScoped.class);
+            expose(i);
+          }
+        });
+      }
+
+      @Provides
+      @Named("J")
+      @CustomScoped
+      String provideJ() {
+        return "j";
+      }
+    };
+
+    @SuppressWarnings("unchecked") // we know the module contains only bindings
+    List<Element> moduleBindings = Elements.getElements(requestScopedBindings);
+    ImmutableMap<Key<?>, Binding<?>> map = indexBindings(moduleBindings);
+    assertFalse(ServletScopes.isRequestScoped(map.get(a)));
+    assertFalse(ServletScopes.isRequestScoped(map.get(b)));
+    assertFalse(ServletScopes.isRequestScoped(map.get(c)));
+    assertFalse(ServletScopes.isRequestScoped(map.get(d)));
+    assertFalse(ServletScopes.isRequestScoped(map.get(e)));
+    assertFalse(ServletScopes.isRequestScoped(map.get(f)));
+    assertFalse(ServletScopes.isRequestScoped(map.get(g)));
+    assertFalse(ServletScopes.isRequestScoped(map.get(h)));
+    assertFalse(ServletScopes.isRequestScoped(map.get(i)));
+    assertFalse(ServletScopes.isRequestScoped(map.get(j)));
+
+    Injector injector = Guice.createInjector(requestScopedBindings);
+    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(a)));
+    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(b)));
+    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(c)));
+    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(d)));
+    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(e)));
+    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(f)));
+    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(g)));
+    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(h)));
+    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(i)));
+    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(j)));
+  }
+
+  @RequestScoped
+  static class AnnotatedRequestScopedClass {}
+
+  @Target({ ElementType.TYPE, ElementType.METHOD })
+  @Retention(RUNTIME)
+  @ScopeAnnotation
+  private @interface CustomScoped {}
+
+  private ImmutableMap<Key<?>, Binding<?>> indexBindings(Iterable<Element> elements) {
+    ImmutableMap.Builder<Key<?>, Binding<?>> builder = ImmutableMap.builder();
+    for (Element element : elements) {
+      if (element instanceof Binding) {
+        Binding<?> binding = (Binding<?>) element;
+        builder.put(binding.getKey(), binding);
+      } else if (element instanceof PrivateElements) {
+        PrivateElements privateElements = (PrivateElements) element;
+        Map<Key<?>, Binding<?>> privateBindings = indexBindings(privateElements.getElements());
+        for (Key<?> exposed : privateElements.getExposedKeys()) {
+          builder.put(exposed, privateBindings.get(exposed));
+        }
+      }
+    }
+    return builder.build();
+  }
+}