Lifecycle now supports parallel execution. Need to add support for parallel startup and autostart next.

TODO added to check servlet spec compliance in GS2 request dispatcher.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@1074 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/lifecycle/src/com/google/inject/lifecycle/BroadcastingLifecycle.java b/lifecycle/src/com/google/inject/lifecycle/BroadcastingLifecycle.java
index 74cb6af..0368185 100644
--- a/lifecycle/src/com/google/inject/lifecycle/BroadcastingLifecycle.java
+++ b/lifecycle/src/com/google/inject/lifecycle/BroadcastingLifecycle.java
@@ -14,11 +14,16 @@
 import java.lang.reflect.Method;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
 import net.sf.cglib.proxy.InvocationHandler;
 import net.sf.cglib.proxy.Proxy;
 
-/** @author [email protected] (Dhanji R. Prasanna) */
-@Singleton class BroadcastingLifecycle implements Lifecycle {
+/**
+ * @author [email protected] (Dhanji R. Prasanna)
+ */
+@Singleton
+class BroadcastingLifecycle implements Lifecycle {
   private final Injector injector;
   private final List<Class<?>> callableClasses;
 
@@ -103,16 +108,7 @@
   }
 
   public <T> T broadcast(Class<T> clazz, Matcher<? super T> matcher) {
-    final List<T> ts = Lists.newArrayList();
-    for (Key<?> key : callableKeys.get(clazz)) {
-      // Should this get instancing happen during method call?
-      @SuppressWarnings("unchecked") // Guarded by getInstance
-          T t = (T) injector.getInstance(key);
-
-      if (matcher.matches(t)) {
-        ts.add(t);
-      }
-    }
+    final List<T> ts = instantiateForBroadcast(clazz, matcher);
 
     @SuppressWarnings("unchecked") T caster = (T) Proxy
         .newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new InvocationHandler() {
@@ -130,4 +126,50 @@
 
     return caster;
   }
-}
\ No newline at end of file
+
+  private <T> List<T> instantiateForBroadcast(Class<T> clazz, Matcher<? super T> matcher) {
+    final List<T> ts = Lists.newArrayList();
+    for (Key<?> key : callableKeys.get(clazz)) {
+      // Should this get instancing happen during method call?
+      @SuppressWarnings("unchecked") // Guarded by getInstance
+          T t = (T) injector.getInstance(key);
+
+      if (matcher.matches(t)) {
+        ts.add(t);
+      }
+    }
+    return ts;
+  }
+
+  public <T> T broadcast(Class<T> clazz, final ExecutorService executorService) {
+    return broadcast(clazz, executorService, any());
+  }
+
+  public <T> T broadcast(Class<T> clazz, final ExecutorService executorService,
+      Matcher<? super T> matcher) {
+    final List<T> ts = instantiateForBroadcast(clazz, matcher);
+
+    @SuppressWarnings("unchecked") T caster = (T) Proxy
+        .newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new InvocationHandler() {
+          public Object invoke(Object o, final Method method, final Object[] objects)
+              throws Throwable {
+
+            // propagate the method call with the same arg list to all instances.
+            for (final T t : ts) {
+              // Submit via executor service. TODO See if this can be parallelized by
+              // yet another dimension, i.e. inParallel(N)
+              executorService.submit(new Callable() {
+                public Object call() throws Exception {
+                  return method.invoke(t, objects);
+                }
+              });
+            }
+
+            // We can't return from multiple instances, so just return null.
+            return null;
+          }
+        });
+
+    return caster;
+  }
+}
diff --git a/lifecycle/src/com/google/inject/lifecycle/Lifecycle.java b/lifecycle/src/com/google/inject/lifecycle/Lifecycle.java
index 0ec38b4..7204521 100644
--- a/lifecycle/src/com/google/inject/lifecycle/Lifecycle.java
+++ b/lifecycle/src/com/google/inject/lifecycle/Lifecycle.java
@@ -2,9 +2,10 @@
 
 import com.google.inject.ImplementedBy;
 import com.google.inject.matcher.Matcher;
+import java.util.concurrent.ExecutorService;
 
 /**
- *  @author [email protected] (Dhanji R. Prasanna)
+ *  @author [email protected] (Dhanji R. Prasanna)
  */
 @ImplementedBy(BroadcastingLifecycle.class)
 public interface Lifecycle {
@@ -14,4 +15,8 @@
   <T> T broadcast(Class<T> clazz);
 
   <T> T broadcast(Class<T> clazz, Matcher<? super T> matcher);
+
+  <T> T broadcast(Class<T> clazz, ExecutorService executorService);
+
+  <T> T broadcast(Class<T> clazz, ExecutorService executorService, Matcher<? super T> matcher);
 }
