Rewrite of parent injectors.

This implementation works okay, but there's still a lot of work to be done:
 - cross-injector concurrency is still weak. I've left TODOs in a few places where this needs to be addressed.
 - Initializer can be simplified further
 - we might be able to axe State/InheritingState. These were mostly for my sanity to constrain  where InjectorImpl gets its data
 - we can always have more test cases

git-svn-id: https://google-guice.googlecode.com/svn/trunk@629 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/BindingProcessor.java b/src/com/google/inject/BindingProcessor.java
index 430dd6b..a9f0386 100644
--- a/src/com/google/inject/BindingProcessor.java
+++ b/src/com/google/inject/BindingProcessor.java
@@ -28,7 +28,6 @@
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Type;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -59,22 +58,17 @@
   };
 
   private final InjectorImpl injector;
-  private final Map<Class<? extends Annotation>, Scope> scopes;
+  private final State state;
   private final List<CreationListener> creationListeners = Lists.newArrayList();
-  private final Map<Key<?>, BindingImpl<?>> bindings;
-  private final CreationTimeMemberInjector memberInjector;
+  private final Initializer initializer;
   private final List<Runnable> untargettedBindings = Lists.newArrayList();
 
-  BindingProcessor(Errors errors,
-      InjectorImpl injector,
-      Map<Class<? extends Annotation>, Scope> scopes,
-      Map<Key<?>, BindingImpl<?>> bindings,
-      CreationTimeMemberInjector memberInjector) {
+  BindingProcessor(Errors errors, InjectorImpl injector, State state,
+      Initializer initializer) {
     super(errors);
     this.injector = injector;
-    this.scopes = scopes;
-    this.bindings = bindings;
-    this.memberInjector = memberInjector;
+    this.state = state;
+    this.initializer = initializer;
   }
 
   @Override public <T> Boolean visitBinding(Binding<T> command) {
@@ -106,7 +100,7 @@
       }
 
       public Scope visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
-        Scope scope = scopes.get(scopeAnnotation);
+        Scope scope = state.getScope(scopeAnnotation);
         if (scope != null) {
           return scope;
         } else {
@@ -122,8 +116,8 @@
 
     command.acceptTargetVisitor(new BindingTargetVisitor<T, Void>() {
       public Void visitInstance(T instance, Set<InjectionPoint> injectionPoints) {
-        ConstantFactory<? extends T> factory = new ConstantFactory<T>(instance);
-        memberInjector.requestInjection(instance, source, injectionPoints);
+        Initializable<T> ref = initializer.requestInjection(instance, source, injectionPoints);
+        ConstantFactory<? extends T> factory = new ConstantFactory<T>(ref);
         InternalFactory<? extends T> scopedFactory = Scopes.scope(key, injector, factory, scope);
         putBinding(new InstanceBindingImpl<T>(injector, key, source, scopedFactory, injectionPoints,
             instance));
@@ -132,11 +126,10 @@
 
       public Void visitProvider(Provider<? extends T> provider,
           Set<InjectionPoint> injectionPoints) {
-        InternalFactoryToProviderAdapter<? extends T> factory
-            = new InternalFactoryToProviderAdapter<T>(provider, source);
-        memberInjector.requestInjection(provider, source, injectionPoints);
-        InternalFactory<? extends T> scopedFactory
-            = Scopes.scope(key, injector, factory, scope);
+        Initializable<Provider<? extends T>> initializable = initializer
+            .<Provider<? extends T>>requestInjection(provider, source, injectionPoints);
+        InternalFactory<T> factory = new InternalFactoryToProviderAdapter<T>(initializable, source);
+        InternalFactory<? extends T> scopedFactory = Scopes.scope(key, injector, factory, scope);
         putBinding(new ProviderInstanceBindingImpl<T>(injector, key, source, scopedFactory, scope,
             provider, loadStrategy, injectionPoints));
         return null;
@@ -246,7 +239,6 @@
 
   private void putBinding(BindingImpl<?> binding) {
     Key<?> key = binding.getKey();
-    Binding<?> original = bindings.get(key);
 
     Class<?> rawType = key.getRawType();
     if (FORBIDDEN_TYPES.contains(rawType)) {
@@ -254,11 +246,17 @@
       return;
     }
 
-    if (bindings.containsKey(key)) {
+    Binding<?> original = state.getExplicitBinding(key);
+    if (original != null) {
       errors.bindingAlreadySet(key, original.getSource());
-    } else {
-      bindings.put(key, binding);
+      return;
     }
+
+    // TODO: make getExplicitBinding() and blacklist() atomic
+
+    // prevent the parent from creating a JIT binding for this key
+    state.parent().blacklist(key);
+    state.putBinding(key, binding);
   }
 
   // It's unfortunate that we have to maintain a blacklist of specific
@@ -268,7 +266,6 @@
       AbstractModule.class,
       Binder.class,
       Binding.class,
-      // Injector.class,
       Key.class,
       Module.class,
       Provider.class, 
diff --git a/src/com/google/inject/ConstantFactory.java b/src/com/google/inject/ConstantFactory.java
index 32e6801..50b67f2 100644
--- a/src/com/google/inject/ConstantFactory.java
+++ b/src/com/google/inject/ConstantFactory.java
@@ -26,21 +26,20 @@
  */
 class ConstantFactory<T> implements InternalFactory<T> {
 
-  private final T value;
+  private final Initializable<T> initializable;
 
-  public ConstantFactory(T value) {
-    this.value = value;
+  public ConstantFactory(Initializable<T> initializable) {
+    this.initializable = initializable;
   }
 
   public T get(Errors errors, InternalContext context, Dependency dependency)
       throws ErrorsException {
-    context.ensureMemberInjected(errors, value);
-    return value;
+    return initializable.get(errors);
   }
 
   public String toString() {
     return new ToStringBuilder(ConstantFactory.class)
-        .add("value", value)
+        .add("value", initializable)
         .toString();
   }
 }
diff --git a/src/com/google/inject/Guice.java b/src/com/google/inject/Guice.java
index fa3b282..5a776c2 100644
--- a/src/com/google/inject/Guice.java
+++ b/src/com/google/inject/Guice.java
@@ -89,48 +89,8 @@
    */
   public static Injector createInjector(Stage stage,
       Iterable<? extends Module> modules) {
-    return createInjector(null, stage, modules);
-  }
-
-
-  /**
-   * Creates an injector for the given set of modules, with the given parent
-   * injector.
-   *
-   * @throws CreationException if one or more errors occur during Injector
-   *     construction
-   */
-  public static Injector createInjector(Injector parent,
-      Iterable<? extends Module> modules) {
-    return createInjector(parent, Stage.DEVELOPMENT, modules);
-  }
-
-
-  /**
-   * Creates an injector for the given set of modules, with the given parent
-   * injector.
-   *
-   * @throws CreationException if one or more errors occur during Injector
-   *     construction
-   */
-  public static Injector createInjector(Injector parent,
-      Module... modules) {
-    return createInjector(parent, Stage.DEVELOPMENT, Arrays.asList(modules));
-  }
-
-  /**
-   * Creates an injector for the given set of modules, in a given development
-   * stage, with the given parent injector.
-   *
-   * @throws CreationException if one or more errors occur during Injector
-   *     construction
-   */
-  public static Injector createInjector(
-      Injector parent, Stage stage,
-      Iterable<? extends Module> modules) {
     return new InjectorBuilder()
         .stage(stage)
-        .parentInjector(parent)
         .addModules(modules)
         .build();
   }
diff --git a/src/com/google/inject/InheritingState.java b/src/com/google/inject/InheritingState.java
new file mode 100644
index 0000000..0d08429
--- /dev/null
+++ b/src/com/google/inject/InheritingState.java
@@ -0,0 +1,119 @@
+/**
+ * Copyright (C) 2008 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 static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.MatcherAndConverter;
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Map;
+import java.util.Collections;
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+class InheritingState implements State {
+
+  // TODO(jessewilson): think about what we need to do w.r.t. concurrency
+
+  private final State parent;
+  private final Map<Key<?>, Binding<?>> explicitBindingsMutable = Maps.newHashMap();
+  private final Map<Key<?>, Binding<?>> explicitBindings
+      = Collections.unmodifiableMap(explicitBindingsMutable);
+  private final Map<Class<? extends Annotation>, Scope> scopes = Maps.newHashMap();
+  private final List<MatcherAndConverter> converters = Lists.newArrayList();
+  private final List<MethodAspect> methodAspects = Lists.newArrayList();
+  private final WeakKeySet blacklistedKeys = new WeakKeySet();
+
+  InheritingState(State parent) {
+    this.parent = checkNotNull(parent, "parent");
+  }
+
+  public State parent() {
+    return parent;
+  }
+
+  @SuppressWarnings("unchecked") // we only put in BindingImpls that match their key types
+  public <T> BindingImpl<T> getExplicitBinding(Key<T> key) {
+    Binding<?> binding = explicitBindings.get(key);
+    return binding != null ? (BindingImpl<T>) binding : parent.getExplicitBinding(key);
+  }
+
+  public Map<Key<?>, Binding<?>> getExplicitBindingsThisLevel() {
+    return explicitBindings;
+  }
+
+  public void putBinding(Key<?> key, BindingImpl<?> binding) {
+    explicitBindingsMutable.put(key, binding);
+  }
+
+  public Scope getScope(Class<? extends Annotation> annotationType) {
+    Scope scope = scopes.get(annotationType);
+    return scope != null ? scope : parent.getScope(annotationType);
+  }
+
+  public void putAnnotation(Class<? extends Annotation> annotationType, Scope scope) {
+    scopes.put(annotationType, scope);
+  }
+
+  public Iterable<MatcherAndConverter> getConvertersThisLevel() {
+    return converters;
+  }
+
+  public void addConverter(MatcherAndConverter matcherAndConverter) {
+    converters.add(matcherAndConverter);
+  }
+
+  public MatcherAndConverter getConverter(
+      String stringValue, TypeLiteral<?> type, Errors errors, Object source) {
+    MatcherAndConverter matchingConverter = null;
+    for (State s = this; s != State.NONE; s = s.parent()) {
+      for (MatcherAndConverter converter : s.getConvertersThisLevel()) {
+        if (converter.getTypeMatcher().matches(type)) {
+          if (matchingConverter != null) {
+            errors.ambiguousTypeConversion(stringValue, source, type, matchingConverter, converter);
+          }
+          matchingConverter = converter;
+        }
+      }
+    }
+    return matchingConverter;
+  }
+
+  public void addMethodAspect(MethodAspect methodAspect) {
+    methodAspects.add(methodAspect);
+  }
+
+  public List<MethodAspect> getMethodAspects() {
+    List<MethodAspect> result = Lists.newArrayList();
+    result.addAll(parent.getMethodAspects());
+    result.addAll(methodAspects);
+    return result;
+  }
+
+  public void blacklist(Key<?> key) {
+    parent.blacklist(key);
+    blacklistedKeys.add(key);
+  }
+
+  public boolean isBlacklisted(Key<?> key) {
+    return blacklistedKeys.contains(key);
+  }
+}
diff --git a/src/com/google/inject/Initializable.java b/src/com/google/inject/Initializable.java
new file mode 100644
index 0000000..a418722
--- /dev/null
+++ b/src/com/google/inject/Initializable.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2008 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 com.google.inject.internal.Errors;
+import com.google.inject.internal.ErrorsException;
+
+/**
+ * Holds a reference that requires initialization to be performed before it can be used.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+interface Initializable<T> {
+
+  /**
+   * Ensures the reference is initialized, then returns it.
+   */
+  T get(Errors errors) throws ErrorsException;
+}
diff --git a/src/com/google/inject/Initializables.java b/src/com/google/inject/Initializables.java
new file mode 100644
index 0000000..a041c74
--- /dev/null
+++ b/src/com/google/inject/Initializables.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2008 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 com.google.inject.internal.Errors;
+import com.google.inject.internal.ErrorsException;
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+class Initializables {
+
+  /**
+   * Returns an initializable for an instance that requires no initialization.
+   */
+  static <T> Initializable<T> of(final T instance) {
+    return new Initializable<T>() {
+      public T get(Errors errors) throws ErrorsException {
+        return instance;
+      }
+
+      @Override public String toString() {
+        return String.valueOf(instance);
+      }
+    };
+  }
+}
diff --git a/src/com/google/inject/Initializer.java b/src/com/google/inject/Initializer.java
new file mode 100644
index 0000000..643492f
--- /dev/null
+++ b/src/com/google/inject/Initializer.java
@@ -0,0 +1,159 @@
+/**
+ * Copyright (C) 2008 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 static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.collect.Maps;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.ErrorsException;
+import com.google.inject.spi.InjectionPoint;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Manages and injects instances at injector-creation time.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+class Initializer {
+  /** the only thread that we'll use to inject members. */
+  private final Thread creatingThread = Thread.currentThread();
+
+  /** zero means everything is injected. */
+  private final CountDownLatch ready = new CountDownLatch(1);
+
+  /** Maps instances that need injection to a source that registered them */
+  private final Map<Object, Object> outstandingInjections = Maps.newIdentityHashMap();
+  private final InjectorImpl injector;
+
+  Initializer(InjectorImpl injector) {
+    this.injector = injector;
+  }
+
+  /**
+   * Registers an instance for member injection when that step is performed.
+   *
+   * @param instance an instance that optionally has members to be injected (each annotated with
+   *      @Inject).
+   * @param source the source location that this injection was requested
+   */
+  public <T> Initializable<T> requestInjection(T instance, Object source,
+      Set<InjectionPoint> injectionPoints) {
+    checkNotNull(source);
+    outstandingInjections.put(instance, source);
+    return new InjectableReference<T>(this, instance);
+  }
+
+  /**
+   * Prepares member injectors for all injected instances. This prompts Guice to do static analysis
+   * on the injected instances.
+   */
+  void validateOustandingInjections(Errors errors) {
+    for (Map.Entry<Object, Object> entry : outstandingInjections.entrySet()) {
+      try {
+        Object toInject = entry.getKey();
+        Object source = entry.getValue();
+        injector.injectors.get(toInject.getClass(), errors.withSource(source));
+      } catch (ErrorsException e) {
+        errors.merge(e.getErrors());
+      }
+    }
+  }
+
+  /**
+   * Performs creation-time injections on all objects that require it. Whenever fulfilling an
+   * injection depends on another object that requires injection, we use {@link
+   * #ensureInjected(Object,com.google.inject.internal.Errors)} to inject that member first.
+   *
+   * <p>If the two objects are codependent (directly or transitively), ordering of injection is
+   * arbitrary.
+   */
+  void injectAll(final Errors errors) {
+    // loop over a defensive copy since ensureInjected() mutates the set. Unfortunately, that copy
+    // is made complicated by a bug in IBM's JDK, wherein entrySet().toArray(Object[]) doesn't work
+    for (Object entryObject : outstandingInjections.entrySet().toArray()) {
+      @SuppressWarnings("unchecked")
+      Entry<Object, Object> entry = (Entry<Object, Object>) entryObject;
+      try {
+        Object toInject = entry.getKey();
+        Object source = entry.getValue();
+        ensureInjected(toInject, errors.withSource(source));
+      } catch (ErrorsException e) {
+        errors.merge(e.getErrors());
+      }
+    }
+
+    if (!outstandingInjections.isEmpty()) {
+      throw new AssertionError("Failed to satisfy " + outstandingInjections);
+    }
+
+    ready.countDown();
+  }
+
+  private class InjectableReference<T> implements Initializable<T> {
+    private final Initializer initializer;
+    private final T instance;
+
+    public InjectableReference(Initializer initializer, T instance) {
+      this.initializer = checkNotNull(initializer, "initializer");
+      this.instance = checkNotNull(instance, "instance");
+    }
+
+    /**
+     * Ensures that the instance has been injected, and returns it.
+     */
+    public T get(Errors errors) throws ErrorsException {
+      initializer.ensureInjected(instance, errors);
+      return instance;
+    }
+
+    @Override public String toString() {
+      return instance.toString();
+    }
+  }
+
+
+  /**
+   * Reentrant. If {@code toInject} was registered for injection at injector-creation time, this
+   * method will ensure that all its members have been injected before returning. This method is
+   * used both internally, and by {@code InternalContext} to satisfy injections while satisfying
+   * other injections.
+   */
+  void ensureInjected(Object toInject, Errors errors) throws ErrorsException {
+    if (ready.getCount() == 0) {
+      return;
+    }
+
+    // just wait for everything to be injected by another thread
+    if (Thread.currentThread() != creatingThread) {
+      try {
+        ready.await();
+        return;
+      } catch (InterruptedException e) {
+        // Give up, since we don't know if our injection is ready
+        throw new RuntimeException(e);
+      }
+    }
+
+    // toInject needs injection, do it right away
+    if (outstandingInjections.remove(toInject) != null) {
+      injector.injectMembersOrThrow(errors, toInject);
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/com/google/inject/InjectionRequestProcessor.java b/src/com/google/inject/InjectionRequestProcessor.java
index f7c8042..933defa 100644
--- a/src/com/google/inject/InjectionRequestProcessor.java
+++ b/src/com/google/inject/InjectionRequestProcessor.java
@@ -38,10 +38,10 @@
 class InjectionRequestProcessor extends AbstractProcessor {
 
   private final List<StaticInjection> staticInjections = Lists.newArrayList();
-  private final CreationTimeMemberInjector memberInjector;
+  private final Initializer memberInjector;
 
   InjectionRequestProcessor(Errors errors,
-      CreationTimeMemberInjector memberInjector) {
+      Initializer memberInjector) {
     super(errors);
     this.memberInjector = memberInjector;
   }
diff --git a/src/com/google/inject/Injector.java b/src/com/google/inject/Injector.java
index ede77eb..a2db575 100644
--- a/src/com/google/inject/Injector.java
+++ b/src/com/google/inject/Injector.java
@@ -86,7 +86,7 @@
   <T> Binding<T> getBinding(Class<T> type);
 
   /**
-   * Finds all bindings to the given type. This method is part of the Injector
+   * Returns all explicit bindings for the given type. This method is part of the Injector
    * Introspection API and is primarily intended for use by tools.
    */
   <T> List<Binding<T>> findBindingsByType(TypeLiteral<T> type);
@@ -120,4 +120,38 @@
    * dependencies ahead of time.
    */
   <T> T getInstance(Class<T> type);
+
+  /**
+   * Returns a new injector that inherits all state from this injector. All
+   * bindings, scopes, interceptors and type converters are inherited -- they
+   * are visible to the child injector. Elements of the child injector are not
+   * visible to its parent.
+   *
+   * <p>Just-in-time bindings created for child injectors will be created in an
+   * ancestor injector whenever possible. This allows for scoped instances to be
+   * shared between injectors. Use explicit bindings to prevent bindings from
+   * being shared with the parent injector.
+   *
+   * <p>No key may be bound by both an injector and one of its ancestors. This
+   * includes just-in-time bindings. The lone exception is the key for {@code
+   * Injector.class}, which is bound by each injector to itself.
+   */
+  Injector createChildInjector(Iterable<? extends Module> modules);
+
+  /**
+   * Returns a new injector that inherits all state from this injector. All
+   * bindings, scopes, interceptors and type converters are inherited -- they
+   * are visible to the child injector. Elements of the child injector are not
+   * visible to its parent.
+   *
+   * <p>Just-in-time bindings created for child injectors will be created in an
+   * ancestor injector whenever possible. This allows for scoped instances to be
+   * shared between injectors. Use explicit bindings to prevent bindings from
+   * being shared with the parent injector.
+   *
+   * <p>No key may be bound by both an injector and one of its ancestors. This
+   * includes just-in-time bindings. The lone exception is the key for {@code
+   * Injector.class}, which is bound by each injector to itself.
+   */
+  Injector createChildInjector(Module... modules);
 }
diff --git a/src/com/google/inject/InjectorBuilder.java b/src/com/google/inject/InjectorBuilder.java
index ea9dffa..4adfa1f 100644
--- a/src/com/google/inject/InjectorBuilder.java
+++ b/src/com/google/inject/InjectorBuilder.java
@@ -30,6 +30,7 @@
 import com.google.inject.spi.Element;
 import com.google.inject.spi.Elements;
 import com.google.inject.spi.InjectionPoint;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -45,7 +46,7 @@
 
   private final Stopwatch stopwatch = new Stopwatch();
 
-  private Injector parent;
+  private InjectorImpl parent = null;
   private Stage stage;
   private Factory reflectionFactory = new RuntimeReflectionFactory();
   private final List<Module> modules = Lists.newLinkedList();
@@ -72,7 +73,7 @@
     return this;
   }
 
-  InjectorBuilder parentInjector(Injector parent) {
+  InjectorBuilder parentInjector(InjectorImpl parent) {
     this.parent = parent;
     return this;
   }
@@ -91,7 +92,10 @@
 
     injector = new InjectorImpl(parent);
 
-    modules.add(0, new BuiltInModule(injector, stage));
+    // bind Stage and Singleton if this is a top-level injector
+    if (parent == null) {
+      modules.add(0, new RootModule(stage));
+    }
 
     elements.addAll(Elements.getElements(stage, modules));
 
@@ -122,22 +126,22 @@
         .processCommands(elements);
 
     InterceptorBindingProcessor interceptorCommandProcessor
-        = new InterceptorBindingProcessor(errors);
+        = new InterceptorBindingProcessor(errors, injector.state);
     interceptorCommandProcessor.processCommands(elements);
     ConstructionProxyFactory proxyFactory = interceptorCommandProcessor.createProxyFactory();
     injector.reflection = reflectionFactory.create(proxyFactory);
     stopwatch.resetAndLog("Interceptors creation");
 
-    new ScopeBindingProcessor(errors, injector.scopes).processCommands(elements);
+    new ScopeBindingProcessor(errors, injector.state).processCommands(elements);
     stopwatch.resetAndLog("Scopes creation");
 
-    new TypeConverterBindingProcessor(errors, injector.converters).processCommands(elements);
+    new TypeConverterBindingProcessor(errors, injector.state).processCommands(elements);
     stopwatch.resetAndLog("Converters creation");
 
+    bindInjector();
     bindLogger();
     bindCommandProcesor = new BindingProcessor(errors,
-        injector, injector.scopes, injector.explicitBindings,
-        injector.memberInjector);
+        injector, injector.state, injector.initializer);
     bindCommandProcesor.processCommands(elements);
     bindCommandProcesor.createUntargettedBindings();
     stopwatch.resetAndLog("Binding creation");
@@ -145,8 +149,7 @@
     injector.index();
     stopwatch.resetAndLog("Binding indexing");
 
-    injectionCommandProcessor
-        = new InjectionRequestProcessor(errors, injector.memberInjector);
+    injectionCommandProcessor = new InjectionRequestProcessor(errors, injector.initializer);
     injectionCommandProcessor.processCommands(elements);
     stopwatch.resetAndLog("Static injection");
   }
@@ -159,7 +162,7 @@
     injectionCommandProcessor.validate(injector);
     stopwatch.resetAndLog("Static validation");
 
-    injector.memberInjector.validateOustandingInjections(errors);
+    injector.initializer.validateOustandingInjections(errors);
     stopwatch.resetAndLog("Instance member validation");
 
     new ProviderLookupProcessor(errors, injector).processCommands(elements);
@@ -173,7 +176,7 @@
     injectionCommandProcessor.injectMembers(injector);
     stopwatch.resetAndLog("Static member injection");
 
-    injector.memberInjector.injectAll(errors);
+    injector.initializer.injectAll(errors);
     stopwatch.resetAndLog("Instance injection");
     errors.throwCreationExceptionIfErrorsExist();
 
@@ -185,8 +188,10 @@
   public void loadEagerSingletons() {
     // load eager singletons, or all singletons if we're in Stage.PRODUCTION.
     // Bindings discovered while we're binding these singletons are not be eager.
-    Set<BindingImpl<?>> candidateBindings = ImmutableSet.copyOf(
-        Iterables.concat(injector.explicitBindings.values(), injector.jitBindings.values()));
+    @SuppressWarnings("unchecked") // casting Collection<Binding> to Collection<BindingImpl> is safe
+    Set<BindingImpl<?>> candidateBindings = ImmutableSet.copyOf(Iterables.concat(
+        (Collection) injector.state.getExplicitBindingsThisLevel().values(),
+        injector.jitBindings.values()));
     for (final BindingImpl<?> binding : candidateBindings) {
       if ((stage == Stage.PRODUCTION && binding.getScope() == SINGLETON)
           || binding.getLoadStrategy() == LoadStrategy.EAGER) {
@@ -215,40 +220,51 @@
     }
   }
 
-  private static class BuiltInModule implements Module {
-    final Injector injector;
+  private static class RootModule implements Module {
     final Stage stage;
 
-    private BuiltInModule(Injector injector, Stage stage) {
-      this.injector = checkNotNull(injector, "injector");
+    private RootModule(Stage stage) {
       this.stage = checkNotNull(stage, "stage");
     }
 
     public void configure(Binder binder) {
       binder = binder.withSource(SourceProvider.UNKNOWN_SOURCE);
-
       binder.bind(Stage.class).toInstance(stage);
       binder.bindScope(Singleton.class, SINGLETON);
-      // Create default bindings.
-      // We use toProvider() instead of toInstance() to avoid infinite recursion
-      // in toString().
-      binder.bind(Injector.class).toProvider(new InjectorProvider(injector));
+    }
+  }
+
+  /**
+   * The Injector is a special case because we allow both parent and child injectors to both have
+   * a binding for that key.
+   */
+  private void bindInjector() {
+    Key<Injector> key = Key.get(Injector.class);
+    InjectorFactory injectorFactory = new InjectorFactory(injector);
+    injector.state.putBinding(key,
+        new ProviderInstanceBindingImpl<Injector>(injector, key, SourceProvider.UNKNOWN_SOURCE,
+            injectorFactory, Scopes.NO_SCOPE, injectorFactory, LoadStrategy.LAZY,
+            ImmutableSet.<InjectionPoint>of()));
+  }
+
+  static class InjectorFactory implements  InternalFactory<Injector>, Provider<Injector> {
+    private final Injector injector;
+
+    private InjectorFactory(Injector injector) {
+      this.injector = injector;
     }
 
-    class InjectorProvider implements Provider<Injector> {
-      final Injector injector;
+    public Injector get(Errors errors, InternalContext context, Dependency<?> dependency)
+        throws ErrorsException {
+      return injector;
+    }
 
-      InjectorProvider(Injector injector) {
-        this.injector = injector;
-      }
+    public Injector get() {
+      return injector;
+    }
 
-      public Injector get() {
-        return injector;
-      }
-
-      public String toString() {
-        return "Provider<Injector>";
-      }
+    public String toString() {
+      return "Provider<Injector>";
     }
   }
 
@@ -259,7 +275,7 @@
   private void bindLogger() {
     Key<Logger> key = Key.get(Logger.class);
     LoggerFactory loggerFactory = new LoggerFactory();
-    injector.explicitBindings.put(key,
+    injector.state.putBinding(key,
         new ProviderInstanceBindingImpl<Logger>(injector, key,
             SourceProvider.UNKNOWN_SOURCE, loggerFactory, Scopes.NO_SCOPE,
             loggerFactory, LoadStrategy.LAZY, ImmutableSet.<InjectionPoint>of()));
@@ -305,6 +321,12 @@
     public <T> List<Binding<T>> findBindingsByType(TypeLiteral<T> type) {
       return this.delegateInjector.findBindingsByType(type);
     }
+    public Injector createChildInjector(Iterable<? extends Module> modules) {
+      return delegateInjector.createChildInjector(modules);
+    }
+    public Injector createChildInjector(Module... modules) {
+      return delegateInjector.createChildInjector(modules);
+    }
     public <T> Provider<T> getProvider(Key<T> key) {
       throw new UnsupportedOperationException(
         "Injector.getProvider(Key<T>) is not supported in Stage.TOOL");
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index cca4ffd..fa14548 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -16,9 +16,9 @@
 
 package com.google.inject;
 
+import com.google.common.base.Nullable;
 import static com.google.common.base.Preconditions.checkState;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
@@ -60,45 +60,42 @@
  * @see InjectorBuilder
  */
 class InjectorImpl implements Injector {
-  final Injector parentInjector;
-  final Map<Key<?>, BindingImpl<?>> explicitBindings = Maps.newHashMap();
+  final State state;
+  final InjectorImpl parent;
   final BindingsMultimap bindingsMultimap = new BindingsMultimap();
-  final Map<Class<? extends Annotation>, Scope> scopes = Maps.newHashMap();
-  final List<MatcherAndConverter> converters = Lists.newArrayList();
-  final Map<Key<?>, BindingImpl<?>> parentBindings = Maps.newHashMap();
-  final CreationTimeMemberInjector memberInjector = new CreationTimeMemberInjector(this);
+  final Initializer initializer = new Initializer(this);
   Reflection reflection;
 
-  InjectorImpl(Injector parentInjector) {
-    this.parentInjector = parentInjector;
+  InjectorImpl(@Nullable InjectorImpl parent) {
+    this.parent = parent;
+    this.state = new InheritingState(parent != null ? parent.state : State.NONE);
+
+    if (parent != null) {
+      localContext = parent.localContext;
+    } else {
+      localContext = new ThreadLocal<InternalContext[]>() {
+        protected InternalContext[] initialValue() {
+          return new InternalContext[1];
+        }
+      };
+    }
   }
 
   /** Indexes bindings by type. */
   void index() {
-    for (BindingImpl<?> binding : explicitBindings.values()) {
+    for (Binding<?> binding : state.getExplicitBindingsThisLevel().values()) {
       index(binding);
     }
   }
 
-  <T> void index(BindingImpl<T> binding) {
+  <T> void index(Binding<T> binding) {
     bindingsMultimap.put(binding.getKey().getTypeLiteral(), binding);
   }
 
-  // not test-covered
   public <T> List<Binding<T>> findBindingsByType(TypeLiteral<T> type) {
     return Collections.<Binding<T>>unmodifiableList(bindingsMultimap.getAll(type));
   }
 
-  // not test-covered
-  <T> List<String> getNamesOfBindingAnnotations(TypeLiteral<T> type) {
-    List<String> names = Lists.newArrayList();
-    for (Binding<T> binding : findBindingsByType(type)) {
-      Key<T> key = binding.getKey();
-      names.add(key.hasAnnotationType() ? key.getAnnotationName() : "[no annotation]");
-    }
-    return names;
-  }
-
   /** Returns the binding for {@code key} */
   public <T> BindingImpl<T> getBinding(Key<T> key) {
     Errors errors = new Errors(key.getRawType());
@@ -120,15 +117,8 @@
    */
   public <T> BindingImpl<T> getBindingOrThrow(Key<T> key, Errors errors)
       throws ErrorsException {
-    if (parentInjector != null) {
-      BindingImpl<T> bindingImpl = getParentBinding(key);
-      if (bindingImpl != null) {
-        return bindingImpl;
-      }
-    }
-
     // Check explicit bindings, i.e. bindings created by modules.
-    BindingImpl<T> binding = getExplicitBindingImpl(key);
+    BindingImpl<T> binding = state.getExplicitBinding(key);
     if (binding != null) {
       return binding;
     }
@@ -137,55 +127,20 @@
     return getJustInTimeBinding(key, errors);
   }
 
-  /**
-   * Checks the parent injector for a scoped binding, and if available, creates an appropriate
-   * binding local to this injector and remembers it.
-   *
-   * TODO: think about this wrt parent jit bindings
-   */
-  @SuppressWarnings("unchecked")
-  private <T> BindingImpl<T> getParentBinding(Key<T> key) {
-    synchronized (parentBindings) {
-      // null values will mean that the parent doesn't have this binding
-      BindingImpl<T> binding = (BindingImpl<T>) parentBindings.get(key);
-      if (binding != null) {
-        return (BindingImpl<T>) binding;
-      }
-      try {
-        binding = (BindingImpl) parentInjector.getBinding(key);
-      }
-      catch (ProvisionException e) {
-        // if this happens, the parent can't create this key, and we ignore it
-      }
-
-      BindingImpl<T> bindingImpl = null;
-      if (binding != null
-          && binding.getScope() != null
-          && !binding.getScope().equals(Scopes.NO_SCOPE)) {
-        // TODO: this binding won't report its injection points or scoping properly
-        bindingImpl = new ProviderInstanceBindingImpl(
-            this,
-            key,
-            binding.getSource(),
-            new InternalFactoryToProviderAdapter(binding.getProvider(), binding.getSource()),
-            Scopes.NO_SCOPE,
-            binding.getProvider(),
-            LoadStrategy.LAZY,
-            ImmutableSet.<InjectionPoint>of());
-      }
-      parentBindings.put(key, bindingImpl); // this kinda scares me
-      return bindingImpl;
-    }
-  }
-
   public <T> Binding<T> getBinding(Class<T> type) {
     return getBinding(Key.get(type));
   }
 
-  /** Gets a binding which was specified explicitly in a module. */
-  @SuppressWarnings("unchecked")
-  <T> BindingImpl<T> getExplicitBindingImpl(Key<T> key) {
-    return (BindingImpl<T>) explicitBindings.get(key);
+  public Injector createChildInjector(Iterable<? extends Module> modules) {
+    return new InjectorBuilder()
+        .parentInjector(this)
+        .stage(getInstance(Stage.class))
+        .addModules(modules)
+        .build();
+  }
+
+  public Injector createChildInjector(Module... modules) {
+    return createChildInjector(ImmutableList.of(modules));
   }
 
   /**
@@ -194,17 +149,33 @@
    * @throws com.google.inject.internal.ErrorsException if the binding could not be created.
    */
   @SuppressWarnings("unchecked")
-  <T> BindingImpl<T> getJustInTimeBinding(Key<T> key, Errors errors) throws ErrorsException {
+  private <T> BindingImpl<T> getJustInTimeBinding(Key<T> key, Errors errors)
+      throws ErrorsException {
+
+    // TODO: synch should span parent and child
+
     synchronized (jitBindings) {
-      // Support null values.
-      if (!jitBindings.containsKey(key)) {
-        BindingImpl<T> binding = createJustInTimeBinding(key, errors);
-        jitBindings.put(key, binding);
-        return binding;
+      // try to get the JIT binding from the parent injector
+      if (parent != null) {
+        try {
+          return parent.getJustInTimeBinding(key, new Errors());
+        } catch (ErrorsException ignored) {
+        }
       }
-      else {
+
+      // Support null values.
+      if (jitBindings.containsKey(key)) {
         return (BindingImpl<T>) jitBindings.get(key);
       }
+
+      if (state.isBlacklisted(key)) {
+        throw errors.childBindingAlreadySet(key).toException();
+      }
+
+      BindingImpl<T> binding = createJustInTimeBinding(key, errors);
+      state.parent().blacklist(key);
+      jitBindings.put(key, binding);
+      return binding;
     }
   }
 
@@ -278,7 +249,7 @@
       throws ErrorsException {
     // Find a constant string binding.
     Key<String> stringKey = key.ofType(String.class);
-    BindingImpl<String> stringBinding = getExplicitBindingImpl(stringKey);
+    BindingImpl<String> stringBinding = state.getExplicitBinding(stringKey);
     if (stringBinding == null || !stringBinding.isConstant()) {
       return null;
     }
@@ -288,15 +259,7 @@
 
     // Find a matching type converter.
     TypeLiteral<T> type = key.getTypeLiteral();
-    MatcherAndConverter matchingConverter = null;
-    for (MatcherAndConverter converter : converters) {
-      if (converter.getTypeMatcher().matches(type)) {
-        if (matchingConverter != null) {
-          errors.ambiguousTypeConversion(stringValue, source, type, matchingConverter, converter);
-        }
-        matchingConverter = converter;
-      }
-    }
+    MatcherAndConverter matchingConverter = state.getConverter(stringValue, type, errors, source);
 
     if (matchingConverter == null) {
       // No converter can handle the given type.
@@ -336,8 +299,8 @@
 
     ConvertedConstantBindingImpl(
         InjectorImpl injector, Key<T> key, T value, Binding<String> originalBinding) {
-      super(injector, key, originalBinding.getSource(), new ConstantFactory<T>(value),
-          Scopes.NO_SCOPE, LoadStrategy.LAZY);
+      super(injector, key, originalBinding.getSource(),
+          new ConstantFactory<T>(Initializables.of(value)), Scopes.NO_SCOPE, LoadStrategy.LAZY);
       this.value = value;
       provider = Providers.of(value);
       this.originalBinding = originalBinding;
@@ -360,14 +323,6 @@
     }
   }
 
-  <T> BindingImpl<T> createBindingFromType(
-      Key<T> key, LoadStrategy loadStrategy, Errors errors) throws ErrorsException {
-    BindingImpl<T> binding = createUnitializedBinding(
-        key, null, loadStrategy, errors);
-    initializeBinding(binding, errors);
-    return binding;
-  }
-
   <T> void initializeBinding(BindingImpl<T> binding, Errors errors) throws ErrorsException {
     // Put the partially constructed binding in the map a little early. This enables us to handle
     // circular dependencies. Example: FooImpl -> BarImpl -> FooImpl.
@@ -389,13 +344,6 @@
     }
   }
 
-  <T> BindingImpl<T> createUnitializedBinding(Key<T> key, Scope scope,
-      LoadStrategy loadStrategy, Errors errors) throws ErrorsException {
-    @SuppressWarnings("unchecked")
-    Class<T> type = (Class<T>) key.getRawType();
-    return createUnitializedBinding(key, type, scope, type, loadStrategy, errors);
-  }
-
   /**
    * Creates a binding for an injectable type with the given scope. Looks for a scope on the type if
    * none is specified.
@@ -441,7 +389,7 @@
     if (scope == null) {
       Class<? extends Annotation> scopeAnnotation = Annotations.findScopeAnnotation(errors, type);
       if (scopeAnnotation != null) {
-        scope = scopes.get(scopeAnnotation);
+        scope = state.getScope(scopeAnnotation);
         if (scope == null) {
           errors.withSource(type).scopeNotFound(scopeAnnotation);
         }
@@ -573,14 +521,23 @@
   }
 
   /**
-   * Returns a new just-in-time binding created by resolving {@code key}. This could be an
-   * injectable class (including those with @ImplementedBy, etc.), an automatically converted
-   * constant, a {@code Provider<X>} binding, etc.
+   * Returns a new just-in-time binding created by resolving {@code key}. The strategies used to
+   * create just-in-time bindings are:
+   * <ol>
+   *   <li>Internalizing Providers. If the requested binding is for {@code Provider<T>}, we delegate
+   *     to the binding for {@code T}.
+   *   <li>Converting constants.
+   *   <li>ImplementedBy and ProvidedBy annotations. Only for unannotated keys.
+   *   <li>The constructor of the raw type. Only for unannotated keys.
+   * </ol>
    *
    * @throws com.google.inject.internal.ErrorsException if the binding cannot be created.
    */
-  <T> BindingImpl<T> createJustInTimeBinding(Key<T> key, Errors errors)
-      throws ErrorsException {
+  <T> BindingImpl<T> createJustInTimeBinding(Key<T> key, Errors errors) throws ErrorsException {
+    if (state.isBlacklisted(key)) {
+      throw errors.childBindingAlreadySet(key).toException();
+    }
+
     // Handle cases where T is a Provider<?>.
     if (isProvider(key)) {
       // These casts are safe. We know T extends Provider<X> and that given Key<Provider<X>>,
@@ -612,7 +569,12 @@
     }
 
     // Create a binding based on the raw type.
-    return createBindingFromType(key, LoadStrategy.LAZY, errors);
+    @SuppressWarnings("unchecked")
+    Class<T> rawType = (Class<T>) key.getRawType();
+    BindingImpl<T> binding = createUnitializedBinding(key, rawType, null /* scope */, rawType,
+        LoadStrategy.LAZY, errors);
+    initializeBinding(binding, errors);
+    return binding;
   }
 
   <T> InternalFactory<? extends T> getInternalFactory(Key<T> key, Errors errors)
@@ -658,7 +620,7 @@
 
   // not test-covered
   public Map<Key<?>, Binding<?>> getBindings() {
-    return Collections.<Key<?>, Binding<?>>unmodifiableMap(explicitBindings);
+    return state.getExplicitBindingsThisLevel();
   }
 
   interface SingleInjectorFactory<M extends Member & AnnotatedElement> {
@@ -669,7 +631,7 @@
   private static class BindingsMultimap {
     final Multimap<TypeLiteral<?>, Binding<?>> multimap = Multimaps.newArrayListMultimap();
 
-    <T> void put(TypeLiteral<T> type, BindingImpl<T> binding) {
+    <T> void put(TypeLiteral<T> type, Binding<T> binding) {
       multimap.put(type, binding);
     }
 
@@ -975,17 +937,13 @@
     return getProvider(type).get();
   }
 
-  final ThreadLocal<InternalContext[]> localContext = new ThreadLocal<InternalContext[]>() {
-    protected InternalContext[] initialValue() {
-      return new InternalContext[1];
-    }
-  };
+  final ThreadLocal<InternalContext[]> localContext;
 
   /** Looks up thread local context. Creates (and removes) a new context if necessary. */
   <T> T callInContext(ContextualCallable<T> callable) throws ErrorsException {
     InternalContext[] reference = localContext.get();
     if (reference[0] == null) {
-      reference[0] = new InternalContext(this);
+      reference[0] = new InternalContext();
       try {
         return callable.call(reference[0]);
       } finally {
@@ -1007,7 +965,7 @@
 
   public String toString() {
     return new ToStringBuilder(Injector.class)
-        .add("bindings", explicitBindings)
+        .add("bindings", state.getExplicitBindingsThisLevel())
         .toString();
   }
 }
diff --git a/src/com/google/inject/InterceptorBindingProcessor.java b/src/com/google/inject/InterceptorBindingProcessor.java
index e48f67b..ef1c2f9 100644
--- a/src/com/google/inject/InterceptorBindingProcessor.java
+++ b/src/com/google/inject/InterceptorBindingProcessor.java
@@ -27,20 +27,20 @@
  */
 class InterceptorBindingProcessor extends AbstractProcessor {
 
-  private final ProxyFactoryBuilder proxyFactoryBuilder;
+  private final State state;
 
-  InterceptorBindingProcessor(Errors errors) {
+  InterceptorBindingProcessor(Errors errors, State state) {
     super(errors);
-    proxyFactoryBuilder = new ProxyFactoryBuilder();
+    this.state = state;
   }
 
   @Override public Boolean visitInterceptorBinding(InterceptorBinding command) {
-    proxyFactoryBuilder.intercept(
-        command.getClassMatcher(), command.getMethodMatcher(), command.getInterceptors());
+    state.addMethodAspect(new MethodAspect(
+        command.getClassMatcher(), command.getMethodMatcher(), command.getInterceptors()));
     return true;
   }
 
   ProxyFactory createProxyFactory() {
-    return proxyFactoryBuilder.create();
+    return new ProxyFactory(state.getMethodAspects());
   }
 }
diff --git a/src/com/google/inject/InternalContext.java b/src/com/google/inject/InternalContext.java
index bcb4e59..4183d0b 100644
--- a/src/com/google/inject/InternalContext.java
+++ b/src/com/google/inject/InternalContext.java
@@ -16,8 +16,6 @@
 
 package com.google.inject;
 
-import com.google.inject.internal.Errors;
-import com.google.inject.internal.ErrorsException;
 import com.google.inject.spi.Dependency;
 import java.util.HashMap;
 import java.util.Map;
@@ -30,14 +28,9 @@
  */
 class InternalContext {
 
-  private final InjectorImpl injector;
   private Map<Object, ConstructionContext<?>> constructionContexts;
   private Dependency dependency;
 
-  public InternalContext(InjectorImpl injector) {
-    this.injector = injector;
-  }
-
   @SuppressWarnings("unchecked")
   public <T> ConstructionContext<T> getConstructionContext(Object key) {
     if (constructionContexts == null) {
@@ -64,12 +57,4 @@
   public void setDependency(Dependency dependency) {
     this.dependency = dependency;
   }
-
-  /**
-   * Ensures that an object requiring injection at Injector-creation time has
-   * been injected before its use.
-   */
-  public void ensureMemberInjected(Errors errors, Object toInject) throws ErrorsException {
-    injector.memberInjector.ensureInjected(toInject, errors);
-  }
 }
diff --git a/src/com/google/inject/InternalFactoryToProviderAdapter.java b/src/com/google/inject/InternalFactoryToProviderAdapter.java
index b3a19f8..c506d79 100644
--- a/src/com/google/inject/InternalFactoryToProviderAdapter.java
+++ b/src/com/google/inject/InternalFactoryToProviderAdapter.java
@@ -27,24 +27,23 @@
 */
 class InternalFactoryToProviderAdapter<T> implements InternalFactory<T> {
 
-  private final Provider<? extends T> provider;
+  private final Initializable<Provider<? extends T>> initializable;
   private final Object source;
 
-  public InternalFactoryToProviderAdapter(Provider<? extends T> provider) {
-    this(provider, SourceProvider.UNKNOWN_SOURCE);
+  public InternalFactoryToProviderAdapter(Initializable<Provider<? extends T>> initializable) {
+    this(initializable, SourceProvider.UNKNOWN_SOURCE);
   }
 
   public InternalFactoryToProviderAdapter(
-      Provider<? extends T> provider, Object source) {
-    this.provider = checkNotNull(provider, "provider");
+      Initializable<Provider<? extends T>> initializable, Object source) {
+    this.initializable = checkNotNull(initializable, "provider");
     this.source = checkNotNull(source, "source");
   }
 
   public T get(Errors errors, InternalContext context, Dependency<?> dependency)
       throws ErrorsException {
     try {
-      context.ensureMemberInjected(errors, provider);
-      return errors.checkForNull(provider.get(), source, dependency);
+      return errors.checkForNull(initializable.get(errors).get(), source, dependency);
     } catch (RuntimeException userException) {
       Errors userErrors = ProvisionException.getErrors(userException);
       throw errors.withSource(source)
@@ -53,6 +52,6 @@
   }
 
   @Override public String toString() {
-    return provider.toString();
+    return initializable.toString();
   }
 }
diff --git a/src/com/google/inject/MethodAspect.java b/src/com/google/inject/MethodAspect.java
index 30515cc..de9e1c9 100644
--- a/src/com/google/inject/MethodAspect.java
+++ b/src/com/google/inject/MethodAspect.java
@@ -16,11 +16,11 @@
 
 package com.google.inject;
 
-import com.google.inject.matcher.Matcher;
 import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.inject.matcher.Matcher;
 import java.lang.reflect.Method;
+import java.util.Arrays;
 import java.util.List;
-
 import org.aopalliance.intercept.MethodInterceptor;
 
 /**
@@ -34,6 +34,13 @@
   final Matcher<? super Method> methodMatcher;
   final List<MethodInterceptor> interceptors;
 
+  /**
+   * @param classMatcher matches classes the interceptor should apply to. For example: {@code
+   *     only(Runnable.class)}.
+   * @param methodMatcher matches methods the interceptor should apply to. For example: {@code
+   *     annotatedWith(Transactional.class)}.
+   * @param interceptors to apply
+   */
   MethodAspect(Matcher<? super Class<?>> classMatcher,
       Matcher<? super Method> methodMatcher, List<MethodInterceptor> interceptors) {
     this.classMatcher = checkNotNull(classMatcher, "class matcher");
@@ -41,6 +48,11 @@
     this.interceptors = checkNotNull(interceptors, "interceptors");
   }
 
+  MethodAspect(Matcher<? super Class<?>> classMatcher,
+      Matcher<? super Method> methodMatcher, MethodInterceptor... interceptors) {
+    this(classMatcher, methodMatcher, Arrays.asList(interceptors));
+  }
+
   boolean matches(Class<?> clazz) {
     return classMatcher.matches(clazz);
   }
diff --git a/src/com/google/inject/ProxyFactory.java b/src/com/google/inject/ProxyFactory.java
index 07c9227..eef3aef 100644
--- a/src/com/google/inject/ProxyFactory.java
+++ b/src/com/google/inject/ProxyFactory.java
@@ -40,8 +40,7 @@
 import org.aopalliance.intercept.MethodInterceptor;
 
 /**
- * Proxies classes applying interceptors to methods as specified in
- * {@link ProxyFactoryBuilder}.
+ * Proxies classes applying interceptors to methods.
  *
  * @author crazybob@google.com (Bob Lee)
  */
diff --git a/src/com/google/inject/ProxyFactoryBuilder.java b/src/com/google/inject/ProxyFactoryBuilder.java
deleted file mode 100644
index 36a3482..0000000
--- a/src/com/google/inject/ProxyFactoryBuilder.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * 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 com.google.common.collect.Lists;
-import com.google.inject.matcher.Matcher;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import org.aopalliance.intercept.MethodInterceptor;
-
-/**
- * Creates a {@link com.google.inject.ProxyFactory}.
- *
- * @author crazybob@google.com (Bob Lee)
- */
-class ProxyFactoryBuilder {
-
-  final List<MethodAspect> methodAspects = Lists.newArrayList();
-
-  /**
-   * Applies the given method interceptor to the methods matched by the class and method matchers.
-   *
-   * @param classMatcher matches classes the interceptor should apply to. For example: {@code
-   * only(Runnable.class)}.
-   * @param methodMatcher matches methods the interceptor should apply to. For example: {@code
-   * annotatedWith(Transactional.class)}.
-   * @param interceptors to apply
-   */
-  public ProxyFactoryBuilder intercept(Matcher<? super Class<?>> classMatcher,
-      Matcher<? super Method> methodMatcher,
-      List<MethodInterceptor> interceptors) {
-    methodAspects.add(new MethodAspect(classMatcher, methodMatcher, interceptors));
-    return this;
-  }
-
-  public ProxyFactoryBuilder intercept(Matcher<? super Class<?>> classMatcher,
-      Matcher<? super Method> methodMatcher,
-      MethodInterceptor... interceptors) {
-    return intercept(classMatcher, methodMatcher, Arrays.asList(interceptors));
-  }
-
-  /** Creates a {@code ProxyFactory}. */
-  public ProxyFactory create() {
-    return new ProxyFactory(new ArrayList<MethodAspect>(methodAspects));
-  }
-}
diff --git a/src/com/google/inject/ScopeBindingProcessor.java b/src/com/google/inject/ScopeBindingProcessor.java
index 1458df5..5936fe9 100644
--- a/src/com/google/inject/ScopeBindingProcessor.java
+++ b/src/com/google/inject/ScopeBindingProcessor.java
@@ -21,7 +21,6 @@
 import com.google.inject.internal.Errors;
 import com.google.inject.spi.ScopeBinding;
 import java.lang.annotation.Annotation;
-import java.util.Map;
 
 /**
  * Handles {@link Binder#bindScope} commands.
@@ -31,12 +30,12 @@
  */
 class ScopeBindingProcessor extends AbstractProcessor {
 
-  private final Map<Class<? extends Annotation>, Scope> scopes;
+  private final State state;
 
   ScopeBindingProcessor(Errors errors,
-      Map<Class<? extends Annotation>, Scope> scopes) {
+      State state) {
     super(errors);
-    this.scopes = scopes;
+    this.state = state;
   }
 
   @Override public Boolean visitScopeBinding(ScopeBinding command) {
@@ -54,11 +53,11 @@
       // Go ahead and bind anyway so we don't get collateral errors.
     }
 
-    Scope existing = scopes.get(checkNotNull(annotationType, "annotation type"));
+    Scope existing = state.getScope(checkNotNull(annotationType, "annotation type"));
     if (existing != null) {
       errors.duplicateScopes(existing, annotationType, scope);
     } else {
-      scopes.put(annotationType, checkNotNull(scope, "scope"));
+      state.putAnnotation(annotationType, checkNotNull(scope, "scope"));
     }
 
     return true;
diff --git a/src/com/google/inject/Scopes.java b/src/com/google/inject/Scopes.java
index e2597cc..f8a668f 100644
--- a/src/com/google/inject/Scopes.java
+++ b/src/com/google/inject/Scopes.java
@@ -93,6 +93,7 @@
     }
     Provider<T> scoped = scope.scope(key,
         new ProviderToInternalFactoryAdapter<T>(injector, creator));
-    return new InternalFactoryToProviderAdapter<T>(scoped);
+    return new InternalFactoryToProviderAdapter<T>(
+        Initializables.<Provider<? extends T>>of(scoped));
   }
 }
diff --git a/src/com/google/inject/State.java b/src/com/google/inject/State.java
new file mode 100644
index 0000000..580eeae
--- /dev/null
+++ b/src/com/google/inject/State.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright (C) 2008 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 com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.MatcherAndConverter;
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The inheritable data within an injector. This class is intended to allow parent and local
+ * injector data to be accessed as a unit.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+interface State {
+
+  static final State NONE = new State() {
+    public State parent() {
+      throw new UnsupportedOperationException();
+    }
+
+    public <T> BindingImpl<T> getExplicitBinding(Key<T> key) {
+      return null;
+    }
+
+    public Map<Key<?>, Binding<?>> getExplicitBindingsThisLevel() {
+      throw new UnsupportedOperationException();
+    }
+
+    public void putBinding(Key<?> key, BindingImpl<?> binding) {
+      throw new UnsupportedOperationException();
+    }
+
+    public Scope getScope(Class<? extends Annotation> scopingAnnotation) {
+      return null;
+    }
+
+    public void putAnnotation(Class<? extends Annotation> annotationType, Scope scope) {
+      throw new UnsupportedOperationException();
+    }
+
+    public void addConverter(MatcherAndConverter matcherAndConverter) {
+      throw new UnsupportedOperationException();
+    }
+
+    public MatcherAndConverter getConverter(String stringValue, TypeLiteral<?> type, Errors errors,
+        Object source) {
+      throw new UnsupportedOperationException();
+    }
+
+    public Iterable<MatcherAndConverter> getConvertersThisLevel() {
+      return ImmutableSet.of();
+    }
+
+    public void addMethodAspect(MethodAspect methodAspect) {
+      throw new UnsupportedOperationException();
+    }
+
+    public List<MethodAspect> getMethodAspects() {
+      return ImmutableList.of();
+    }
+
+    public void blacklist(Key<?> key) {
+    }
+
+    public boolean isBlacklisted(Key<?> key) {
+      return true;
+    }
+  };
+
+  State parent();
+
+  /** Gets a binding which was specified explicitly in a module, or null. */
+  <T> BindingImpl<T> getExplicitBinding(Key<T> key);
+
+  /** Returns the explicit bindings at this level only. */
+  Map<Key<?>, Binding<?>> getExplicitBindingsThisLevel();
+
+  void putBinding(Key<?> key, BindingImpl<?> binding);
+
+  /** Returns the matching scope, or null. */
+  Scope getScope(Class<? extends Annotation> scopingAnnotation);
+
+  void putAnnotation(Class<? extends Annotation> annotationType, Scope scope);
+
+  void addConverter(MatcherAndConverter matcherAndConverter);
+
+  /** Returns the matching converter for {@code type}, or null if none match. */
+  MatcherAndConverter getConverter(
+      String stringValue, TypeLiteral<?> type, Errors errors, Object source);
+
+  /** Returns all converters at this level only. */
+  Iterable<MatcherAndConverter> getConvertersThisLevel();
+
+  void addMethodAspect(MethodAspect methodAspect);
+
+  List<MethodAspect> getMethodAspects();
+
+  /**
+   * Forbids the corresponding injector from creating a binding to {@code key}. Child injectors
+   * blacklist their bound keys on their parent injectors to prevent just-in-time bindings on the
+   * parent injector that would conflict.
+   */
+  void blacklist(Key<?> key);
+
+  /**
+   * Returns true if {@code key} is forbidden from being bound in this injector. This indicates that
+   * one of this injector's descendent's has bound the key.
+   */
+  boolean isBlacklisted(Key<?> key);
+}
diff --git a/src/com/google/inject/TypeConverterBindingProcessor.java b/src/com/google/inject/TypeConverterBindingProcessor.java
index 1f2b291..b9d1fd3 100644
--- a/src/com/google/inject/TypeConverterBindingProcessor.java
+++ b/src/com/google/inject/TypeConverterBindingProcessor.java
@@ -29,7 +29,6 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
-import java.util.List;
 
 /**
  * Handles {@link Binder#convertToTypes} commands.
@@ -39,11 +38,11 @@
  */
 class TypeConverterBindingProcessor extends AbstractProcessor {
 
-  private final List<MatcherAndConverter> converters;
+  private final State state;
 
-  TypeConverterBindingProcessor(Errors errors, List<MatcherAndConverter> converters) {
+  TypeConverterBindingProcessor(Errors errors, State state) {
     super(errors);
-    this.converters = converters;
+    this.state = state;
 
     // Configure type converters.
     convertToPrimitiveType(int.class, Integer.class);
@@ -162,11 +161,12 @@
 
   private void internalConvertToTypes(Matcher<? super TypeLiteral<?>> typeMatcher,
       TypeConverter converter) {
-    converters.add(new MatcherAndConverter(typeMatcher, converter, SourceProvider.UNKNOWN_SOURCE));
+    state.addConverter(
+        new MatcherAndConverter(typeMatcher, converter, SourceProvider.UNKNOWN_SOURCE));
   }
 
   @Override public Boolean visitTypeConverterBinding(TypeConverterBinding command) {
-    converters.add(new MatcherAndConverter(
+    state.addConverter(new MatcherAndConverter(
         command.getTypeMatcher(), command.getTypeConverter(), command.getSource()));
     return true;
   }
diff --git a/src/com/google/inject/WeakKeySet.java b/src/com/google/inject/WeakKeySet.java
new file mode 100644
index 0000000..b1c9799
--- /dev/null
+++ b/src/com/google/inject/WeakKeySet.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (C) 2008 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 com.google.common.collect.Sets;
+import java.util.Set;
+
+/**
+ * Minimal set that doesn't hold strong references to the contained keys.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class WeakKeySet {
+
+  /**
+   * We store strings rather than keys so we don't hold strong references.
+   *
+   * <p>One potential problem with this approach is that parent and child injectors cannot define
+   * keys whose class names are equal but class loaders are different. This shouldn't be an issue
+   * in practice.
+   */
+  private Set<String> backingSet = Sets.newHashSet();
+
+  public boolean add(Key<?> key) {
+    return backingSet.add(key.toString());
+  }
+
+  public boolean contains(Object o) {
+    return o instanceof Key && backingSet.contains(o.toString());
+  }
+}
diff --git a/src/com/google/inject/internal/Errors.java b/src/com/google/inject/internal/Errors.java
index 228d2d1..e3a3cb3 100644
--- a/src/com/google/inject/internal/Errors.java
+++ b/src/com/google/inject/internal/Errors.java
@@ -241,6 +241,10 @@
     return addMessage("A binding to %s was already configured at %s.", key, source);
   }
 
+  public Errors childBindingAlreadySet(Key<?> key) {
+    return addMessage("A binding to %s already exists on a child injector.", key);
+  }
+
   public Errors errorInjectingMethod(Throwable cause) {
     return addMessage(cause, "Error injecting method, %s", cause);
   }
diff --git a/test/com/google/inject/ParentInjectorTest.java b/test/com/google/inject/ParentInjectorTest.java
index 41e8118..953549b 100644
--- a/test/com/google/inject/ParentInjectorTest.java
+++ b/test/com/google/inject/ParentInjectorTest.java
@@ -16,83 +16,204 @@
 
 package com.google.inject;
 
+import com.google.common.collect.ImmutableList;
+import static com.google.inject.Asserts.assertContains;
+import com.google.inject.matcher.Matchers;
+import com.google.inject.name.Names;
+import com.google.inject.spi.TypeConverter;
+import static java.lang.annotation.ElementType.TYPE;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+import java.util.List;
 import junit.framework.TestCase;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
 
 /**
- *
+ * @author jessewilson@google.com (Jesse Wilson)
  */
 public class ParentInjectorTest extends TestCase {
 
-  Module baseModule = new AbstractModule() {
+  public void testParentAndChildCannotShareExplicitBindings() {
+    Injector parent = Guice.createInjector(bindsA);
+    try {
+      parent.createChildInjector(bindsA);
+      fail("Created the same explicit binding on both parent and child");
+    } catch (CreationException e) {
+      assertContains(e.getMessage(), "A binding to ", A.class.getName(), " was already configured",
+          " at ", getClass().getName(), ".configure(ParentInjectorTest.java:",
+          " at ", getClass().getName(), ".configure(ParentInjectorTest.java:");
+    }
+  }
+
+  public void testParentJitBindingWontClobberChildBinding() {
+    Injector parent = Guice.createInjector();
+    parent.createChildInjector(bindsA);
+    try {
+      parent.getInstance(A.class);
+      fail("Created a just-in-time binding on the parent that's the same as a child's binding");
+    } catch (ProvisionException e) {
+      assertContains(e.getMessage(), "A binding to ", A.class.getName(),
+          " already exists on a child injector.");
+    }
+  }
+
+  public void testJustInTimeBindingsAreSharedWithParentIfPossible() {
+    Injector parent = Guice.createInjector();
+    Injector child = parent.createChildInjector();
+    assertSame(child.getInstance(A.class), parent.getInstance(A.class));
+
+    Injector anotherChild = parent.createChildInjector();
+    assertSame(anotherChild.getInstance(A.class), parent.getInstance(A.class));
+
+    Injector grandchild = child.createChildInjector();
+    assertSame(grandchild.getInstance(A.class), parent.getInstance(A.class));
+  }
+
+  public void testBindingsInherited() {
+    Injector parent = Guice.createInjector(bindsB);
+    Injector child = parent.createChildInjector();
+    assertSame(RealB.class, child.getInstance(B.class).getClass());
+  }
+
+  public void testChildBindingsNotVisibleToParent() {
+    Injector parent = Guice.createInjector();
+    parent.createChildInjector(bindsB);
+    try {
+      parent.getBinding(B.class);
+      fail();
+    } catch (ProvisionException expected) {
+    }
+  }
+
+  public void testScopesInherited() {
+    Injector parent = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        bindScope(MyScope.class, Scopes.SINGLETON);
+      }
+    });
+    Injector child = parent.createChildInjector(new AbstractModule() {
+      @Override protected void configure() {
+        bind(A.class).in(MyScope.class);
+      }
+    });
+    assertSame(child.getInstance(A.class), child.getInstance(A.class));
+  }
+
+  public void testInterceptorsInherited() {
+    Injector parent = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        super.bindInterceptor(Matchers.any(), Matchers.returns(Matchers.identicalTo(A.class)),
+            returnNullInterceptor);
+      }
+    });
+
+    Injector child = parent.createChildInjector(new AbstractModule() {
+      protected void configure() {
+        bind(C.class);
+      }
+    });
+
+    assertNull(child.getInstance(C.class).interceptedMethod());
+  }
+
+  public void testTypeConvertersInherited() {
+    Injector parent = Guice.createInjector(bindListConverterModule);
+    Injector child = parent.createChildInjector(bindStringNamedB);
+
+    assertEquals(ImmutableList.of(), child.getInstance(Key.get(List.class, Names.named("B"))));
+  }
+
+  public void testTypeConvertersConflicting() {
+    Injector parent = Guice.createInjector(bindListConverterModule);
+    Injector child = parent.createChildInjector(bindListConverterModule, bindStringNamedB);
+
+    try {
+      child.getInstance(Key.get(List.class, Names.named("B")));
+      fail();
+    } catch (ProvisionException expected) {
+      Asserts.assertContains(expected.getMessage(), "Multiple converters can convert");
+    }
+  }
+
+  public void testInjectorInjectionSpanningInjectors() {
+    Injector parent = Guice.createInjector();
+    Injector child = parent.createChildInjector(new AbstractModule() {
+      protected void configure() {
+        bind(D.class);
+      }
+    });
+
+    D d = child.getInstance(D.class);
+    assertSame(d.injector, child);
+
+    E e = child.getInstance(E.class);
+    assertSame(e.injector, parent);
+  }
+
+  @Singleton
+  static class A {}
+
+  private final Module bindsA = new AbstractModule() {
     protected void configure() {
-      bind(Foo.class).to(FooImpl.class).in(Scopes.SINGLETON);
-      bind(Bar.class).to(BarOne.class);
+      bind(A.class).toInstance(new A());
     }
   };
 
-  Injector injector = Guice.createInjector(baseModule);
-  Injector childInjector = Guice.createInjector(injector,
-      new AbstractModule() {
-        protected void configure() {
-          bind(Bar.class).to(BarTwo.class);
-          bind(Bus.class).to(BusImpl.class);
-        }
-      });
+  interface B {}
+  static class RealB implements B {}
 
-  /** Make sure that singletons are properly handled **/
-  public void testExplicitSingleton() throws Exception {
-    Foo fooParent = injector.getInstance(Foo.class);
-    Foo fooChild = childInjector.getInstance(Foo.class);
+  private final Module bindsB = new AbstractModule() {
+    protected void configure() {
+      bind(B.class).to(RealB.class);
+    }
+  };
 
-    assertTrue(fooChild instanceof FooImpl);
-    assertSame(fooParent, fooChild);
+  @Target(TYPE) @Retention(RUNTIME) @ScopeAnnotation
+  public @interface MyScope {}
+
+  private final MethodInterceptor returnNullInterceptor = new MethodInterceptor() {
+    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
+      return null;
+    }
+  };
+
+  private final TypeConverter listConverter = new TypeConverter() {
+    public Object convert(String value, TypeLiteral<?> toType) {
+      return ImmutableList.of();
+    }
+  };
+
+  private final Module bindListConverterModule = new AbstractModule() {
+    protected void configure() {
+      convertToTypes(Matchers.any(), listConverter);
+    }
+  };
+
+  private final Module bindStringNamedB = new AbstractModule() {
+    protected void configure() {
+      bind(String.class).annotatedWith(Names.named("B")).toInstance("buzz");
+    }
+  };
+
+  public static class C {
+    public A interceptedMethod() {
+      return new A();
+    }
   }
 
-  /**
-   * Make sure that when there are non scoped bindings in the parent,
-   * they are not used.
-   */
-  public void testNonSingletons() throws Exception {
-    Bar barParent = injector.getInstance(Bar.class);
-    Bar barChild = childInjector.getInstance(Bar.class);
-
-    assertNotSame(barParent, barChild);
-    assertTrue(barParent instanceof BarOne);
-    assertTrue(barChild instanceof BarTwo);
+  static class D {
+    @Inject Injector injector;
   }
 
-  public void testImplicitSingleton() throws Exception {
-    Car carParent = injector.getInstance(Car.class);
-    Car carChild = childInjector.getInstance(Car.class);
-
-    assertNotNull(carParent);
-    assertNotNull(carChild);
-    assertSame(carParent, carChild);
+  static class E {
+    @Inject Injector injector;
   }
 
-  public void testImplicitSingletonFromChild() throws Exception {
-    Truck truck = childInjector.getInstance(Truck.class);
-    assertNotNull(truck);
-  }
-
-  interface Foo {}
-  @Singleton
-  static class FooImpl implements Foo {}
-
-  interface Bar {}
-  static class BarOne implements Bar {}
-  static class BarTwo implements Bar {}
-
-  @Singleton
-  static class Car {}
-
-  interface Bus {}
-  static class BusImpl implements Bus {}
-
-  @Singleton
-  private static class Truck {
-    @Inject
-    Truck(Bus bus) {}
-  }
-
+  private final Module bindsD = new AbstractModule() {
+    protected void configure() {
+      bind(D.class);
+    }
+  };
 }
diff --git a/test/com/google/inject/ProxyFactoryTest.java b/test/com/google/inject/ProxyFactoryTest.java
index 18be9d1..4a0d214 100644
--- a/test/com/google/inject/ProxyFactoryTest.java
+++ b/test/com/google/inject/ProxyFactoryTest.java
@@ -17,6 +17,7 @@
 
 package com.google.inject;
 
+import com.google.common.collect.Lists;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
 import static com.google.inject.matcher.Matchers.annotatedWith;
@@ -27,6 +28,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.InvocationTargetException;
+import java.util.List;
 import junit.framework.TestCase;
 import org.aopalliance.intercept.MethodInterceptor;
 import org.aopalliance.intercept.MethodInvocation;
@@ -36,13 +38,14 @@
  */
 public class ProxyFactoryTest extends TestCase {
 
+  List<MethodAspect> aspects = Lists.newArrayList();
+
   public void testSimpleCase()
       throws NoSuchMethodException, InvocationTargetException, ErrorsException {
     SimpleInterceptor interceptor = new SimpleInterceptor();
 
-    ProxyFactoryBuilder builder = new ProxyFactoryBuilder();
-    builder.intercept(any(), any(), interceptor);
-    ProxyFactory factory = builder.create();
+    aspects.add(new MethodAspect(any(), any(), interceptor));
+    ProxyFactory factory = new ProxyFactory(aspects);
 
     ConstructionProxy<Simple> constructionProxy = factory
         .createConstructionProxy(new Errors(), InjectionPoint.forConstructorOf(Simple.class));
@@ -74,11 +77,8 @@
       throws NoSuchMethodException, InvocationTargetException, ErrorsException {
     SimpleInterceptor interceptor = new SimpleInterceptor();
 
-    ProxyFactoryBuilder builder = new ProxyFactoryBuilder();
-
-    builder.intercept(
-        only(Bar.class), annotatedWith(Intercept.class), interceptor);
-    ProxyFactory factory = builder.create();
+    aspects.add(new MethodAspect(only(Bar.class), annotatedWith(Intercept.class), interceptor));
+    ProxyFactory factory = new ProxyFactory(aspects);
 
     ConstructionProxy<Foo> fooFactory =
         factory.get(new Errors(), InjectionPoint.forConstructorOf(Foo.class));
@@ -131,9 +131,8 @@
       throws InvocationTargetException, NoSuchMethodException, ErrorsException {
     SimpleInterceptor interceptor = new SimpleInterceptor();
 
-    ProxyFactoryBuilder builder = new ProxyFactoryBuilder();
-    builder.intercept(any(), any(), interceptor);
-    ProxyFactory factory = builder.create();
+    aspects.add(new MethodAspect(any(), any(), interceptor));
+    ProxyFactory factory = new ProxyFactory(aspects);
 
     ConstructionProxy<A> constructor =
         factory.get(new Errors(), InjectionPoint.forConstructorOf(A.class));
@@ -147,9 +146,8 @@
       throws NoSuchMethodException, InvocationTargetException, ErrorsException {
     SimpleInterceptor interceptor = new SimpleInterceptor();
 
-    ProxyFactoryBuilder builder = new ProxyFactoryBuilder();
-    builder.intercept(not(any()), not(any()), interceptor);
-    ProxyFactory factory = builder.create();
+    aspects.add(new MethodAspect(not(any()), not(any()), interceptor));
+    ProxyFactory factory = new ProxyFactory(aspects);
 
     ConstructionProxy<A> constructor =
         factory.get(new Errors(), InjectionPoint.forConstructorOf(A.class));
@@ -171,9 +169,8 @@
     DoubleInterceptor doubleInterceptor = new DoubleInterceptor();
     CountingInterceptor countingInterceptor = new CountingInterceptor();
 
-    ProxyFactoryBuilder builder = new ProxyFactoryBuilder();
-    builder.intercept(any(), any(), doubleInterceptor, countingInterceptor);
-    ProxyFactory factory = builder.create();
+    aspects.add(new MethodAspect(any(), any(), doubleInterceptor, countingInterceptor));
+    ProxyFactory factory = new ProxyFactory(aspects);
 
     ConstructionProxy<Counter> constructor =
         factory.get(new Errors(), InjectionPoint.forConstructorOf(Counter.class));