More cleanup of Errors -- including sources as a factory method rather than push/pop methods (that require try/finally clauses)

I'll want to get this for InjectionPoints sooner or later also.

Also merging AddMessageCommand and AddThrowableErrorCommand, with a command that ads an spi.Message.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@532 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/BindCommandProcessor.java b/src/com/google/inject/BindCommandProcessor.java
index e44d18a..765a225 100644
--- a/src/com/google/inject/BindCommandProcessor.java
+++ b/src/com/google/inject/BindCommandProcessor.java
@@ -201,19 +201,19 @@
       Class<? extends Annotation> annotationType = key.getAnnotationType();
 
       if (!Annotations.isRetainedAtRuntime(annotationType)) {
-        errors.at(StackTraceElements.forType(annotationType)).missingRuntimeRetention(source);
+        errors.withSource(StackTraceElements.forType(annotationType)).missingRuntimeRetention(source);
       }
 
       if (!Key.isBindingAnnotation(annotationType)) {
-        errors.at(StackTraceElements.forType(annotationType)).missingBindingAnnotation(source);
+        errors.withSource(StackTraceElements.forType(annotationType)).missingBindingAnnotation(source);
       }
     }
 
     Class<? super T> rawType = key.getRawType();
     if (!Classes.isConcrete(rawType)) {
-      Class<? extends Annotation> scopeAnnotation = Scopes.getScopeAnnotation(errors, rawType);
+      Class<? extends Annotation> scopeAnnotation = Scopes.findScopeAnnotation(errors, rawType);
       if (scopeAnnotation != null) {
-        errors.at(StackTraceElements.forType(rawType))
+        errors.withSource(StackTraceElements.forType(rawType))
             .scopeAnnotationOnAbstractType(scopeAnnotation, rawType, source);
       }
     }
diff --git a/src/com/google/inject/BoundProviderFactory.java b/src/com/google/inject/BoundProviderFactory.java
index e699fc1..22c7fc1 100644
--- a/src/com/google/inject/BoundProviderFactory.java
+++ b/src/com/google/inject/BoundProviderFactory.java
@@ -38,13 +38,10 @@
   }
 
   public void notify(final InjectorImpl injector, final Errors errors) {
-    errors.pushSource(source);
     try {
-      providerFactory = injector.getInternalFactory(providerKey, errors);
+      providerFactory = injector.getInternalFactory(providerKey, errors.withSource(source));
     } catch (ErrorsException e) {
       errors.merge(e.getErrors());
-    } finally {
-      errors.popSource(source);
     }
   }
 
diff --git a/src/com/google/inject/CommandProcessor.java b/src/com/google/inject/CommandProcessor.java
index ec10502..918cb57 100644
--- a/src/com/google/inject/CommandProcessor.java
+++ b/src/com/google/inject/CommandProcessor.java
@@ -16,8 +16,7 @@
 
 package com.google.inject;
 
-import com.google.inject.commands.AddMessageErrorCommand;
-import com.google.inject.commands.AddThrowableErrorCommand;
+import com.google.inject.commands.AddMessageCommand;
 import com.google.inject.commands.BindCommand;
 import com.google.inject.commands.BindConstantCommand;
 import com.google.inject.commands.BindInterceptorCommand;