diff --git a/lifecycle/src/com/google/inject/lifecycle/ListOfMatchers.java b/lifecycle/src/com/google/inject/lifecycle/ListOfMatchers.java
index 847b1e1..32aedad 100644
--- a/lifecycle/src/com/google/inject/lifecycle/ListOfMatchers.java
+++ b/lifecycle/src/com/google/inject/lifecycle/ListOfMatchers.java
@@ -4,7 +4,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-/** @author [email protected] (Dhanji R. Prasanna) */
+/** @author [email protected] (Dhanji R. Prasanna) */
 @Retention(RetentionPolicy.RUNTIME)
 @BindingAnnotation
 @interface ListOfMatchers {
diff --git a/lifecycle/src/com/google/inject/lifecycle/Startable.java b/lifecycle/src/com/google/inject/lifecycle/Startable.java
index beae593..6ea9ea8 100644
--- a/lifecycle/src/com/google/inject/lifecycle/Startable.java
+++ b/lifecycle/src/com/google/inject/lifecycle/Startable.java
@@ -10,7 +10,7 @@
  * All instances that wish to use startable *must* be bound as
  * singletons.
  *
- * @author [email protected] (Dhanji R. Prasanna)
+ * @author [email protected] (Dhanji R. Prasanna)
  */
 public interface Startable {
   /**
diff --git a/lifecycle/test/com/google/inject/lifecycle/ArbitraryBroadcastTest.java b/lifecycle/test/com/google/inject/lifecycle/ArbitraryBroadcastTest.java
index 0751807..f2e930c 100644
--- a/lifecycle/test/com/google/inject/lifecycle/ArbitraryBroadcastTest.java
+++ b/lifecycle/test/com/google/inject/lifecycle/ArbitraryBroadcastTest.java
@@ -4,7 +4,7 @@
 import com.google.inject.Singleton;
 import junit.framework.TestCase;
 
-/** @author [email protected] (Dhanji R. Prasanna) */
+/** @author [email protected] (Dhanji R. Prasanna) */
 public class ArbitraryBroadcastTest extends TestCase {
   private static int called;
 
diff --git a/lifecycle/test/com/google/inject/lifecycle/MultipleStartableTest.java b/lifecycle/test/com/google/inject/lifecycle/MultipleStartableTest.java
index 500bced..6e67ff9 100644
--- a/lifecycle/test/com/google/inject/lifecycle/MultipleStartableTest.java
+++ b/lifecycle/test/com/google/inject/lifecycle/MultipleStartableTest.java
@@ -4,7 +4,7 @@
 import com.google.inject.Singleton;
 import junit.framework.TestCase;
 
-/** @author [email protected] (Dhanji R. Prasanna) */
+/** @author [email protected] (Dhanji R. Prasanna) */
 public class MultipleStartableTest extends TestCase {
   private static int started;
 
diff --git a/lifecycle/test/com/google/inject/lifecycle/ParallelizedBroadcastTest.java b/lifecycle/test/com/google/inject/lifecycle/ParallelizedBroadcastTest.java
new file mode 100644
index 0000000..27e8775
--- /dev/null
+++ b/lifecycle/test/com/google/inject/lifecycle/ParallelizedBroadcastTest.java
@@ -0,0 +1,55 @@
+package com.google.inject.lifecycle;
+
+import com.google.inject.Guice;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import junit.framework.TestCase;
+
+/** @author [email protected] (Dhanji R. Prasanna) */
+public class ParallelizedBroadcastTest extends TestCase {
+  private static final AtomicInteger called = new AtomicInteger();
+
+  public final void testCallable() throws InterruptedException {
+    called.set(0);
+    Lifecycle lifecycle = Guice.createInjector(new LifecycleModule() {
+
+      @Override
+      protected void configureLifecycle() {
+        bind(Runnable.class).to(AClass.class);
+
+        // Does not get called because the key does not expose the callable.
+        bind(Object.class).to(AClass.class);
+
+        bind(BaseClass.class).to(AClass.class);
+
+        callable(Runnable.class);
+      }
+
+    }).getInstance(Lifecycle.class);
+
+    final ExecutorService executorService = Executors.newFixedThreadPool(3);
+    lifecycle
+      .broadcast(Runnable.class, executorService)
+      .run();
+
+    executorService.shutdown();
+    executorService.awaitTermination(5L, TimeUnit.SECONDS);
+
+    assertEquals(2, called.get());
+  }
+
+  public static class BaseClass implements Runnable {
+
+    public void run() {
+      called.incrementAndGet();
+    }
+  }
+
+  public static class AClass extends BaseClass implements Runnable {
+    public void run() {
+      super.run();
+    }
+  }
+}
diff --git a/lifecycle/test/com/google/inject/lifecycle/StartableTest.java b/lifecycle/test/com/google/inject/lifecycle/StartableTest.java
index 7f197b8..0ec2e37 100644
--- a/lifecycle/test/com/google/inject/lifecycle/StartableTest.java
+++ b/lifecycle/test/com/google/inject/lifecycle/StartableTest.java
@@ -4,7 +4,7 @@
 import com.google.inject.Singleton;
 import junit.framework.TestCase;
 
-/** @author [email protected] (Dhanji R. Prasanna) */
+/** @author [email protected] (Dhanji R. Prasanna) */
 public class StartableTest extends TestCase {
   private static boolean started;
 
diff --git a/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java b/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java
index 320229e..f5392fb 100755
--- a/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java
+++ b/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java
@@ -115,6 +115,10 @@
    */
   RequestDispatcher getRequestDispatcher(String path) {
     final String newRequestUri = path;
+
+    // TODO(dhanji): check servlet spec to see if the following is legal or not.
+    // Need to strip query string if requested...
+
     for (final ServletDefinition servletDefinition : servletDefinitions) {
       if (servletDefinition.shouldServe(path)) {
         return new RequestDispatcher() {