Creating a canonical form for Keys with arrays in them. 
Fixing Serialization for Key, CreationException and TypeLiteral.
More SerializationTests.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@492 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/CreationException.java b/src/com/google/inject/CreationException.java
index 819c7cf..ba78de4 100644
--- a/src/com/google/inject/CreationException.java
+++ b/src/com/google/inject/CreationException.java
@@ -17,12 +17,8 @@
 package com.google.inject;
 
 import com.google.inject.spi.Message;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Formatter;
-import java.util.List;
+
+import java.util.*;
 
 /**
  * Thrown when errors occur while creating a {@link Injector}. Includes a list
@@ -45,7 +41,7 @@
     this.errorMessages = new ArrayList<Message>(errorMessages);
     Collections.sort(this.errorMessages, new Comparator<Message>() {
       public int compare(Message a, Message b) {
-        return a.getSourceString().compareTo(b.getSourceString());
+        return a.getSource().compareTo(b.getSource());
       }
     });
   }
@@ -59,7 +55,7 @@
     Formatter fmt = new Formatter().format("Guice configuration errors:%n%n");
     int index = 1;
     for (Message errorMessage : errorMessages) {
-      fmt.format("%s) Error at %s:%n", index++, errorMessage.getSourceString())
+      fmt.format("%s) Error at %s:%n", index++, errorMessage.getSource())
          .format(" %s%n%n", errorMessage.getMessage());
     }
     return fmt.format("%s error[s]", errorMessages.size()).toString();
diff --git a/src/com/google/inject/Key.java b/src/com/google/inject/Key.java
index 3c6ebfc..b96822a 100644
--- a/src/com/google/inject/Key.java
+++ b/src/com/google/inject/Key.java
@@ -43,12 +43,12 @@
  *
  * @author crazybob@google.com (Bob Lee)
  */