@@ -41,29 +40,29 @@
  */
 abstract class CommandProcessor implements Command.Visitor<Boolean> {
 
-  protected final Errors errors;
+  protected Errors errors;
 
   protected CommandProcessor(Errors errors) {
     this.errors = errors;
   }
 
   public void processCommands(List<Command> commands) {
-    for (Iterator<Command> i = commands.iterator(); i.hasNext(); ) {
-      Command command = i.next();
-      errors.pushSource(command.getSource());
-      Boolean allDone = command.acceptVisitor(this);
-      if (allDone) {
-        i.remove();
+    Errors errorsAnyCommand = this.errors;
+    try {
+      for (Iterator<Command> i = commands.iterator(); i.hasNext(); ) {
+        Command command = i.next();
+        this.errors = errorsAnyCommand.withSource(command.getSource());
+        Boolean allDone = command.acceptVisitor(this);
+        if (allDone) {
+          i.remove();
+        }
       }
-      errors.popSource(command.getSource());
+    } finally {
+      this.errors = errorsAnyCommand;
     }
   }
 
-  public Boolean visitAddMessageError(AddMessageErrorCommand command) {
-    return false;
-  }
-
-  public Boolean visitAddError(AddThrowableErrorCommand command) {
+  public Boolean visitAddMessage(AddMessageCommand command) {
     return false;
   }
 
diff --git a/src/com/google/inject/ConstructorInjector.java b/src/com/google/inject/ConstructorInjector.java
index 66cdada..4cff625 100644
--- a/src/com/google/inject/ConstructorInjector.java
+++ b/src/com/google/inject/ConstructorInjector.java
@@ -52,15 +52,11 @@
       throws ErrorsException {
     Constructor constructor = constructionProxy.getConstructor();
     Object source = StackTraceElements.forMember(constructor);
-    errors.pushSource(source);
-    try {
-      return constructionProxy.getParameters().isEmpty()
-          ? null // default constructor.
-          : injector.getParametersInjectors(constructor,
-              constructionProxy.getParameters(), errors);
-    } finally {
-      errors.popSource(source);
-    }
+    errors = errors.withSource(source);
+    return constructionProxy.getParameters().isEmpty()
+        ? null // default constructor.
+        : injector.getParametersInjectors(constructor,
+            constructionProxy.getParameters(), errors);
   }
 
   /**
diff --git a/src/com/google/inject/ErrorsCommandProcessor.java b/src/com/google/inject/ErrorsCommandProcessor.java
index 6b0fdbb..d755391 100644
--- a/src/com/google/inject/ErrorsCommandProcessor.java
+++ b/src/com/google/inject/ErrorsCommandProcessor.java
@@ -16,9 +16,11 @@
 
 package com.google.inject;
 
-import com.google.inject.commands.AddMessageErrorCommand;
-import com.google.inject.commands.AddThrowableErrorCommand;
+import com.google.inject.commands.AddMessageCommand;
 import com.google.inject.internal.Errors;
+import com.google.inject.spi.Message;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /**
  * Handles {@link Binder#addError} commands.
@@ -28,17 +30,27 @@
  */
 class ErrorsCommandProcessor extends CommandProcessor {
 
+  private static final Logger logger = Logger.getLogger(Guice.class.getName());
+
   ErrorsCommandProcessor(Errors errors) {
     super(errors);
   }
 
-  @Override public Boolean visitAddMessageError(AddMessageErrorCommand command) {
-    errors.userReportedError(command.getMessage(), command.getArguments());
+  @Override public Boolean visitAddMessage(AddMessageCommand command) {
+    Message message = command.getMessage();
+    if (message.getCause() != null) {
+      String rootMessage = getRootMessage(message.getCause());
+      logger.log(Level.INFO,
+          "An exception was caught and reported. Message: " + rootMessage,
+          message.getCause());
+    }
+
+    errors.addMessage(message);
     return true;
   }
 
-  @Override public Boolean visitAddError(AddThrowableErrorCommand command) {
-    errors.exceptionReportedByModuleSeeLogs(command.getThrowable());
-    return true;
+  public static String getRootMessage(Throwable t) {
+    Throwable cause = t.getCause();
+    return cause == null ? t.toString() : getRootMessage(cause);
   }
 }
diff --git a/src/com/google/inject/FactoryProxy.java b/src/com/google/inject/FactoryProxy.java
index 67f89e6..cf7fe4c 100644
--- a/src/com/google/inject/FactoryProxy.java
+++ b/src/com/google/inject/FactoryProxy.java
@@ -41,13 +41,10 @@
   }
 
   public void notify(final InjectorImpl injector, final Errors errors) {
-    errors.pushSource(source);
     try {
-      targetFactory = injector.getInternalFactory(targetKey, errors);
+      targetFactory = injector.getInternalFactory(targetKey, errors.withSource(source));
     } catch (ErrorsException e) {
       errors.merge(e.getErrors());
-    } finally {
-      errors.popSource(source);
     }
   }
 
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index 55d47ea..f2180f0 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -149,7 +149,7 @@
 
   /** Returns the binding for {@code key} */
   public <T> BindingImpl<T> getBinding(Key<T> key) {
-    Errors errors = new Errors();
+    Errors errors = new Errors(StackTraceElements.forType(key.getRawType()));
     try {
       BindingImpl<T> result = getBindingOrThrow(key, errors);
       ProvisionException.throwNewIfNonEmpty(errors);
@@ -488,11 +488,11 @@
     }
 
     if (scope == null) {
-      Class<? extends Annotation> scopeAnnotation = Scopes.getScopeAnnotation(errors, type);
+      Class<? extends Annotation> scopeAnnotation = Scopes.findScopeAnnotation(errors, type);
       if (scopeAnnotation != null) {
         scope = scopes.get(scopeAnnotation);
         if (scope == null) {
-          errors.at(StackTraceElements.forType(type)).scopeNotFound(scopeAnnotation);
+          errors.withSource(StackTraceElements.forType(type)).scopeNotFound(scopeAnnotation);
         }
       }
     }
@@ -639,9 +639,9 @@
       // Look for a binding without annotation attributes or return null.
       if (key.hasAttributes()) {
         try {
-          return getBindingOrThrow(key.withoutAttributes(), new Errors());
-        }
-        catch (ErrorsException ignored) {
+          Errors ignored = new Errors();
+          return getBindingOrThrow(key.withoutAttributes(), ignored);
+        } catch (ErrorsException ignored) {
           // throw with a more appropriate message below
         }
       }
@@ -659,7 +659,10 @@
     return getBindingOrThrow(key, errors).internalFactory;
   }
 
-  /** Field and method injectors. */
+  /**
+   * Field and method injectors. Each value is either an Errors or a
+   * {@code List<SingleMemberInjector>}.
+   */
   private final Map<Class<?>, Object> injectors = new ReferenceCache<Class<?>, Object>() {
     protected Object create(Class<?> key) {
       Errors errors = new Errors();
@@ -735,15 +738,14 @@
         continue;
       }
 
-      Errors errorsForMember = inject.optional() ? new Errors() : errors;
       Object source = StackTraceElements.forMember(member);
-      errorsForMember.pushSource(source);
+      Errors errorsForMember = inject.optional()
+          ? new Errors(source)
+          : errors.withSource(source);
       try {
         injectors.add(injectorFactory.create(this, member, errorsForMember));
       } catch (ErrorsException ignoredForNow) {
         // if this was an optional injection, it is completely ignored
-      } finally {
-        errorsForMember.popSource(source);
       }
     }
   }
@@ -795,12 +797,7 @@
       final Key<?> key = Keys.get(field.getGenericType(), field, field.getAnnotations(), errors);
 
       Object source = StackTraceElements.forMember(field);
-      errors.pushSource(source);
-      try {
-        factory = injector.getInternalFactory(key, errors);
-      } finally {
-        errors.popSource(source);
-      }
+      factory = injector.getInternalFactory(key, errors.withSource(source));
 
       injectionPoint = InjectionPoint.newInstance(
           field, Nullability.allowsNull(field.getAnnotations()), key);
@@ -863,12 +860,7 @@
       Member member, final Errors errors) throws ErrorsException {
     InternalFactory<? extends T> factory;
     Object source = StackTraceElements.forMember(member);
-    errors.pushSource(source);
-    try {
-      factory = getInternalFactory(parameter.getKey(), errors);
-    } finally {
-      errors.popSource(source);
-    }
+    factory = getInternalFactory(parameter.getKey(), errors.withSource(source));
 
     InjectionPoint<T> injectionPoint = InjectionPoint.newInstance(
         member, parameter.getIndex(), parameter.allowsNull(), parameter.getKey());
@@ -947,7 +939,7 @@
   final Map<Class<?>, Object> constructors = new ReferenceCache<Class<?>, Object>() {
     @SuppressWarnings("unchecked")
     protected Object create(Class<?> implementation) {
-      Errors errors = new Errors();
+      Errors errors = new Errors(StackTraceElements.forType(implementation));
       try {
         ConstructorInjector result = new ConstructorInjector(
             errors, InjectorImpl.this, implementation);
@@ -1018,8 +1010,7 @@
     Errors errors = new Errors();
     try {
       injectMembersOrThrow(errors, o);
-    }
-    catch (ErrorsException e) {
+    } catch (ErrorsException e) {
       errors.merge(e.getErrors());
     }
 
@@ -1076,7 +1067,7 @@
   }
 
   public <T> Provider<T> getProvider(final Key<T> key) {
-    Errors errors = new Errors();
+    Errors errors = new Errors(StackTraceElements.forType(key.getRawType()));
     try {
       Provider<T> result = getProviderOrThrow(key, errors);
       errors.throwIfNecessary();
diff --git a/src/com/google/inject/ProviderMethods.java b/src/com/google/inject/ProviderMethods.java
index 258f81d..dfed20a 100644
--- a/src/com/google/inject/ProviderMethods.java
+++ b/src/com/google/inject/ProviderMethods.java
@@ -69,13 +69,12 @@
     }
 
     <T> void bindProviderMethod(final Method method) {
-      Errors errors = new Errors()
-          .pushSource(StackTraceElements.forMember(method));
+      Errors errors = new Errors(StackTraceElements.forMember(method));
 
       method.setAccessible(true);
 
       Class<? extends Annotation> scopeAnnotation
-          = findScopeAnnotation(errors, method.getAnnotations());
+          = Scopes.findScopeAnnotation(errors, method.getAnnotations());
       Annotation bindingAnnotation
           = Keys.findBindingAnnotation(errors, method, method.getAnnotations());
 
@@ -98,8 +97,8 @@
 
           try {
             // We know this cast is safe becase T is the method's return type.
-            @SuppressWarnings({ "unchecked", "UnnecessaryLocalVariable" }) T result = (T) method
-                .invoke(providers, parameters);
+            @SuppressWarnings({ "unchecked", "UnnecessaryLocalVariable" })
+            T result = (T) method.invoke(providers, parameters);
             return result;
           }
           catch (IllegalAccessException e) {
@@ -141,25 +140,5 @@
 
       return parameterProviders;
     }
-
-    /**
-     * Returns the scoping annotation, or null if there isn't one.
-     */
-    Class<? extends Annotation> findScopeAnnotation(Errors errors, Annotation[] annotations) {
-      Class<? extends Annotation> found = null;
-
-      for (Annotation annotation : annotations) {
-        if (annotation.annotationType()
-            .isAnnotationPresent(ScopeAnnotation.class)) {
-          if (found != null) {
-            errors.duplicateScopeAnnotations(found, annotation.annotationType());
-          } else {
-            found = annotation.annotationType();
-          }
-        }
-      }
-
-      return found;
-    }
   }
 }
diff --git a/src/com/google/inject/ProxyFactory.java b/src/com/google/inject/ProxyFactory.java
index 07aca7c..48fc94f 100644
--- a/src/com/google/inject/ProxyFactory.java
+++ b/src/com/google/inject/ProxyFactory.java
@@ -23,6 +23,7 @@
 import com.google.inject.internal.GuiceFastClass;
 import com.google.inject.internal.GuiceNamingPolicy;
 import com.google.inject.internal.ReferenceCache;
+import com.google.inject.internal.StackTraceElements;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -61,7 +62,7 @@
 
   Map<Constructor<?>, Object> constructionProxies = new ReferenceCache<Constructor<?>, Object>() {
     protected Object create(Constructor<?> constructor) {
-      Errors errors = new Errors();
+      Errors errors = new Errors(StackTraceElements.forMember(constructor));
       try {
         ConstructionProxy<?> result = createConstructionProxy(errors, constructor);
         errors.throwIfNecessary();
diff --git a/src/com/google/inject/RequestStaticInjectionCommandProcessor.java b/src/com/google/inject/RequestStaticInjectionCommandProcessor.java
index a2ba1a3..132ff54 100644
--- a/src/com/google/inject/RequestStaticInjectionCommandProcessor.java
+++ b/src/com/google/inject/RequestStaticInjectionCommandProcessor.java
@@ -68,15 +68,11 @@
     }
 
     void validate(final InjectorImpl injector) {
-      errors.pushSource(source);
-      try {
-        injector.addSingleInjectorsForFields(
-            type.getDeclaredFields(), true, memberInjectors, errors);
-        injector.addSingleInjectorsForMethods(
-            type.getDeclaredMethods(), true, memberInjectors, errors);
-      } finally {
-        errors.popSource(source);
-      }
+      Errors errorsForMember = errors.withSource(source);
+      injector.addSingleInjectorsForFields(
+          type.getDeclaredFields(), true, memberInjectors, errorsForMember);
+      injector.addSingleInjectorsForMethods(
+          type.getDeclaredMethods(), true, memberInjectors, errorsForMember);
     }
 
     void injectMembers(InjectorImpl injector) {
diff --git a/src/com/google/inject/Scopes.java b/src/com/google/inject/Scopes.java
index f0bc525..52e36ec 100644
--- a/src/com/google/inject/Scopes.java
+++ b/src/com/google/inject/Scopes.java
@@ -89,11 +89,21 @@
   /**
    * Returns the scope annotation on {@code type}, or null if none is specified.
    */
-  static Class<? extends Annotation> getScopeAnnotation(
+  static Class<? extends Annotation> findScopeAnnotation(
       Errors errors, Class<?> implementation) {
+    return findScopeAnnotation(errors, implementation.getAnnotations());
+  }
+
+
+  /**
+   * Returns the scoping annotation, or null if there isn't one.
+   */
+  static Class<? extends Annotation> findScopeAnnotation(Errors errors, Annotation[] annotations) {
     Class<? extends Annotation> found = null;
-    for (Annotation annotation : implementation.getAnnotations()) {
-      if (isScopeAnnotation(annotation)) {
+
+    for (Annotation annotation : annotations) {
+      if (annotation.annotationType()
+          .isAnnotationPresent(ScopeAnnotation.class)) {
         if (found != null) {
           errors.duplicateScopeAnnotations(found, annotation.annotationType());
         } else {
diff --git a/src/com/google/inject/ScopesCommandProcessor.java b/src/com/google/inject/ScopesCommandProcessor.java
index 2f9a3a4..dc2199f 100644
--- a/src/com/google/inject/ScopesCommandProcessor.java
+++ b/src/com/google/inject/ScopesCommandProcessor.java
@@ -45,12 +45,12 @@
     Class<? extends Annotation> annotationType = command.getAnnotationType();
 
     if (!Scopes.isScopeAnnotation(annotationType)) {
-      errors.at(StackTraceElements.forType(annotationType)).missingScopeAnnotation();
+      errors.withSource(StackTraceElements.forType(annotationType)).missingScopeAnnotation();
       // Go ahead and bind anyway so we don't get collateral errors.
     }
 
     if (!Annotations.isRetainedAtRuntime(annotationType)) {
-      errors.at(StackTraceElements.forType(annotationType))
+      errors.withSource(StackTraceElements.forType(annotationType))
           .missingRuntimeRetention(command.getSource());
       // Go ahead and bind anyway so we don't get collateral errors.
     }
diff --git a/src/com/google/inject/commands/AddMessageCommand.java b/src/com/google/inject/commands/AddMessageCommand.java
new file mode 100644
index 0000000..7ed6145
--- /dev/null
+++ b/src/com/google/inject/commands/AddMessageCommand.java
@@ -0,0 +1,56 @@
+/**
+ * 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.commands;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.spi.InjectionPoint;
+import com.google.inject.spi.Message;
+
+/**
+ * Immutable snapshot of a request to add a string message.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public final class AddMessageCommand implements Command {
+  private final Message message;
+
+  AddMessageCommand(Message message) {
+    this.message = message;
+  }
+
+  AddMessageCommand(Object source, String message, Object[] arguments) {
+    this.message = new Message(source, String.format(message, arguments));
+  }
+
+  AddMessageCommand(Object source, Throwable throwable) {
+    this.message = new Message(source,
+        "An exception was caught and reported. Message: " + throwable.getMessage(), 
+        ImmutableList.<InjectionPoint>of(), throwable);
+  }
+
+  public Object getSource() {
+    return message.getSource();
+  }
+
+  public <T> T acceptVisitor(Visitor<T> visitor) {
+    return visitor.visitAddMessage(this);
+  }
+
+  public Message getMessage() {
+    return message;
+  }
+}
diff --git a/src/com/google/inject/commands/AddMessageErrorCommand.java b/src/com/google/inject/commands/AddMessageErrorCommand.java
deleted file mode 100644
index cd4a8cf..0000000
--- a/src/com/google/inject/commands/AddMessageErrorCommand.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * 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.commands;
-
-
-import java.util.Arrays;
-import static java.util.Collections.unmodifiableList;
-import java.util.List;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * Immutable snapshot of a request to add a string message.
- *
- * @author jessewilson@google.com (Jesse Wilson)
- */
-public final class AddMessageErrorCommand implements Command {
-  private final Object source;
-  private final String message;
-  private final List<Object> arguments;
-
-  AddMessageErrorCommand(Object source, String message, Object[] arguments) {
-    this.source = checkNotNull(source, "source");
-    this.message = checkNotNull(message, "message");
-    this.arguments = unmodifiableList(Arrays.asList(arguments.clone()));
-  }
-
-  public Object getSource() {
-    return source;
-  }
-
-  public <T> T acceptVisitor(Visitor<T> visitor) {
-    return visitor.visitAddMessageError(this);
-  }
-
-  public String getMessage() {
-    return message;
-  }
-
-  public List<Object> getArguments() {
-    return arguments;
-  }
-}
diff --git a/src/com/google/inject/commands/AddThrowableErrorCommand.java b/src/com/google/inject/commands/AddThrowableErrorCommand.java
deleted file mode 100644
index 456af93..0000000
--- a/src/com/google/inject/commands/AddThrowableErrorCommand.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * 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.commands;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * Immutable snapshot of a request to add a throwable message.
- *
- * @author jessewilson@google.com (Jesse Wilson)
- */
-public final class AddThrowableErrorCommand implements Command {
-  private final Object source;
-  private final Throwable throwable;
-
-  AddThrowableErrorCommand(Object source, Throwable throwable) {
-    this.source = checkNotNull(source, "source");
-    this.throwable = checkNotNull(throwable, "throwable");
-  }
-
-  public Object getSource() {
-    return source;
-  }
-
-  public <T> T acceptVisitor(Visitor<T> visitor) {
-    return visitor.visitAddError(this);
-  }
-
-  public Throwable getThrowable() {
-    return throwable;
-  }
-}
diff --git a/src/com/google/inject/commands/Command.java b/src/com/google/inject/commands/Command.java
index 862a77f..e87f67e 100644
--- a/src/com/google/inject/commands/Command.java
+++ b/src/com/google/inject/commands/Command.java
@@ -29,8 +29,7 @@
    * Visit commands.
    */
   public interface Visitor<V> {
-    V visitAddMessageError(AddMessageErrorCommand command);
-    V visitAddError(AddThrowableErrorCommand command);
+    V visitAddMessage(AddMessageCommand command);
     V visitBindInterceptor(BindInterceptorCommand command);
     V visitBindScope(BindScopeCommand command);
     V visitRequestStaticInjection(RequestStaticInjectionCommand command);
diff --git a/src/com/google/inject/commands/CommandRecorder.java b/src/com/google/inject/commands/CommandRecorder.java
index f3bb7a2..42bb586 100644
--- a/src/com/google/inject/commands/CommandRecorder.java
+++ b/src/com/google/inject/commands/CommandRecorder.java
@@ -138,15 +138,15 @@
     }
 
     public void addError(String message, Object... arguments) {
-      commands.add(new AddMessageErrorCommand(getSource(), message, arguments));
+      commands.add(new AddMessageCommand(getSource(), message, arguments));
     }
 
     public void addError(Throwable t) {
-      commands.add(new AddThrowableErrorCommand(getSource(), t));
+      commands.add(new AddMessageCommand(getSource(), t));
     }
 
     public void addError(Message message) {
-      throw new UnsupportedOperationException("TODO");
+      commands.add(new AddMessageCommand(message));
     }
 
     public <T> BindCommand<T>.BindingBuilder bind(Key<T> key) {
diff --git a/src/com/google/inject/commands/CommandReplayer.java b/src/com/google/inject/commands/CommandReplayer.java
index b40f793..a666737 100644
--- a/src/com/google/inject/commands/CommandReplayer.java
+++ b/src/com/google/inject/commands/CommandReplayer.java
@@ -54,16 +54,11 @@
     checkNotNull(commands, "commands");
 
     Command.Visitor<Void> visitor = new Command.Visitor<Void>() {
-      public Void visitAddMessageError(AddMessageErrorCommand command) {
+      public Void visitAddMessage(AddMessageCommand command) {
         replayAddMessageError(binder, command);
         return null;
       }
 
-      public Void visitAddError(AddThrowableErrorCommand command) {
-        replayAddError(binder, command);
-        return null;
-      }
-
       public Void visitBindInterceptor(BindInterceptorCommand command) {
         replayBindInterceptor(binder, command);
         return null;
@@ -105,13 +100,8 @@
     }
   }
 
-  public void replayAddMessageError(final Binder binder, final AddMessageErrorCommand command) {
-    binder.withSource(command.getSource())
-        .addError(command.getMessage(), command.getArguments().toArray());
-  }
-
-  public void replayAddError(final Binder binder, final AddThrowableErrorCommand command) {
-    binder.withSource(command.getSource()).addError(command.getThrowable());
+  public void replayAddMessageError(final Binder binder, final AddMessageCommand command) {
+    binder.withSource(command.getSource()).addError(command.getMessage());
   }
 
   public void replayBindInterceptor(final Binder binder, final BindInterceptorCommand command) {
diff --git a/src/com/google/inject/commands/DefaultCommandVisitor.java b/src/com/google/inject/commands/DefaultCommandVisitor.java
index 08f433b..cf98592 100644
--- a/src/com/google/inject/commands/DefaultCommandVisitor.java
+++ b/src/com/google/inject/commands/DefaultCommandVisitor.java
@@ -34,11 +34,7 @@
     return null;
   }
 
-  public V visitAddError(AddThrowableErrorCommand command) {
-    return visitCommand(command);
-  }
-
-  public V visitAddMessageError(AddMessageErrorCommand command) {
+  public V visitAddMessage(AddMessageCommand command) {
     return visitCommand(command);
   }
 
diff --git a/src/com/google/inject/internal/Errors.java b/src/com/google/inject/internal/Errors.java
index 4da99f5..b80df00 100644
--- a/src/com/google/inject/internal/Errors.java
+++ b/src/com/google/inject/internal/Errors.java
@@ -21,7 +21,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.inject.CreationException;
-import com.google.inject.Guice;
 import com.google.inject.Key;
 import com.google.inject.Provider;
 import com.google.inject.Scope;
@@ -39,8 +38,6 @@
 import java.util.Comparator;
 import java.util.Formatter;
 import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
 
 /**
  * A collection of error messages. If this type is passed as a method parameter, the method is
@@ -50,15 +47,42 @@
  */
 public final class Errors implements Serializable {
 
-  private static final Logger logger = Logger.getLogger(Guice.class.getName());
+  // TODO: Provide a policy on what the source line should be for a given member.
+  //       Should we prefer the line where the binding was made?
+  //       Should we prefer the member?
+  // For example, if we bind a class with two scope annotations, is that a problem
+  // with the binding, or a problem with the bound class? The catch being that if it's
+  // a problem with the binding, then we report a different source line if the class
+  // is retrieved via a JIT binding.
+  //
+  // What about a missing implementation? Is that at the caller?
+  // What about injection points?
 
-  private Object sourceForNextError = null;
-  private final List<Object> sources = Lists.newArrayList();
+  /** the stacktrace or member that will be the reference location for new errors */
+  private final Object source;
 
   /** false indicates that new errors should not be added */
   private boolean isMutable = true;
-  private final List<Message> errors = Lists.newArrayList();
-  private final List<InjectionPoint> injectionPoints = Lists.newArrayList();
+  private final List<Message> errors;
+  private final List<InjectionPoint> injectionPoints;
+
+  public Errors() {
+    this(SourceProvider.UNKNOWN_SOURCE);
+  }
+
+  public Errors(Object source) {
+    this.source = source;
+    isMutable = true;
+    errors = Lists.newArrayList();
+    injectionPoints = Lists.newArrayList();
+  }
+
+  public Errors(Errors parent, Object source) {
+    this.source = source;
+    isMutable = parent.isMutable;
+    errors = parent.errors;
+    injectionPoints = Lists.newArrayList(parent.injectionPoints);
+  }
 
   public Errors userReportedError(String messageFormat, List<Object> arguments) {
     return addMessage(messageFormat, arguments);
@@ -74,18 +98,11 @@
   }
 
   /**
-   * Specifies the source for every error added until the matching call to
-   * {@link #popSource(Object)}.
+   * Returns a new instance that uses {@code source} as a reference point for
+   * newly added errors.
    */
-  public Errors pushSource(Object source) {
-    sources.add(source);
-    return this;
-  }
-
-  public Errors popSource(Object source) {
-    Object popped = sources.remove(sources.size() - 1);
-    checkArgument(popped == source);
-    return this;
+  public Errors withSource(Object source) {
+    return new Errors(this, source);
   }
 
   /**
@@ -164,15 +181,6 @@
     return addMessage("@ProvidedBy points to the same class it annotates.");
   }
 
-  public Errors exceptionReportedByModuleSeeLogs(Throwable throwable) {
-    String rootMessage = getRootMessage(throwable);
-
-    logger.log(Level.INFO, format("An exception was caught and reported. Message: %s", rootMessage),
-        throwable);
-    return addMessage(throwable,
-        "An exception was caught and reported. See log for details. Message: %s", rootMessage);
-  }
-
   public Errors missingBindingAnnotation(Object source) {
     return addMessage("Please annotate with @BindingAnnotation.%n"
         + " Bound at %s.", source);
@@ -302,14 +310,6 @@
         expectedType);
   }
 
-  /**
-   * Convenience method to set the source for a single error.
-   */
-  public Errors at(Object source) {
-    sourceForNextError = source;
-    return this;
-  }
-
   public Errors makeImmutable() {
     isMutable = false;
     return this;
@@ -327,7 +327,7 @@
   public Errors merge(Errors moreErrors) {
     checkState(isMutable);
 
-    if (moreErrors != this) {
+    if (moreErrors.errors != this.errors) {
       for (Message message : moreErrors.errors) {
         List<InjectionPoint> injectionPoints = Lists.newArrayList();
         injectionPoints.addAll(this.injectionPoints);
@@ -361,23 +361,17 @@
   }
 
   private Errors addMessage(Throwable cause, String messageFormat, Object... arguments) {
+    String message = format(messageFormat, arguments);
+    addMessage(new Message(source, message, ImmutableList.copyOf(injectionPoints), cause));
+    return this;
+  }
+
+  public Errors addMessage(Message message) {
     if (!isMutable) {
       throw new AssertionError();
     }
 
-    String message = format(messageFormat, arguments);
-
-    Object source;
-    if (sourceForNextError != null) {
-      source = sourceForNextError;
-      sourceForNextError = null;
-    } else if (!sources.isEmpty()) {
-      source = sources.get(sources.size() - 1);
-    } else {
-      source = SourceProvider.UNKNOWN_SOURCE;
-    }
-
-    errors.add(new Message(source, message, ImmutableList.copyOf(injectionPoints), cause));
+    errors.add(message);
     return this;
   }
 
@@ -442,7 +436,7 @@
     return fmt.format("%s error[s]", errorMessages.size()).toString();
   }
 
-  static abstract class Converter<T> {
+  private static abstract class Converter<T> {
 
     final Class<T> type;
 
@@ -461,12 +455,7 @@
     abstract String toString(T t);
   }
 
-  public static String getRootMessage(Throwable t) {
-    Throwable cause = t.getCause();
-    return cause == null ? t.toString() : getRootMessage(cause);
-  }
-
-  static final Collection<Converter<?>> converters = ImmutableList.of(
+  private static final Collection<Converter<?>> converters = ImmutableList.of(
       new Converter<MatcherAndConverter>(MatcherAndConverter.class) {
         public String toString(MatcherAndConverter m) {
           return m.toString();
diff --git a/src/com/google/inject/internal/LineNumbers.java b/src/com/google/inject/internal/LineNumbers.java
index 82aaf7b..4e46c93 100644
--- a/src/com/google/inject/internal/LineNumbers.java
+++ b/src/com/google/inject/internal/LineNumbers.java
@@ -52,9 +52,12 @@
    */
   public LineNumbers(Class type) throws IOException {
     this.type = type;
-    InputStream in = type.getResourceAsStream("/" + type.getName().replace('.', '/') + ".class");
-    checkArgument(in != null, "Cannot find bytecode for %s", type);
-    new ClassReader(in).accept(new LineNumberReader(), ClassReader.SKIP_FRAMES);
+
+    if (!type.isArray()) {
+      InputStream in = type.getResourceAsStream("/" + type.getName().replace('.', '/') + ".class");
+      checkArgument(in != null, "Cannot find bytecode for %s", type);
+      new ClassReader(in).accept(new LineNumberReader(), ClassReader.SKIP_FRAMES);
+    }
   }
 
   /**