-public abstract class Key<T> implements Serializable {
+public class Key<T> implements Serializable {
 
-  final AnnotationStrategy annotationStrategy;
+  private final AnnotationStrategy annotationStrategy;
 
-  final TypeLiteral<T> typeLiteral;
-  final int hashCode;
+  private final TypeLiteral<T> typeLiteral;
+  private final int hashCode;
 
   /**
    * Constructs a new key. Derives the type from this class's type parameter.
@@ -104,7 +104,7 @@
    */
   @SuppressWarnings("unchecked")
   protected Key() {
-    this.annotationStrategy = NULL_STRATEGY;
+    this.annotationStrategy = NullAnnotationStrategy.INSTANCE;
     this.typeLiteral
         = (TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass());
     this.hashCode = computeHashCode();
@@ -135,21 +135,21 @@
   /**
    * Gets the key type.
    */
-  public TypeLiteral<T> getTypeLiteral() {
+  public final TypeLiteral<T> getTypeLiteral() {
     return typeLiteral;
   }
 
   /**
    * Gets the annotation type.
    */
-  public Class<? extends Annotation> getAnnotationType() {
+  public final Class<? extends Annotation> getAnnotationType() {
     return annotationStrategy.getAnnotationType();
   }
 
   /**
    * Gets the annotation.
    */
-  public Annotation getAnnotation() {
+  public final Annotation getAnnotation() {
     return annotationStrategy.getAnnotation();
   }
 
@@ -167,10 +167,6 @@
     return annotationStrategy.getAnnotationType().toString();
   }
 
-  public int hashCode() {
-    return this.hashCode;
-  }
-
   Class<? super T> getRawType() {
     return typeLiteral.getRawType();
   }
@@ -182,7 +178,7 @@
     return ofType(typeLiteral.providerType());
   }
 
-  public boolean equals(Object o) {
+  @Override public final boolean equals(Object o) {
     if (o == this) {
       return true;
     }
@@ -194,7 +190,11 @@
         && typeLiteral.equals(other.typeLiteral);
   }
 
-  public String toString() {
+  @Override public final int hashCode() {
+    return this.hashCode;
+  }
+
+  @Override public final String toString() {
     return new ToStringBuilder(Key.class)
         .add("type", typeLiteral)
         .add("annotation", annotationStrategy)
@@ -206,14 +206,14 @@
    */
   static <T> Key<T> get(Class<T> type,
       AnnotationStrategy annotationStrategy) {
-    return new SimpleKey<T>(type, annotationStrategy);
+    return new Key<T>(type, annotationStrategy);
   }
 
   /**
    * Gets a key for an injection type.
    */
   public static <T> Key<T> get(Class<T> type) {
-    return new SimpleKey<T>(type, NULL_STRATEGY);
+    return new Key<T>(type, NullAnnotationStrategy.INSTANCE);
   }
 
   /**
@@ -221,21 +221,21 @@
    */
   public static <T> Key<T> get(Class<T> type,
       Class<? extends Annotation> annotationType) {
-    return new SimpleKey<T>(type, strategyFor(annotationType));
+    return new Key<T>(type, strategyFor(annotationType));
   }
 
   /**
    * Gets a key for an injection type and an annotation.
    */
   public static <T> Key<T> get(Class<T> type, Annotation annotation) {
-    return new SimpleKey<T>(type, strategyFor(annotation));
+    return new Key<T>(type, strategyFor(annotation));
   }
 
   /**
    * Gets a key for an injection type.
    */
   public static Key<?> get(Type type) {
-    return new SimpleKey<Object>(type, NULL_STRATEGY);
+    return new Key<Object>(type, NullAnnotationStrategy.INSTANCE);
   }
 
   /**
@@ -243,21 +243,21 @@
    */
   public static Key<?> get(Type type,
       Class<? extends Annotation> annotationType) {
-    return new SimpleKey<Object>(type, strategyFor(annotationType));
+    return new Key<Object>(type, strategyFor(annotationType));
   }
 
   /**
    * Gets a key for an injection type and an annotation.
    */
   public static Key<?> get(Type type, Annotation annotation) {
-    return new SimpleKey<Object>(type, strategyFor(annotation));
+    return new Key<Object>(type, strategyFor(annotation));
   }
 
   /**
    * Gets a key for an injection type.
    */
   public static <T> Key<T> get(TypeLiteral<T> typeLiteral) {
-    return new SimpleKey<T>(typeLiteral, NULL_STRATEGY);
+    return new Key<T>(typeLiteral, NullAnnotationStrategy.INSTANCE);
   }
 
   /**
@@ -265,7 +265,7 @@
    */
   public static <T> Key<T> get(TypeLiteral<T> typeLiteral,
       Class<? extends Annotation> annotationType) {
-    return new SimpleKey<T>(typeLiteral, strategyFor(annotationType));
+    return new Key<T>(typeLiteral, strategyFor(annotationType));
   }
 
   /**
@@ -273,7 +273,7 @@
    */
   public static <T> Key<T> get(TypeLiteral<T> typeLiteral,
       Annotation annotation) {
-    return new SimpleKey<T>(typeLiteral, strategyFor(annotation));
+    return new Key<T>(typeLiteral, strategyFor(annotation));
   }
 
   /**
@@ -281,7 +281,7 @@
    * key.
    */
   <T> Key<T> ofType(Class<T> type) {
-    return new SimpleKey<T>(type, annotationStrategy);
+    return new Key<T>(type, annotationStrategy);
   }
 
   /**
@@ -289,7 +289,7 @@
    * key.
    */
   Key<?> ofType(Type type) {
-    return new SimpleKey<Object>(type, annotationStrategy);
+    return new Key<Object>(type, annotationStrategy);
   }
 
   /**
@@ -297,12 +297,11 @@
    * key.
    */
   <T> Key<T> ofType(TypeLiteral<T> type) {
-    return new SimpleKey<T>(type, annotationStrategy);
+    return new Key<T>(type, annotationStrategy);
   }
 
   /**
    * Returns true if this key has annotation attributes.
-   * @return
    */
   boolean hasAttributes() {
     return annotationStrategy.hasAttributes();
@@ -313,19 +312,7 @@
    * annotation type.
    */
   Key<T> withoutAttributes() {
-    return new SimpleKey<T>(typeLiteral, annotationStrategy.withoutAttributes());
-  }
-
-  private static class SimpleKey<T> extends Key<T> {
-
-    private SimpleKey(Type type, AnnotationStrategy annotationStrategy) {
-      super(type, annotationStrategy);
-    }
-
-    private SimpleKey(TypeLiteral<T> typeLiteral,
-        AnnotationStrategy annotationStrategy) {
-      super(typeLiteral, annotationStrategy);
-    }
+    return new Key<T>(typeLiteral, annotationStrategy.withoutAttributes());
   }
 
   interface AnnotationStrategy extends Serializable {
@@ -336,42 +323,6 @@
     AnnotationStrategy withoutAttributes();
   }
 
-  static final AnnotationStrategy NULL_STRATEGY = new AnnotationStrategy() {
-
-    public boolean hasAttributes() {
-      return false;
-    }
-
-    public AnnotationStrategy withoutAttributes() {
-      throw new UnsupportedOperationException("Key already has no attributes.");
-    }
-
-    public Annotation getAnnotation() {
-      return null;
-    }
-
-    public Class<? extends Annotation> getAnnotationType() {
-      return null;
-    }
-
-    public boolean equals(Object o) {
-      return o == NULL_STRATEGY;
-    }
-
-    public int hashCode() {
-      return 0;
-    }
-
-    public String toString() {
-      return "[none]";
-    }
-
-    Object readResolve() {
-      return NULL_STRATEGY;
-    }
-    private static final long serialVersionUID = 0;
-  };
-
   /**
    * Returns {@code true} if the given annotation type has no attributes.
    */
@@ -424,6 +375,30 @@
     }
   }
 
+  static enum NullAnnotationStrategy implements AnnotationStrategy {
+    INSTANCE;
+
+    public boolean hasAttributes() {
+      return false;
+    }
+
+    public AnnotationStrategy withoutAttributes() {
+      throw new UnsupportedOperationException("Key already has no attributes.");
+    }
+
+    public Annotation getAnnotation() {
+      return null;
+    }
+
+    public Class<? extends Annotation> getAnnotationType() {
+      return null;
+    }
+
+    @Override public String toString() {
+      return "[none]";
+    }
+  }
+
   // this class not test-covered
   static class AnnotationInstanceStrategy implements AnnotationStrategy {
 
@@ -449,7 +424,7 @@
       return annotation.annotationType();
     }
 
-    public boolean equals(Object o) {
+    @Override public boolean equals(Object o) {
       if (!(o instanceof AnnotationInstanceStrategy)) {
         return false;
       }
@@ -458,11 +433,11 @@
       return annotation.equals(other.annotation);
     }
 
-    public int hashCode() {
+    @Override public int hashCode() {
       return annotation.hashCode();
     }
 
-    public String toString() {
+    @Override public String toString() {
       return annotation.toString();
     }
 
@@ -498,7 +473,7 @@
       return annotationType;
     }
 
-    public boolean equals(Object o) {
+    @Override public boolean equals(Object o) {
       if (!(o instanceof AnnotationTypeStrategy)) {
         return false;
       }
@@ -507,11 +482,11 @@
       return annotationType.equals(other.annotationType);
     }
 
-    public int hashCode() {
+    @Override public int hashCode() {
       return annotationType.hashCode();
     }
 
-    public String toString() {
+    @Override public String toString() {
       return "@" + annotationType.getName();
     }
 
@@ -527,5 +502,16 @@
     return annotationType.isAnnotationPresent(BindingAnnotation.class);
   }
 
+  /**
+   * Returns the canonical form of this key for serialization. The returned
+   * instance is always a {@code Key}, never a subclass. This prevents problems
+   * caused by serializing anonymous types.
+   */
+  protected final Object writeReplace() {
+    return getClass() == Key.class
+        ? this
+        : new Key<T>(typeLiteral, annotationStrategy);
+  }
+
   private static final long serialVersionUID = 0;
 }
diff --git a/src/com/google/inject/ProviderMethods.java b/src/com/google/inject/ProviderMethods.java
index 20acdef..a6d021b 100644
--- a/src/com/google/inject/ProviderMethods.java
+++ b/src/com/google/inject/ProviderMethods.java
@@ -16,10 +16,11 @@
 
 package com.google.inject;
 
-import com.google.inject.internal.StackTraceElements;
 import com.google.inject.internal.ErrorMessages;
+import com.google.inject.internal.StackTraceElements;
 import com.google.inject.spi.SourceProvider;
 import com.google.inject.spi.SourceProviders;
+
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -28,7 +29,7 @@
 import java.util.List;
 
 /**
- * Creates bindings to methods annotated with
+ * Creates bindings to methods annotated with {@literal @}
  * {@link com.google.inject.Provides}. Use the scope and binding annotations
  * on the provider method to configure the binding.
  */
diff --git a/src/com/google/inject/TypeLiteral.java b/src/com/google/inject/TypeLiteral.java
index 2d80a31..932950d 100644
--- a/src/com/google/inject/TypeLiteral.java
+++ b/src/com/google/inject/TypeLiteral.java
@@ -18,6 +18,7 @@
 
 import static com.google.inject.internal.Objects.nonNull;
 import com.google.inject.internal.Types;
+import static com.google.inject.internal.Types.canonicalize;
 
 import java.io.Serializable;
 import java.lang.reflect.ParameterizedType;
@@ -66,20 +67,22 @@
    */
   @SuppressWarnings("unchecked")
   TypeLiteral(Type type) {
-    this.rawType = (Class<? super T>) Types.getRawType(nonNull(type, "type"));
-    this.type = type;
-    this.hashCode = Types.hashCode(type);
+    this.type = canonicalize(nonNull(type, "type"));
+    this.rawType = (Class<? super T>) Types.getRawType(this.type);
+    this.hashCode = Types.hashCode(this.type);
   }
 
   /**
-   * Gets type from super class's type parameter.
+   * Returns the type from super class's type parameter in {@link
+   * Types#canonicalize(Type) canonical form}.
    */
   static Type getSuperclassTypeParameter(Class<?> subclass) {
     Type superclass = subclass.getGenericSuperclass();
     if (superclass instanceof Class) {
       throw new RuntimeException("Missing type parameter.");
     }
-    return ((ParameterizedType) superclass).getActualTypeArguments()[0];
+    ParameterizedType parameterized = (ParameterizedType) superclass;
+    return canonicalize(parameterized.getActualTypeArguments()[0]);
   }
 
   /**
@@ -141,8 +144,15 @@
     return new TypeLiteral<T>(type);
   }
 
+  /**
+   * Returns the canonical form of this type literal for serialization. The
+   * returned instance is always a {@code TypeLiteral}, never a subclass. This
+   * prevents problems caused by serializing anonymous types.
+   */
   protected final Object writeReplace() {
-    return get(Types.makeSerializable(type));
+    return getClass() == TypeLiteral.class
+        ? this
+        : get(type);
   }
 
   private static final long serialVersionUID = 0;
diff --git a/src/com/google/inject/internal/Types.java b/src/com/google/inject/internal/Types.java
index a18a563..1b4380c 100644
--- a/src/com/google/inject/internal/Types.java
+++ b/src/com/google/inject/internal/Types.java
@@ -30,11 +30,11 @@
   private Types() {}
 
   /**
-   * Returns an equal Type that implements {@code Serializable}. Unfortunately,
-   * the Sun JDK 1.5 Type classes (among others) are not serializable, so we
-   * replace them with our own serializable implementations here.
+   * Returns a type that is functionally equal but not necessarily equal
+   * according to {@link Object#equals(Object) Object.equals()}. The returned
+   * type is {@link Serializable}.
    */
-  public static Type makeSerializable(Type type) {
+  public static Type canonicalize(Type type) {
     if (type instanceof ParameterizedTypeImpl
         || type instanceof GenericArrayTypeImpl) {
       return type;
@@ -48,6 +48,10 @@
       GenericArrayType g = (GenericArrayType) type;
       return newGenericArrayType(g.getGenericComponentType());
 
+    } else if (type instanceof Class<?> && ((Class<?>) type).isArray()) {
+      Class<?> c = (Class<?>) type;
+      return newGenericArrayType(c.getComponentType());
+
     } else {
       // type is either serializable as-is or unsupported
       return type;
@@ -198,11 +202,11 @@
     private final Type[] typeArguments;
 
     private ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
-      this.ownerType = ownerType == null ? null : makeSerializable(ownerType);
-      this.rawType = makeSerializable(rawType);
+      this.ownerType = ownerType == null ? null : canonicalize(ownerType);
+      this.rawType = canonicalize(rawType);
       this.typeArguments = typeArguments.clone();
       for (int t = 0; t < this.typeArguments.length; t++) {
-        this.typeArguments[t] = makeSerializable(this.typeArguments[t]);
+        this.typeArguments[t] = canonicalize(this.typeArguments[t]);
       }
     }
 
@@ -238,7 +242,7 @@
     private final Type componentType;
 
     private GenericArrayTypeImpl(Type componentType) {
-      this.componentType = componentType;
+      this.componentType = canonicalize(componentType);
     }
 
     public Type getGenericComponentType() {
diff --git a/src/com/google/inject/matcher/Matchers.java b/src/com/google/inject/matcher/Matchers.java
index 8ca5658..a9f4ff4 100644
--- a/src/com/google/inject/matcher/Matchers.java
+++ b/src/com/google/inject/matcher/Matchers.java
@@ -300,7 +300,7 @@
     }
 
     @Override public String toString() {
-      return "package(" + targetPackage.getName() + ")";
+      return "inPackage(" + targetPackage.getName() + ")";
     }
 
     public Object readResolve() {
diff --git a/src/com/google/inject/name/Names.java b/src/com/google/inject/name/Names.java
index e0059f9..758538e 100644
--- a/src/com/google/inject/name/Names.java
+++ b/src/com/google/inject/name/Names.java
@@ -18,8 +18,8 @@
 
 import com.google.inject.Binder;
 import com.google.inject.Key;
-import com.google.inject.spi.SourceProvider;
 import com.google.inject.spi.SourceProviders;
+
 import java.util.Map;
 import java.util.Properties;
 
@@ -49,7 +49,7 @@
   public static void bindProperties(final Binder binder,
       final Map<String, String> properties) {
     SourceProviders.withDefault(
-        new SimpleSourceProvider(SourceProviders.defaultSource()),
+        SourceProviders.defaultSource(),
         new Runnable() {
           public void run() {
             for (Map.Entry<String, String> entry : properties.entrySet()) {
@@ -67,7 +67,7 @@
   public static void bindProperties(final Binder binder,
       final Properties properties) {
     SourceProviders.withDefault(
-        new SimpleSourceProvider(SourceProviders.defaultSource()),
+        SourceProviders.defaultSource(),
         new Runnable() {
           public void run() {
             for (Map.Entry<Object, Object> entry : properties.entrySet()) {
@@ -78,17 +78,4 @@
           }
         });
   }
-
-  static class SimpleSourceProvider implements SourceProvider {
-
-    final Object source;
-
-    SimpleSourceProvider(Object source) {
-      this.source = source;
-    }
-
-    public Object source() {
-      return source;
-    }
-  }
 }
diff --git a/src/com/google/inject/spi/Message.java b/src/com/google/inject/spi/Message.java
index 35cd795..1bf78f5 100644
--- a/src/com/google/inject/spi/Message.java
+++ b/src/com/google/inject/spi/Message.java
@@ -18,19 +18,21 @@
 
 import static com.google.inject.internal.Objects.nonNull;
 
+import java.io.Serializable;
+
 /**
  * A message. Contains a source pointing to the code which resulted
  * in this message and a text message.
  *
  * @author crazybob@google.com (Bob Lee)
  */
-public class Message {
+public final class Message implements Serializable {
 
-  final Object source;
-  final String message;
+  private final String source;
+  private final String message;
 
   public Message(Object source, String message) {
-    this.source = nonNull(source, "source");
+    this.source = nonNull(source, "source").toString();
     this.message = nonNull(message, "message");
   }
 
@@ -39,22 +41,10 @@
   }
 
   /**
-   * Gets the source of the configuration which resulted in this error message.
-   */
-  public Object getSource() {
-    return source;
-  }
-
-  String sourceString = null;
-
-  /**
    * Returns a string representation of the source object. 
    */
-  public String getSourceString() {
-    if (sourceString == null) {
-      sourceString = source.toString();
-    }
-    return sourceString;
+  public String getSource() {
+    return source;
   }
 
   /**
@@ -65,7 +55,7 @@
   }
 
   public String toString() {
-    return getSourceString() + " " + message;
+    return source + " " + message;
   }
 
   public int hashCode() {
diff --git a/test/com/google/inject/Asserts.java b/test/com/google/inject/Asserts.java
index b083d8b..507b9bc 100644
--- a/test/com/google/inject/Asserts.java
+++ b/test/com/google/inject/Asserts.java
@@ -76,4 +76,12 @@
       throw new RuntimeException(e);
     }
   }
+
+  public static void assertNotSerializable(Object object) throws IOException {
+    try {
+      reserialize(object);
+      Assert.fail();
+    } catch (NotSerializableException expected) {
+    }
+  }
 }
diff --git a/test/com/google/inject/BinderTest.java b/test/com/google/inject/BinderTest.java
index 7086cfa..bd62347 100644
--- a/test/com/google/inject/BinderTest.java
+++ b/test/com/google/inject/BinderTest.java
@@ -17,9 +17,11 @@
 package com.google.inject;
 
 import static com.google.inject.Asserts.assertContains;
+import static com.google.inject.Asserts.assertNotSerializable;
 import com.google.inject.name.Names;
 import junit.framework.TestCase;
 
+import java.io.IOException;
 import java.util.List;
 
 /**
@@ -40,7 +42,7 @@
       }
     });
 
-    assertTrue(fooProvider.get() instanceof Foo);
+    assertNotNull(fooProvider.get());
   }
 
   static class Foo {}
@@ -94,6 +96,67 @@
     }
   }
 
+  public void testNothingIsSerializableInBinderApi() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        @Override public void configure() {
+          try {
+            assertNotSerializable(binder());
+            assertNotSerializable(getProvider(Integer.class));
+            assertNotSerializable(getProvider(Key.get(new TypeLiteral<List<String>>() {})));
+            assertNotSerializable(bind(Integer.class));
+            assertNotSerializable(bind(Integer.class).annotatedWith(Names.named("a")));
+            assertNotSerializable(bindConstant());
+            assertNotSerializable(bindConstant().annotatedWith(Names.named("b")));
+          } catch (IOException e) {
+            fail(e.getMessage());
+          }
+        }
+      });
+      fail();
+    } catch (CreationException ignored) {
+    }
+  }
+
+  /**
+   * Although {@code String[].class} isn't equal to {@code new
+   * GenericArrayTypeImpl(String.class)}, Guice should treat these two types
+   * interchangeably.
+   */
+  public void testArrayTypeCanonicalization() {
+    final String[] strings = new String[] { "A" };
+    final Integer[] integers = new Integer[] { 1 };
+    
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        bind(String[].class).toInstance(strings);
+        bind(new TypeLiteral<Integer[]>() {}).toInstance(integers);
+      }
+    });
+
+    assertSame(integers, injector.getInstance(Key.get(new TypeLiteral<Integer[]>() {})));
+    assertSame(integers, injector.getInstance(new Key<Integer[]>() {}));
+    assertSame(integers, injector.getInstance(Integer[].class));
+    assertSame(strings, injector.getInstance(Key.get(new TypeLiteral<String[]>() {})));
+    assertSame(strings, injector.getInstance(new Key<String[]>() {}));
+    assertSame(strings, injector.getInstance(String[].class));
+
+    try {
+      Guice.createInjector(new AbstractModule() {
+        protected void configure() {
+          bind(String[].class).toInstance(strings);
+          bind(new TypeLiteral<String[]>() {}).toInstance(strings);
+        }
+      });
+      fail();
+    } catch (CreationException expected) {
+      Asserts.assertContains(expected.getMessage(),
+          "A binding to java.lang.String[] was already configured");
+      Asserts.assertContains(expected.getMessage(),
+          "1 error[s]");
+    }
+  }
+
 //  public void testBindInterfaceWithoutImplementation() {
 //    Guice.createInjector(new AbstractModule() {
 //      protected void configure() {
diff --git a/test/com/google/inject/InjectorTest.java b/test/com/google/inject/InjectorTest.java
index 40a3dc7..8edba4a 100644
--- a/test/com/google/inject/InjectorTest.java
+++ b/test/com/google/inject/InjectorTest.java
@@ -16,9 +16,12 @@
 
 package com.google.inject;
 
+import static com.google.inject.Asserts.assertNotSerializable;
+import junit.framework.TestCase;
+
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import junit.framework.TestCase;
 
 /**
  * @author crazybob@google.com (Bob Lee)
@@ -108,6 +111,16 @@
     assertEquals(5, (int) iw.i);
   }
 
+  public void testInjectorApiIsNotSerializable() throws IOException {
+    Injector injector = Guice.createInjector();
+    assertNotSerializable(injector);
+    assertNotSerializable(injector.getProvider(String.class));
+    assertNotSerializable(injector.getBinding(String.class));
+    for (Binding<?> binding : injector.getBindings().values()) {
+      assertNotSerializable(binding);
+    }
+  }
+
   static class IntegerWrapper {
     @Inject @I Integer i;
   }
diff --git a/test/com/google/inject/KeyTest.java b/test/com/google/inject/KeyTest.java
index 008a9d0..76e0e14 100644
--- a/test/com/google/inject/KeyTest.java
+++ b/test/com/google/inject/KeyTest.java
@@ -80,6 +80,7 @@
     assertEqualWhenReserialized(Key.get(B[].class));
     assertEqualWhenReserialized(Key.get(new TypeLiteral<Map<List<B>, B>>() {}));
     assertEqualWhenReserialized(Key.get(new TypeLiteral<List<B[]>>() {}));
+    assertEqualWhenReserialized(new Key<List<B[]>>() {});
   }
 
   interface B {}
diff --git a/test/com/google/inject/ScopesTest.java b/test/com/google/inject/ScopesTest.java
index 29236d7..2ff3999 100644
--- a/test/com/google/inject/ScopesTest.java
+++ b/test/com/google/inject/ScopesTest.java
@@ -18,36 +18,128 @@
 
 import junit.framework.TestCase;
 
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * @author crazybob@google.com (Bob Lee)
  */
 public class ScopesTest extends TestCase {
 
-  public void testSingletonAnnotation() throws CreationException {
+  public void testSingletonAnnotation() {
     Injector injector = Guice.createInjector(new AbstractModule() {
       protected void configure() {
-        bind(SampleSingleton.class);
+        bind(AnnotatedSingleton.class);
       }
     });
 
     assertSame(
-        injector.getInstance(SampleSingleton.class),
-        injector.getInstance(SampleSingleton.class));
+        injector.getInstance(AnnotatedSingleton.class),
+        injector.getInstance(AnnotatedSingleton.class));
   }
 
-  @Singleton
-  static class SampleSingleton {}
-
-  public void testOverriddingAnnotation()
-      throws CreationException {
+  public void testBoundAsSingleton() {
     Injector injector = Guice.createInjector(new AbstractModule() {
       protected void configure() {
-        bind(SampleSingleton.class).in(Scopes.NO_SCOPE);
+        bind(BoundAsSingleton.class).in(Scopes.SINGLETON);
+      }
+    });
+
+    assertSame(
+        injector.getInstance(BoundAsSingleton.class),
+        injector.getInstance(BoundAsSingleton.class));
+  }
+
+  public void testJustInTimeAnnotatedSingleton() {
+    Injector injector = Guice.createInjector();
+
+    assertSame(
+        injector.getInstance(AnnotatedSingleton.class),
+        injector.getInstance(AnnotatedSingleton.class));
+  }
+
+  public void testSingletonIsPerInjector() {
+    assertNotSame(
+        Guice.createInjector().getInstance(AnnotatedSingleton.class),
+        Guice.createInjector().getInstance(AnnotatedSingleton.class));
+  }
+
+  public void testOverriddingAnnotation() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        bind(AnnotatedSingleton.class).in(Scopes.NO_SCOPE);
       }
     });
 
     assertNotSame(
-        injector.getInstance(SampleSingleton.class),
-        injector.getInstance(SampleSingleton.class));
+        injector.getInstance(AnnotatedSingleton.class),
+        injector.getInstance(AnnotatedSingleton.class));
+  }
+
+  public void testAnnotatedSingletonsInProductionAreEager() {
+    int nextInstanceId = AnnotatedSingleton.nextInstanceId.intValue();
+    
+    Guice.createInjector(Stage.PRODUCTION, new AbstractModule() {
+      protected void configure() {
+        bind(AnnotatedSingleton.class);
+      }
+    });
+
+    assertEquals(nextInstanceId + 1, AnnotatedSingleton.nextInstanceId.intValue());
+  }
+
+  public void testAnnotatedSingletonsInDevelopmentAreNotEager() {
+    int nextInstanceId = AnnotatedSingleton.nextInstanceId.intValue();
+
+    Guice.createInjector(Stage.DEVELOPMENT, new AbstractModule() {
+      protected void configure() {
+        bind(AnnotatedSingleton.class);
+      }
+    });
+
+    assertEquals(nextInstanceId, AnnotatedSingleton.nextInstanceId.intValue());
+  }
+
+  public void testBoundAsSingletonInProductionAreEager() {
+    int nextInstanceId = BoundAsSingleton.nextInstanceId.intValue();
+
+    Guice.createInjector(Stage.PRODUCTION, new AbstractModule() {
+      protected void configure() {
+        bind(BoundAsSingleton.class).in(Scopes.SINGLETON);
+      }
+    });
+
+    assertEquals(nextInstanceId + 1, BoundAsSingleton.nextInstanceId.intValue());
+  }
+
+  public void testBoundAsSingletonInDevelopmentAreNotEager() {
+    int nextInstanceId = BoundAsSingleton.nextInstanceId.intValue();
+
+    Guice.createInjector(Stage.DEVELOPMENT, new AbstractModule() {
+      protected void configure() {
+        bind(BoundAsSingleton.class).in(Scopes.SINGLETON);
+      }
+    });
+
+    assertEquals(nextInstanceId, BoundAsSingleton.nextInstanceId.intValue());
+  }
+
+  public void testSingletonScopeIsNotSerializable() throws IOException {
+    Asserts.assertNotSerializable(Scopes.SINGLETON);
+  }
+
+  public void testNoScopeIsNotSerializable() throws IOException {
+    Asserts.assertNotSerializable(Scopes.NO_SCOPE);
+  }
+
+  @Singleton
+  static class AnnotatedSingleton {
+    static final AtomicInteger nextInstanceId = new AtomicInteger(1);
+    final int instanceId = nextInstanceId.getAndIncrement();
+  }
+
+  static class BoundAsSingleton {
+    static final AtomicInteger nextInstanceId = new AtomicInteger(1);
+    final int instanceId = nextInstanceId.getAndIncrement();
   }
 }
diff --git a/test/com/google/inject/SerializationTest.java b/test/com/google/inject/SerializationTest.java
index 40da32a..ad93956 100644
--- a/test/com/google/inject/SerializationTest.java
+++ b/test/com/google/inject/SerializationTest.java
@@ -17,9 +17,7 @@
 
 package com.google.inject;
 
-import static com.google.inject.Asserts.assertEqualWhenReserialized;
 import static com.google.inject.Asserts.assertSimilarWhenReserialized;
-import junit.framework.Assert;
 import junit.framework.AssertionFailedError;
 import junit.framework.TestCase;
 
@@ -40,13 +38,9 @@
     protected void configure() {}
   }
 
-  public void testSingletonScopeIsNotSerializable() throws IOException {
-    assertNotSerializable(Scopes.SINGLETON);
-  }
-
   public void testCreationExceptionIsSerializable() throws IOException {
     try {
-      assertEqualWhenReserialized(createCreationException());
+      assertSimilarWhenReserialized(createCreationException());
     } catch (NotSerializableException e) {
       fail("Known failure. CreationException is not Serializable.");
     }
@@ -78,21 +72,9 @@
     }
   }
 
-  public void testInjectorIsNotSerializable() throws IOException {
-    assertNotSerializable(Guice.createInjector());
-  }
-
   static class A {
     @Inject B b;
   }
 
   static class B {}
-
-  public static void assertNotSerializable(Object object) throws IOException {
-    try {
-      Asserts.reserialize(object);
-      Assert.fail();
-    } catch (NotSerializableException expected) {
-    }
-  }
 }
diff --git a/test/com/google/inject/TypeLiteralTest.java b/test/com/google/inject/TypeLiteralTest.java
index d061905..e87c17f 100644
--- a/test/com/google/inject/TypeLiteralTest.java
+++ b/test/com/google/inject/TypeLiteralTest.java
@@ -73,9 +73,7 @@
   public void testEqualityOfGenericArrayAndClassArray() {
     TypeLiteral<String[]> arrayAsClass = TypeLiteral.get(String[].class);
     TypeLiteral<String[]> arrayAsType = new TypeLiteral<String[]>() {};
-    assertEquals("Known failure. Although they're functionally equal, Java has two non-equal "
-        + "Types that can represent a String[]. We should probably choose a canonical form.",
-        arrayAsClass, arrayAsType);
+    assertEquals(arrayAsClass, arrayAsType);
   }
 
   public void testSerialization() throws IOException {
diff --git a/test/com/google/inject/matcher/MatcherTest.java b/test/com/google/inject/matcher/MatcherTest.java
index 9d1b077..bc6a3d8 100644
--- a/test/com/google/inject/matcher/MatcherTest.java
+++ b/test/com/google/inject/matcher/MatcherTest.java
@@ -17,6 +17,7 @@
 package com.google.inject.matcher;
 
 import static com.google.inject.Asserts.assertEqualWhenReserialized;
+import static com.google.inject.Asserts.assertEqualsBothWays;
 import static com.google.inject.matcher.Matchers.*;
 import com.google.inject.name.Named;
 import com.google.inject.name.Names;
@@ -35,21 +36,41 @@
 
   public void testAny() {
     assertTrue(any().matches(null));
+    assertEquals("any()", any().toString());
+    assertEqualsBothWays(any(), any());
+    assertFalse(any().equals(not(any())));
   }
 
   public void testNot() {
     assertFalse(not(any()).matches(null));
+    assertEquals("not(any())", not(any()).toString());
+    assertEqualsBothWays(not(any()), not(any()));
+    assertFalse(not(any()).equals(any()));
   }
 
   public void testAnd() {
     assertTrue(any().and(any()).matches(null));
     assertFalse(any().and(not(any())).matches(null));
+    assertEquals("and(any(), any())", any().and(any()).toString());
+    assertEqualsBothWays(any().and(any()), any().and(any()));
+    assertFalse(any().and(any()).equals(not(any())));
+  }
+
+  public void testOr() {
+    assertTrue(any().or(not(any())).matches(null));
+    assertFalse(not(any()).or(not(any())).matches(null));
+    assertEquals("or(any(), any())", any().or(any()).toString());
+    assertEqualsBothWays(any().or(any()), any().or(any()));
+    assertFalse(any().or(any()).equals(not(any())));
   }
 
   public void testAnnotatedWith() {
     assertTrue(annotatedWith(Foo.class).matches(Bar.class));
     assertFalse(annotatedWith(Foo.class).matches(
         MatcherTest.class.getMethods()[0]));
+    assertEquals("annotatedWith(Foo.class)", annotatedWith(Foo.class).toString());
+    assertEqualsBothWays(annotatedWith(Foo.class), annotatedWith(Foo.class));
+    assertFalse(annotatedWith(Foo.class).equals(annotatedWith(Named.class)));
 
     try {
       annotatedWith(Baz.class);
@@ -62,24 +83,36 @@
     assertTrue(subclassesOf(Runnable.class).matches(Runnable.class));
     assertTrue(subclassesOf(Runnable.class).matches(MyRunnable.class));
     assertFalse(subclassesOf(Runnable.class).matches(Object.class));
+    assertEquals("subclassesOf(Runnable.class)", subclassesOf(Runnable.class).toString());
+    assertEqualsBothWays(subclassesOf(Runnable.class), subclassesOf(Runnable.class));
+    assertFalse(subclassesOf(Runnable.class).equals(subclassesOf(Object.class)));
   }
 
   public void testOnly() {
-    assertTrue(only(1000).matches(new Integer(1000)));
-    assertFalse(only(1).matches(new Integer(1000)));
+    assertTrue(only(1000).matches(1000));
+    assertFalse(only(1).matches(1000));
+    assertEquals("only(1)", only(1).toString());
+    assertEqualsBothWays(only(1), only(1));
+    assertFalse(only(1).equals(only(2)));
   }
 
-  public void testSameAs() {
+  @SuppressWarnings("UnnecessaryBoxing")
+  public void testIdenticalTo() {
     Object o = new Object();
-    assertTrue(only(o).matches(o));
-    assertFalse(only(o).matches(new Object()));
+    assertEquals("identicalTo(1)", identicalTo(1).toString());
+    assertTrue(identicalTo(o).matches(o));
+    assertFalse(identicalTo(o).matches(new Object()));
+    assertEqualsBothWays(identicalTo(o), identicalTo(o));
+    assertFalse(identicalTo(1).equals(identicalTo(new Integer(1))));
   }
 
   public void testInPackage() {
-    assertTrue(inPackage(Matchers.class.getPackage())
-        .matches(MatcherTest.class));
-    assertFalse(inPackage(Matchers.class.getPackage())
-        .matches(Object.class));
+    Package matchersPackage = Matchers.class.getPackage();
+    assertEquals("inPackage(com.google.inject.matcher)", inPackage(matchersPackage).toString());
+    assertTrue(inPackage(matchersPackage).matches(MatcherTest.class));
+    assertFalse(inPackage(matchersPackage).matches(Object.class));
+    assertEqualsBothWays(inPackage(matchersPackage), inPackage(matchersPackage));
+    assertFalse(inPackage(matchersPackage).equals(inPackage(Object.class.getPackage())));
   }
 
   public void testReturns() throws NoSuchMethodException {
@@ -88,20 +121,23 @@
         Object.class.getMethod("toString")));
     assertFalse(predicate.matches(
         Object.class.getMethod("hashCode")));
+    assertEquals("returns(only(class java.lang.String))", returns(only(String.class)).toString());
+    assertEqualsBothWays(predicate, returns(only(String.class)));
+    assertFalse(predicate.equals(returns(only(Integer.class))));
   }
   
   public void testSerialization() throws IOException {
-    assertEqualWhenReserialized(Matchers.any());
-    assertEqualWhenReserialized(Matchers.not(Matchers.any()));
-    assertEqualWhenReserialized(Matchers.annotatedWith(Named.class));
-    assertEqualWhenReserialized(Matchers.annotatedWith(Names.named("foo")));
-    assertEqualWhenReserialized(Matchers.only("foo"));
-    assertEqualWhenReserialized(Matchers.identicalTo(Object.class));
-    assertEqualWhenReserialized(Matchers.inPackage(String.class.getPackage()));
-    assertEqualWhenReserialized(Matchers.returns(Matchers.any()));
-    assertEqualWhenReserialized(Matchers.subclassesOf(AbstractList.class));
-    assertEqualWhenReserialized(Matchers.only("a").or(Matchers.only("b")));
-    assertEqualWhenReserialized(Matchers.only("a").and(Matchers.only("b")));
+    assertEqualWhenReserialized(any());
+    assertEqualWhenReserialized(not(any()));
+    assertEqualWhenReserialized(annotatedWith(Named.class));
+    assertEqualWhenReserialized(annotatedWith(Names.named("foo")));
+    assertEqualWhenReserialized(only("foo"));
+    assertEqualWhenReserialized(identicalTo(Object.class));
+    assertEqualWhenReserialized(inPackage(String.class.getPackage()));
+    assertEqualWhenReserialized(returns(any()));
+    assertEqualWhenReserialized(subclassesOf(AbstractList.class));
+    assertEqualWhenReserialized(only("a").or(only("b")));
+    assertEqualWhenReserialized(only("a").and(only("b")));
   }
 
   static abstract class MyRunnable implements Runnable {}
diff --git a/test/com/google/inject/name/NamesTest.java b/test/com/google/inject/name/NamesTest.java
new file mode 100644
index 0000000..113ae49
--- /dev/null
+++ b/test/com/google/inject/name/NamesTest.java
@@ -0,0 +1,48 @@
+/**
+ * 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.name;
+
+import static com.google.inject.Asserts.assertEqualWhenReserialized;
+import static com.google.inject.Asserts.assertEqualsBothWays;
+import junit.framework.TestCase;
+
+import java.io.IOException;
+
+/**
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class NamesTest extends TestCase {
+
+  @Named("foo") private String foo;
+  private Named namedFoo;
+  
+  protected void setUp() throws Exception {
+    super.setUp();
+    namedFoo = getClass().getDeclaredField("foo").getAnnotation(Named.class);
+  }
+
+  public void testConsistentEqualsAndHashcode() {
+    Named actual = Names.named("foo");
+    assertEqualsBothWays(namedFoo, actual);
+    assertEquals(namedFoo.toString(), actual.toString());
+  }
+
+  public void testNamedIsSerializable() throws IOException {
+    assertEqualWhenReserialized(Names.named("foo"));
+  }
+}