Added support for binding to annotations instead of names.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@142 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/ConstantConversionException.java b/src/com/google/inject/ConstantConversionException.java
index ff170b5..c90def4 100644
--- a/src/com/google/inject/ConstantConversionException.java
+++ b/src/com/google/inject/ConstantConversionException.java
@@ -37,15 +37,19 @@
 
   static String createMessage(String value, Key<?> key, Member member,
       String reason) {
+    String annotationMessage = key.hasAnnotationType()
+        ? " annotated with " + key.getAnnotationName()
+        : "";
+    
     return member == null
         ? "Error converting '" + value + "' to "
             + key.getRawType().getSimpleName()
-            + " while getting dependency named '" + key.getName()
-            + "'. Reason: " + reason
+            + " while getting binding value" + annotationMessage
+            + ". Reason: " + reason
         : "Error converting '" + value + "' to "
             + key.getRawType().getSimpleName() + " while injecting "
-            + member.getName() + " with dependency named '" + key.getName()
-            + "' in " + member.getDeclaringClass().getSimpleName()
+            + member.getName() + " with binding value" + annotationMessage
+            + " required by " + member.getDeclaringClass().getSimpleName()
             + ". Reason: " + reason;
   }
 }
diff --git a/src/com/google/inject/ConstructorInjector.java b/src/com/google/inject/ConstructorInjector.java
index d36ea31..6e3b0ea 100644
--- a/src/com/google/inject/ConstructorInjector.java
+++ b/src/com/google/inject/ConstructorInjector.java
@@ -16,9 +16,6 @@
 
 package com.google.inject;
 
-import com.google.inject.util.SurrogateAnnotations;
-import com.google.inject.util.DuplicateAnnotationException;
-
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 
@@ -34,9 +31,6 @@
   final ContainerImpl.ParameterInjector<?>[] parameterInjectors;
   final ConstructionProxy<T> constructionProxy;
 
-  /** Annotation on the constructor. */
-  Inject inject;
-
   ConstructorInjector(ContainerImpl container, Class<T> implementation) {
     this.implementation = implementation;
     Constructor<T> constructor = findConstructorIn(container, implementation);
@@ -59,13 +53,12 @@
   ContainerImpl.ParameterInjector<?>[] createParameterInjector(
       ContainerImpl container, Constructor<T> constructor) {
     try {
-      return inject == null
+      return constructor.getParameterTypes().length == 0
           ? null // default constructor.
           : container.getParametersInjectors(
               constructor,
               constructor.getParameterAnnotations(),
-              constructor.getGenericParameterTypes(),
-              inject.value()
+              constructor.getGenericParameterTypes()
           );
     }
     catch (ContainerImpl.MissingDependencyException e) {
@@ -77,27 +70,17 @@
   private Constructor<T> findConstructorIn(ContainerImpl container,
       Class<T> implementation) {
     Constructor<T> found = null;
-    @SuppressWarnings("unchecked") // why doesn't it return the right thing?
+    @SuppressWarnings("unchecked")
     Constructor<T>[] constructors
         = (Constructor<T>[]) implementation.getDeclaredConstructors();
     for (Constructor<T> constructor : constructors) {
-      Inject inject = null;
-      try {
-        inject = SurrogateAnnotations.findAnnotation(Inject.class, constructor);
-      } catch (DuplicateAnnotationException e) {
-        container.errorHandler.handle(ErrorMessages.DUPLICATE_ANNOTATIONS,
-            Inject.class.getSimpleName(), constructor, e.getFirst(),
-            e.getSecond());
-      }
-
-      if (inject != null) {
+      if (constructor.getAnnotation(Inject.class) != null) {
         if (found != null) {
           container.errorHandler.handle(
               ErrorMessages.TOO_MANY_CONSTRUCTORS, implementation);
           return ContainerImpl.invalidConstructor();
         }
         found = constructor;
-        this.inject = inject;
       }
     }
     if (found != null) {
diff --git a/src/com/google/inject/Container.java b/src/com/google/inject/Container.java
index ac3302a..a7113ff 100644
--- a/src/com/google/inject/Container.java
+++ b/src/com/google/inject/Container.java
@@ -18,51 +18,11 @@
 
 import java.util.List;
 import java.util.Map;
+import java.lang.annotation.Annotation;
 
 /**
  * Injects dependencies into constructors, methods and fields annotated with
- * &#64;{@link Inject}.
- *
- * <p>When injecting a method or constructor, you can additionally annotate its
- * parameters with &#64;{@link Inject} and specify a dependency name. When a
- * parameter has no annotation, the container uses the name from the method or
- * constructor's &#64;{@link Inject} annotation respectively.
- *
- * <p>For example:
- *
- * <pre>
- *  class Foo {
- *
- *    // Inject the int constant named "i".
- *    &#64;Inject("i") int i;
- *
- *    // Inject the default implementation of Bar and the String constant
- *    // named "s".
- *    &#64;Inject Foo(Bar bar, @Inject("s") String s) {
- *      ...
- *    }
- *
- *    // Inject the default implementation of Baz and the Bob implementation
- *    // named "foo".
- *    &#64;Inject void initialize(Baz baz, @Inject("foo") Bob bob) {
- *      ...
- *    }
- *
- *    // Inject the default implementation of Tee.
- *    &#64;Inject void setTee(Tee tee) {
- *      ...
- *    }
- *  }
- * </pre>
- *
- * <p>To get an instance of {@code Foo}:
- *
- * <pre>
- *  Container c = ...;
- *  Key&lt;Foo> fooKey = Key.get(Foo.class);
- *  Factory&lt;Foo> fooFactory = c.getFactory(fooKey);
- *  Foo foo = fooFactory.get();
- * </pre>
+ * {@code @}{@link Inject}. Provides access to {@link Binding}s.
  *
  * @author crazybob@google.com (Bob Lee)
  * @see ContainerBuilder
@@ -120,22 +80,50 @@
   <T> T getInstance(Key<T> key);
 
   /**
-   * Gets an instance from the factory bound to the given type and name.
+   * Gets an instance from the factory bound to the given type and annotation.
    */
-  <T> T getInstance(TypeLiteral<T> type, String name);
+  <T> T getInstance(TypeLiteral<T> type,
+      Annotation annotation);
 
   /**
-   * Gets an instance from the factory bound to the given type and name.
+   * Gets an instance from the factory bound to the given type and annotation.
    */
-  <T> T getInstance(Class<T> type, String name);
+  <T> T getInstance(Class<T> type,
+      Annotation annotation);
 
   /**
-   * Gets the factory bound to the given type and name.
+   * Gets the factory bound to the given type and annotation.
    */
-  <T> Factory<T> getFactory(Class<T> type, String name);
+  <T> Factory<T> getFactory(Class<T> type,
+      Annotation annotation);
 
   /**
-   * Gets the factory bound to the given type and name.
+   * Gets the factory bound to the given type and annotation.
    */
-  <T> Factory<T> getFactory(TypeLiteral<T> type, String name);
+  <T> Factory<T> getFactory(TypeLiteral<T> type,
+      Annotation annotation);
+
+  /**
+   * Gets an instance from the factory bound to the given type and annotation.
+   */
+  <T> T getInstance(TypeLiteral<T> type,
+      Class<? extends Annotation> annotationType);
+
+  /**
+   * Gets an instance from the factory bound to the given type and annotation.
+   */
+  <T> T getInstance(Class<T> type,
+      Class<? extends Annotation> annotationType);
+
+  /**
+   * Gets the factory bound to the given type and annotation.
+   */
+  <T> Factory<T> getFactory(Class<T> type,
+      Class<? extends Annotation> annotationType);
+
+  /**
+   * Gets the factory bound to the given type and annotation.
+   */
+  <T> Factory<T> getFactory(TypeLiteral<T> type,
+      Class<? extends Annotation> annotationType);
 }
diff --git a/src/com/google/inject/ContainerBuilder.java b/src/com/google/inject/ContainerBuilder.java
index 0aca683..7dbff74 100644
--- a/src/com/google/inject/ContainerBuilder.java
+++ b/src/com/google/inject/ContainerBuilder.java
@@ -17,6 +17,7 @@
 package com.google.inject;
 
 import com.google.inject.ContainerImpl.Injector;
+import com.google.inject.Key.AnnotationStrategy;
 import static com.google.inject.Scopes.CONTAINER;
 import static com.google.inject.Scopes.CONTAINER_NAME;
 import static com.google.inject.Scopes.DEFAULT;
@@ -24,13 +25,10 @@
 import com.google.inject.matcher.Matcher;
 import com.google.inject.spi.Message;
 import com.google.inject.spi.SourceConsumer;
-import com.google.inject.util.Objects;
 import static com.google.inject.util.Objects.nonNull;
 import com.google.inject.util.Stopwatch;
 import com.google.inject.util.ToStringBuilder;
-
-import org.aopalliance.intercept.MethodInterceptor;
-
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -40,6 +38,7 @@
 import java.util.Map;
 import java.util.Properties;
 import java.util.logging.Logger;
+import org.aopalliance.intercept.MethodInterceptor;
 
 /**
  * Builds a dependency injection {@link Container}. Binds {@link Key}s to
@@ -197,15 +196,33 @@
    */
   public ConstantBindingBuilder bind(String name) {
     ensureNotCreated();
-    return bind(name, source());
+    return bind(source(), Key.strategyFor(new NamedImpl(name)));
+  }
+
+  /**
+   * Binds a constant to the given annotation.
+   */
+  public ConstantBindingBuilder bind(Annotation annotation) {
+    ensureNotCreated();
+    return bind(source(), Key.strategyFor(annotation));
+  }
+
+  /**
+   * Binds a constant to the given annotation type.
+   */
+  public ConstantBindingBuilder bind(
+      Class<? extends Annotation> annotationType) {
+    ensureNotCreated();
+    return bind(source(), Key.strategyFor(annotationType));
   }
 
   /**
    * Binds a constant to the given name from the given source.
    */
-  private ConstantBindingBuilder bind(String name, Object source) {
+  private ConstantBindingBuilder bind(Object source,
+      AnnotationStrategy annotationStrategy) {
     ConstantBindingBuilder builder =
-        new ConstantBindingBuilder(Objects.nonNull(name, "name")).from(source);
+        new ConstantBindingBuilder(annotationStrategy).from(source);
     constantBindingBuilders.add(builder);
     return builder;
   }
@@ -219,7 +236,7 @@
     for (Map.Entry<String, String> entry : properties.entrySet()) {
       String key = entry.getKey();
       String value = entry.getValue();
-      bind(key, source).to(value);
+      bind(source, Key.strategyFor(new NamedImpl(key))).to(value);
     }
   }
 
@@ -232,7 +249,7 @@
     for (Map.Entry<Object, Object> entry : properties.entrySet()) {
       String key = (String) entry.getKey();
       String value = (String) entry.getValue();
-      bind(key, source).to(value);
+      bind(source, Key.strategyFor(new NamedImpl(key))).to(value);
     }
   }
 
@@ -495,13 +512,34 @@
     }
 
     /**
-     * Sets the name of this binding.
+     * Binds to injection points annotated with {@code @Named(name)}.
      */
     public BindingBuilder<T> named(String name) {
-      if (!this.key.hasDefaultName()) {
-        errorHandler.handle(ErrorMessages.NAME_ALREADY_SET);
+      return annotatedWith(new NamedImpl(nonNull(name, "name")));
+    }
+
+    /**
+     * Specifies the annotation type for this binding.
+     */
+    public BindingBuilder<T> annotatedWith(
+        Class<? extends Annotation> annotationType) {
+      if (this.key.hasAnnotationType()) {
+        errorHandler.handle(ErrorMessages.ANNOTATION_ALREADY_SPECIFIED);
+      } else {
+        this.key = Key.get(this.key.getType(), annotationType);
       }
-      this.key = this.key.named(name);
+      return this;
+    }
+
+    /**
+     * Specifies an annotation for this binding.
+     */
+    public BindingBuilder<T> annotatedWith(Annotation annotation) {
+      if (this.key.hasAnnotationType()) {
+        errorHandler.handle(ErrorMessages.ANNOTATION_ALREADY_SPECIFIED);
+      } else {
+        this.key = Key.get(this.key.getType(), annotation);
+      }
       return this;
     }
 
@@ -704,20 +742,22 @@
   }
 
   private static class BindingInfo<T> {
+
     final Class<T> type;
     final T value;
-    final String name;
+    final AnnotationStrategy annotationStrategy;
     final Object source;
 
-    BindingInfo(Class<T> type, T value, String name, Object source) {
+    BindingInfo(Class<T> type, T value,
+        AnnotationStrategy annotationStrategy, Object source) {
       this.type = type;
       this.value = value;
-      this.name = name;
+      this.annotationStrategy = annotationStrategy;
       this.source = source;
     }
 
     Binding<T> createBinding(ContainerImpl container) {
-      Key<T> key = Key.get(type, name);
+      Key<T> key = Key.get(type, annotationStrategy);
       ConstantFactory<T> factory = new ConstantFactory<T>(value);
       return Binding.newInstance(container, key, source, factory);
     }
@@ -729,11 +769,11 @@
   public class ConstantBindingBuilder {
 
     BindingInfo<?> bindingInfo;
-    final String name;
+    final AnnotationStrategy annotationStrategy;
     Object source = ContainerBuilder.UNKNOWN_SOURCE;
 
-    ConstantBindingBuilder(String name) {
-      this.name = name;
+    ConstantBindingBuilder(AnnotationStrategy annotationStrategy) {
+      this.annotationStrategy = annotationStrategy;
     }
 
     boolean hasValue() {
@@ -826,8 +866,8 @@
       if (this.bindingInfo != null) {
         addError(source, ErrorMessages.CONSTANT_VALUE_ALREADY_SET);
       } else {
-        // TODO: we're sure name and source will have been set already, right?
-        this.bindingInfo = new BindingInfo<T>(type, value, name, source);
+        this.bindingInfo
+            = new BindingInfo<T>(type, value, annotationStrategy, source);
       }
     }
 
diff --git a/src/com/google/inject/ContainerImpl.java b/src/com/google/inject/ContainerImpl.java
index 7f49d68..00fde7e 100644
--- a/src/com/google/inject/ContainerImpl.java
+++ b/src/com/google/inject/ContainerImpl.java
@@ -116,10 +116,10 @@
     return bindingsMultimap.getAll(type);
   }
 
-  <T> List<String> getNamesOfBindingsTo(TypeLiteral<T> type) {
+  <T> List<String> getNamesOfBindingAnnotations(TypeLiteral<T> type) {
     List<String> names = new ArrayList<String>();
     for (Binding<T> binding : findBindingsByType(type)) {
-      names.add(binding.getKey().getName());
+      names.add(binding.getKey().getAnnotationName());
     }
     return names;
   }
@@ -160,8 +160,7 @@
           = ((ParameterizedType) factoryType).getActualTypeArguments()[0];
 
       try {
-        final Factory<?> factory
-            = getFactory(Key.get(entryType, key.getName()));
+        final Factory<?> factory = getFactory(key.ofType(entryType));
         return new InternalFactory<T>() {
           @SuppressWarnings("unchecked")
           public T get(InternalContext context) {
@@ -171,7 +170,7 @@
       }
       catch (ConfigurationException e) {
         ErrorMessages.handleMissingBinding(errorHandler, member, key,
-            getNamesOfBindingsTo(key.getType()));
+            getNamesOfBindingAnnotations(key.getType()));
         return invalidFactory();
       }
     }
@@ -181,7 +180,7 @@
         = PRIMITIVE_COUNTERPARTS.get(rawType);
     if (primitiveCounterpart != null) {
       Binding<?> counterpartBinding
-          = getBinding(Key.get(primitiveCounterpart, key.getName()));
+          = getBinding(key.ofType(primitiveCounterpart));
       if (counterpartBinding != null) {
         return (InternalFactory<? extends T>)
             counterpartBinding.getInternalFactory();
@@ -189,8 +188,8 @@
     }
 
     // Can we convert from a String constant?
-    Binding<String> stringBinding
-        = getBinding(Key.get(String.class, key.getName()));
+    Key<String> stringKey = key.ofType(String.class);
+    Binding<String> stringBinding = getBinding(stringKey);
     if (stringBinding != null && stringBinding.isConstant()) {
       // We don't need do pass in an InternalContext because we know this is
       // a ConstantFactory which will not use it.
@@ -313,9 +312,9 @@
       List<Injector> injectors) {
     addInjectorsForMembers(Arrays.asList(methods), statics, injectors,
         new InjectorFactory<Method>() {
-          public Injector create(ContainerImpl container, Method method,
-              String name) throws MissingDependencyException {
-            return new MethodInjector(container, method, name);
+          public Injector create(ContainerImpl container, Method method)
+              throws MissingDependencyException {
+            return new MethodInjector(container, method);
           }
         });
   }
@@ -324,9 +323,9 @@
       List<Injector> injectors) {
     addInjectorsForMembers(Arrays.asList(fields), statics, injectors,
         new InjectorFactory<Field>() {
-          public Injector create(ContainerImpl container, Field field,
-              String name) throws MissingDependencyException {
-            return new FieldInjector(container, field, name);
+          public Injector create(ContainerImpl container, Field field)
+              throws MissingDependencyException {
+            return new FieldInjector(container, field);
           }
         });
   }
@@ -347,10 +346,10 @@
 
         if (inject != null) {
           try {
-            injectors.add(injectorFactory.create(this, member, inject.value()));
+            injectors.add(injectorFactory.create(this, member));
           }
           catch (MissingDependencyException e) {
-            if (inject.required()) {
+            if (!inject.optional()) {
               // TODO: Report errors for more than one parameter per member.
               e.handle(errorHandler);
             }
@@ -375,7 +374,7 @@
   }
 
   interface InjectorFactory<M extends Member & AnnotatedElement> {
-    Injector create(ContainerImpl container, M member, String name)
+    Injector create(ContainerImpl container, M member)
         throws MissingDependencyException;
   }
 
@@ -415,14 +414,15 @@
     final InternalFactory<?> factory;
     final ExternalContext<?> externalContext;
 
-    public FieldInjector(ContainerImpl container, Field field, String name)
+    public FieldInjector(ContainerImpl container, Field field)
         throws MissingDependencyException {
       this.field = field;
 
       // Ewwwww...
       field.setAccessible(true);
 
-      Key<?> key = Key.get(field.getGenericType(), name);
+      Key<?> key = Key.get(
+          field.getGenericType(), field, field.getAnnotations(), errorHandler);
       factory = container.getFactory(field, key);
       if (factory == null) {
         throw new MissingDependencyException(key, field);
@@ -435,7 +435,8 @@
       ExternalContext<?> previous = context.getExternalContext();
       context.setExternalContext(externalContext);
       try {
-        field.set(o, factory.get(context));
+        Object value = factory.get(context);
+        field.set(o, value);
       }
       catch (IllegalAccessException e) {
         throw new AssertionError(e);
@@ -456,17 +457,8 @@
    */
   <M extends AccessibleObject & Member>
   ParameterInjector<?>[] getParametersInjectors(M member,
-      Annotation[][] annotations, Type[] parameterTypes, String defaultName)
+      Annotation[][] annotations, Type[] parameterTypes)
       throws MissingDependencyException {
-    boolean defaultNameOverridden = !defaultName.equals(Key.DEFAULT_NAME);
-
-    // We only carry over the name from the member level annotation to the
-    // parameters if there's only one parameter.
-    if (parameterTypes.length != 1 && defaultNameOverridden) {
-      errorHandler.handle(
-          ErrorMessages.NAME_ON_MEMBER_WITH_MULTIPLE_PARAMS, member);
-    }
-
     ParameterInjector<?>[] parameterInjectors
         = new ParameterInjector<?>[parameterTypes.length];
     Iterator<Annotation[]> annotationsIterator
@@ -474,29 +466,8 @@
     int index = 0;
     for (Type parameterType : parameterTypes) {
       Annotation[] parameterAnnotations = annotationsIterator.next();
-      Inject inject = null;
-      try {
-        inject = SurrogateAnnotations.findAnnotation(Inject.class,
-            parameterAnnotations);
-      } catch (DuplicateAnnotationException e) {
-        errorHandler.handle(ErrorMessages.DUPLICATE_ANNOTATIONS,
-            Inject.class.getSimpleName(), member, e.getFirst(),
-            e.getSecond());
-      }
-
-      String name;
-      if (defaultNameOverridden) {
-        name = defaultName;
-        if (inject != null) {
-          errorHandler.handle(
-              ErrorMessages.NAME_ON_MEMBER_AND_PARAMETER, member);
-        }
-      }
-      else {
-        name = inject == null ? defaultName : inject.value();
-      }
-
-      Key<?> key = Key.get(parameterType, name);
+      Key<?> key = Key.get(
+          parameterType, member, parameterAnnotations, errorHandler);
       parameterInjectors[index] = createParameterInjector(key, member, index);
       index++;
     }
@@ -521,14 +492,14 @@
     final FastMethod fastMethod;
     final ParameterInjector<?>[] parameterInjectors;
 
-    public MethodInjector(ContainerImpl container, Method method, String name)
+    public MethodInjector(ContainerImpl container, Method method)
         throws MissingDependencyException {
       FastClass fastClass = GuiceFastClass.create(method.getDeclaringClass());
       this.fastMethod = fastClass.getMethod(method);
       Type[] parameterTypes = method.getGenericParameterTypes();
       parameterInjectors = parameterTypes.length > 0
           ? container.getParametersInjectors(
-              method, method.getParameterAnnotations(), parameterTypes, name)
+              method, method.getParameterAnnotations(), parameterTypes)
           : null;
     }
 
@@ -632,20 +603,44 @@
     });
   }
 
-  public <T> T getInstance(TypeLiteral<T> type, String name) {
-    return getFactory(Key.get(type, name)).get();
+  public <T> T getInstance(TypeLiteral<T> type,
+      Annotation annotation) {
+    return getFactory(Key.get(type, annotation)).get();
   }
 
-  public <T> T getInstance(Class<T> type, String name) {
-    return getFactory(Key.get(type, name)).get();
+  public <T> T getInstance(Class<T> type,
+      Annotation annotation) {
+    return getFactory(Key.get(type, annotation)).get();
   }
 
-  public <T> Factory<T> getFactory(Class<T> type, String name) {
-    return getFactory(Key.get(type, name));
+  public <T> Factory<T> getFactory(Class<T> type,
+      Annotation annotation) {
+    return getFactory(Key.get(type, annotation));
   }
 
-  public <T> Factory<T> getFactory(TypeLiteral<T> type, String name) {
-    return getFactory(Key.get(type, name));
+  public <T> Factory<T> getFactory(TypeLiteral<T> type,
+      Annotation annotation) {
+    return getFactory(Key.get(type, annotation));
+  }
+
+  public <T> T getInstance(TypeLiteral<T> type,
+      Class<? extends Annotation> annotationType) {
+    return getFactory(Key.get(type, annotationType)).get();
+  }
+
+  public <T> T getInstance(Class<T> type,
+      Class<? extends Annotation> annotationType) {
+    return getFactory(Key.get(type, annotationType)).get();
+  }
+
+  public <T> Factory<T> getFactory(Class<T> type,
+      Class<? extends Annotation> annotationType) {
+    return getFactory(Key.get(type, annotationType));
+  }
+
+  public <T> Factory<T> getFactory(TypeLiteral<T> type,
+      Class<? extends Annotation> annotationType) {
+    return getFactory(Key.get(type, annotationType));
   }
 
   public <T> T getInstance(TypeLiteral<T> type) {
@@ -756,7 +751,7 @@
 
     void handle(ErrorHandler errorHandler) {
       ErrorMessages.handleMissingBinding(errorHandler, member, key,
-          getNamesOfBindingsTo(key.getType()));
+          getNamesOfBindingAnnotations(key.getType()));
     }
   }
 
diff --git a/src/com/google/inject/ErrorMessages.java b/src/com/google/inject/ErrorMessages.java
index 90ddd7c..6454472 100644
--- a/src/com/google/inject/ErrorMessages.java
+++ b/src/com/google/inject/ErrorMessages.java
@@ -36,8 +36,8 @@
           + " type were found.";
 
   private static final String MISSING_BINDING_BUT_OTHERS_EXIST =
-      "Binding to %s not found, but %s requires it. Names of other"
-          + " bindings to that type: %s";
+      "Binding to %s not found, but %s requires it. Annotations on other"
+          + " bindings to that type include: %s";
 
   static void handleMissingBinding(ErrorHandler errorHandler, Member member,
       Key<?> key, List<String> otherNames) {
@@ -76,7 +76,8 @@
   static final String CANNOT_INJECT_INTERFACE = "Injecting into interfaces is"
       + " not supported. Please use a concrete type instead of %s.";
 
-  static final String NAME_ALREADY_SET = "Binding name is set more than once.";
+  static final String ANNOTATION_ALREADY_SPECIFIED =
+      "More than one annotation type is specified for this binding.";
 
   static final String IMPLEMENTATION_ALREADY_SET = "Implementation is set more"
       + " than once.";
@@ -90,7 +91,7 @@
       + " implementation with with an annotation which is annotated with"
       + " @Scoped.";
 
-  static final String DUPLICATE_ANNOTATIONS = "Duplicate @%s annotations found" 
+  static final String DUPLICATE_ANNOTATIONS = "Duplicate binding annotations found"
       + " on %s: %s and %s";
 
   static final String CONSTANT_VALUE_ALREADY_SET = "Constant value is set more"
@@ -147,9 +148,9 @@
       },
       new Converter<Key>(Key.class) {
         public String toString(Key k) {
-          return k.hasDefaultName()
-              ? k.getType().toString()
-              : k.getType() + " named '" + k.getName() + "'";
+          return k.hasAnnotationType()
+              ? k.getType() + " annotated with " + k.getAnnotationName()
+              : k.getType().toString();
         }
       }
   );
diff --git a/src/com/google/inject/ForBinding.java b/src/com/google/inject/ForBinding.java
new file mode 100644
index 0000000..637d541
--- /dev/null
+++ b/src/com/google/inject/ForBinding.java
@@ -0,0 +1,31 @@
+/**
+ * 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 java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotates annotations which are used for binding.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+@Target({ ElementType.ANNOTATION_TYPE })
+@Retention(RUNTIME)
+public @interface ForBinding {}
diff --git a/src/com/google/inject/Inject.java b/src/com/google/inject/Inject.java
index 10e4f7c..7fa96c9 100644
--- a/src/com/google/inject/Inject.java
+++ b/src/com/google/inject/Inject.java
@@ -20,31 +20,28 @@
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
 import java.lang.annotation.Retention;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 import java.lang.annotation.Target;
 
 /**
- * <p>Annotates members and parameters which should have their value[s]
- * injected. If applied to another annotation type, that annotation type
- * can act as a surrogate and save you from having to repeat the dependency
- * name over and over.
+ * <p>Annotates members which should have their value[s] injected.
+ *
+ * <p>Also applies to other annotations which can be used with {@link Key}s and
+ * at injection points.
  *
  * @author crazybob@google.com (Bob Lee)
  */
-@Target({ METHOD, CONSTRUCTOR, FIELD, PARAMETER, ANNOTATION_TYPE })
+@Target({ METHOD, CONSTRUCTOR, FIELD, ANNOTATION_TYPE })
 @Retention(RUNTIME)
 public @interface Inject {
 
   /**
-   * Dependency name. Defaults to {@link Key#DEFAULT_NAME}.
+   * Indicates whether injection at the target is optional or not. The default
+   * is {@code false}. Can be used on methods and fields. If a method has
+   * multiple parameters and one parameter binding is missing, the method
+   * won't be invoked at all. Not applicable to constructors or other
+   * annotations.
    */
-  String value() default Key.DEFAULT_NAME;
-
-  /**
-   * Whether or not injection is required. Applicable only to methods and fields
-   * (not constructors or parameters).
-   */
-  boolean required() default true;
+  boolean optional() default false;
 }
diff --git a/src/com/google/inject/Key.java b/src/com/google/inject/Key.java
index bc40e75..afcd968 100644
--- a/src/com/google/inject/Key.java
+++ b/src/com/google/inject/Key.java
@@ -17,31 +17,34 @@
 package com.google.inject;
 
 import static com.google.inject.util.Objects.nonNull;
+import com.google.inject.util.ToStringBuilder;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Member;
 import java.lang.reflect.Type;
 
 /**
- * Binding key consisting of a type and a name. Matches the type and name
- * ({@link Inject#value()}) at a point of injection.
+ * Binding key consisting of an injection type and an optional annotation.
+ * Matches the type and annotation at a point of injection.
  *
- * <p>For example, {@code new Key<List<String>>("cities") {}} will match:
+ * <p>For example, {@code Key.get(Service.class, Transactional.class) {}} will
+ * match:
  *
  * <pre>
- *   {@literal @}Inject("cities")
- *   public void setList(List&lt;String> cities) {
+ *   {@literal @}Inject
+ *   public void setService({@literal @}Transactional Service  cities) {
  *     ...
  *   }
  * </pre>
  *
+ * <p>{@code Key} supports generic types via subclassing just like {@link
+ * TypeLiteral}.
+ *
  * @author crazybob@google.com (Bob Lee)
  */
 public abstract class Key<T> {
 
-  /**
-   * Default binding name.
-   */
-  public static final String DEFAULT_NAME = "default";
+  final AnnotationStrategy annotationStrategy;
 
-  final String name;
   final TypeLiteral<T> typeLiteral;
   final int hashCode;
 
@@ -52,58 +55,98 @@
    * parameter in the anonymous class's type hierarchy so we can reconstitute it
    * at runtime despite erasure.
    *
-   * <p>Example usage for a binding of type {@code Foo} named "bar":
-   * {@code new Key<Foo>("bar") {}}.
+   * <p>Example usage for a binding of type {@code Foo} annotated with
+   * {@code @Bar}:
+   *
+   * <p>{@code new Key<Foo>(Bar.class) {}}.
    */
   @SuppressWarnings("unchecked")
-  protected Key(String name) {
-    this.name = nonNull(name, "name");
+  protected Key(Class<? extends Annotation> annotationType) {
+    this.annotationStrategy = strategyFor(annotationType);
     this.typeLiteral
         = (TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass());
     this.hashCode = computeHashCode();
   }
 
   /**
-   * Convenience method. Delegates to {@link #Key(String)} with
-   * {@link #DEFAULT_NAME}.
+   * Constructs a new key. Derives the type from this class's type parameter.
+   *
+   * <p>Clients create an empty anonymous subclass. Doing so embeds the type
+   * parameter in the anonymous class's type hierarchy so we can reconstitute it
+   * at runtime despite erasure.
+   *
+   * <p>Example usage for a binding of type {@code Foo} annotated with
+   * {@code @Bar}:
+   *
+   * <p>{@code new Key<Foo>(new Bar()) {}}.
    */
+  @SuppressWarnings("unchecked")
+  protected Key(Annotation annotation) {
+    this.annotationStrategy = strategyFor(annotation);
+    this.typeLiteral
+        = (TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass());
+    this.hashCode = computeHashCode();
+  }
+
+  /**
+   * Constructs a new key. Derives the type from this class's type parameter.
+   *
+   * <p>Clients create an empty anonymous subclass. Doing so embeds the type
+   * parameter in the anonymous class's type hierarchy so we can reconstitute it
+   * at runtime despite erasure.
+   *
+   * <p>Example usage for a binding of type {@code Foo} annotated with
+   * {@code @Named("bar")}:
+   *
+   * <p>{@code new Key<Foo>("bar") {}}.
+   */
+  @SuppressWarnings("unchecked")
+  protected Key(String name) {
+    this.annotationStrategy = strategyFor(new NamedImpl(name));
+    this.typeLiteral
+        = (TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass());
+    this.hashCode = computeHashCode();
+  }
+
+  /**
+   * Constructs a new key. Derives the type from this class's type parameter.
+   *
+   * <p>Clients create an empty anonymous subclass. Doing so embeds the type
+   * parameter in the anonymous class's type hierarchy so we can reconstitute it
+   * at runtime despite erasure.
+   *
+   * <p>Example usage for a binding of type {@code Foo}:
+   *
+   * <p>{@code new Key<Foo>() {}}.
+   */
+  @SuppressWarnings("unchecked")
   protected Key() {
-    this(DEFAULT_NAME);
+    this.annotationStrategy = NULL_STRATEGY;
+    this.typeLiteral
+        = (TypeLiteral<T>) TypeLiteral.fromSuperclassTypeParameter(getClass());
+    this.hashCode = computeHashCode();
   }
 
   /**
    * Unsafe. Constructs a key from a manually specified type.
    */
   @SuppressWarnings("unchecked")
-  private Key(Type type, String name) {
-    this.name = nonNull(name, "name");
+  private Key(Type type, AnnotationStrategy annotationStrategy) {
+    this.annotationStrategy = annotationStrategy;
     this.typeLiteral = (TypeLiteral<T>) TypeLiteral.get(type);
     this.hashCode = computeHashCode();
   }
 
   /** Constructs a key from a manually specified type. */
-  private Key(TypeLiteral<T> typeLiteral, String name) {
-    this.name = nonNull(name, "name");
+  private Key(TypeLiteral<T> typeLiteral,
+      AnnotationStrategy annotationStrategy) {
+    this.annotationStrategy = annotationStrategy;
     this.typeLiteral = typeLiteral;
     this.hashCode = computeHashCode();
   }
 
   private int computeHashCode() {
-    return typeLiteral.hashCode() * 31 + name.hashCode();
-  }
-
-  /**
-   * Returns {@code true} if this key has the default name.
-   */
-  public boolean hasDefaultName() {
-    return DEFAULT_NAME.equals(this.name);
-  }
-
-  /**
-   * Returns a new key with the same type as this key and the given name,
-   */
-  Key<T> named(String name) {
-    return new SimpleKey<T>(this.typeLiteral, name);
+    return typeLiteral.hashCode() * 31 + annotationStrategy.hashCode();
   }
 
   /**
@@ -114,10 +157,23 @@
   }
 
   /**
-   * Gets the binding name.
+   * Gets the annotation type.
    */
- public String getName() {
-    return name;
+  public Class<? extends Annotation> getAnnotationType() {
+    return annotationStrategy.getAnnotationType();
+  }
+
+  boolean hasAnnotationType() {
+    return annotationStrategy.getAnnotationType() != null;
+  }
+
+  String getAnnotationName() {
+    Annotation annotation = annotationStrategy.getAnnotation();
+    if (annotation != null) {
+      return annotation.toString();
+    }
+
+    return annotationStrategy.getAnnotationType().toString();
   }
 
   public int hashCode() {
@@ -136,64 +192,299 @@
       return false;
     }
     Key<?> other = (Key<?>) o;
-    return name.equals(other.name) && typeLiteral.equals(other.typeLiteral);
+    return annotationStrategy.equals(other.annotationStrategy)
+        && typeLiteral.equals(other.typeLiteral);
   }
 
   public String toString() {
-    return Key.class.getSimpleName()
-        + "[type=" + typeLiteral + ", name='" + name + "']";
+    return new ToStringBuilder(Key.class)
+        .add("type", typeLiteral)
+        .add("annotation", annotationStrategy)
+        .toString();
   }
 
   /**
-   * Gets a key for a {@code Class}. Defaults name to {@link #DEFAULT_NAME}.
+   * Gets a key for an injection type and an annotation strategy.
+   */
+  static <T> Key<T> get(Class<T> type,
+      AnnotationStrategy annotationStrategy) {
+    return new SimpleKey<T>(type, annotationStrategy);
+  }
+
+  /**
+   * Gets a key for an injection type.
    */
   public static <T> Key<T> get(Class<T> type) {
-    return new SimpleKey<T>(type, DEFAULT_NAME);
+    return new SimpleKey<T>(type, NULL_STRATEGY);
   }
 
   /**
-   * Gets a key for a {@code Class} and a name.
+   * Gets a key for an injection type and an annotation type.
+   */
+  public static <T> Key<T> get(Class<T> type,
+      Class<? extends Annotation> annotationType) {
+    return new SimpleKey<T>(type, strategyFor(annotationType));
+  }
+
+  /**
+   * Gets a key for an injection type and the annotation {@code Named(name)}.
    */
   public static <T> Key<T> get(Class<T> type, String name) {
-    return new SimpleKey<T>(type, name);
+    return new SimpleKey<T>(type, strategyFor(new NamedImpl(name)));
   }
 
   /**
-   * Gets a key for a type. Defaults name to {@link #DEFAULT_NAME}.
+   * 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));
+  }
+
+  /**
+   * Gets a key for an injection type.
    */
   public static Key<?> get(Type type) {
-    return new SimpleKey<Object>(type, DEFAULT_NAME);
+    return new SimpleKey<Object>(type, NULL_STRATEGY);
   }
 
   /**
-   * Gets a key for a type and a name.
+   * Gets a key for an injection type and an annotation type.
+   */
+  public static Key<?> get(Type type,
+      Class<? extends Annotation> annotationType) {
+    return new SimpleKey<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));
+  }
+
+  /**
+   * Gets a key for an injection type and the annotation {@code Named(name)}.
    */
   public static Key<?> get(Type type, String name) {
-    return new SimpleKey<Object>(type, name);
+    return new SimpleKey<Object>(type, strategyFor(new NamedImpl(name)));
   }
 
   /**
-   * Gets a key for a type. Defaults name to {@link #DEFAULT_NAME}.
+   * Gets a key for an injection type.
    */
   public static <T> Key<T> get(TypeLiteral<T> typeLiteral) {
-    return new SimpleKey<T>(typeLiteral, DEFAULT_NAME);
+    return new SimpleKey<T>(typeLiteral, NULL_STRATEGY);
   }
 
   /**
-   * Gets key for a type and a name.
+   * Gets a key for an injection type and an annotation type.
+   */
+  public static <T> Key<T> get(TypeLiteral<T> typeLiteral,
+      Class<? extends Annotation> annotationType) {
+    return new SimpleKey<T>(typeLiteral, strategyFor(annotationType));
+  }
+
+  /**
+   * Gets a key for an injection type and an annotation.
+   */
+  public static <T> Key<T> get(TypeLiteral<T> typeLiteral,
+      Annotation annotation) {
+    return new SimpleKey<T>(typeLiteral, strategyFor(annotation));
+  }
+
+  /**
+   * Gets a key for an injection type and the annotation {@code Named(name)}.
    */
   public static <T> Key<T> get(TypeLiteral<T> typeLiteral, String name) {
-    return new SimpleKey<T>(typeLiteral, name);
+    return new SimpleKey<T>(typeLiteral, strategyFor(new NamedImpl(name)));
+  }
+
+  /**
+   * Gets a key for the given type, member and annotations.
+   */
+  static Key<?> get(Type type, Member member, Annotation[] annotations,
+      ErrorHandler errorHandler) {
+    Annotation found = null;
+    for (Annotation annotation : annotations) {
+      if (annotation.annotationType().getAnnotation(ForBinding.class) != null) {
+        if (found == null) {
+          found = annotation;
+        } else {
+          errorHandler.handle(ErrorMessages.DUPLICATE_ANNOTATIONS, member,
+              found, annotation);
+        }
+      }
+    }
+    Key<?> key = found == null ? Key.get(type) : Key.get(type, found);
+    return key;
+  }
+
+  /**
+   * Returns a new key of the specified type with the same annotation as this
+   * key.
+   */
+  public <T> Key<T> ofType(Class<T> type) {
+    return new SimpleKey<T>(type, annotationStrategy);
+  }
+
+  /**
+   * Returns a new key of the specified type with the same annotation as this
+   * key.
+   */
+  public Key<?> ofType(Type type) {
+    return new SimpleKey<Object>(type, annotationStrategy);
+  }
+
+  /**
+   * Returns a new key of the specified type with the same annotation as this
+   * key.
+   */
+  public <T> Key<T> ofType(TypeLiteral<T> type) {
+    return new SimpleKey<T>(type, annotationStrategy);
   }
 
   private static class SimpleKey<T> extends Key<T> {
 
-    private SimpleKey(Type type, String name) {
-      super(type, name);
+    private SimpleKey(Type type, AnnotationStrategy annotationStrategy) {
+      super(type, annotationStrategy);
     }
 
-    private SimpleKey(TypeLiteral<T> typeLiteral, String name) {
-      super(typeLiteral, name);
+    private SimpleKey(TypeLiteral<T> typeLiteral,
+        AnnotationStrategy annotationStrategy) {
+      super(typeLiteral, annotationStrategy);
+    }
+  }
+
+  interface AnnotationStrategy {
+
+    Annotation getAnnotation();
+    Class<? extends Annotation> getAnnotationType();
+  }
+
+  static final AnnotationStrategy NULL_STRATEGY = new AnnotationStrategy() {
+
+    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]";
+    }
+  };
+
+  /**
+   * Returns {@code true} if the given annotation type has no attributes.
+   */
+  static boolean isMarker(Class<? extends Annotation> annotationType) {
+    return annotationType.getDeclaredMethods().length == 0;
+  }
+
+  /**
+   * Gets the strategy for an annotation.
+   */
+  static AnnotationStrategy strategyFor(Annotation annotation) {
+    nonNull(annotation, "annotation");
+    return isMarker(annotation.annotationType())
+        ? new AnnotationTypeStrategy(annotation.annotationType(), annotation)
+        : new AnnotationInstanceStrategy(annotation);
+  }
+
+  /**
+   * Gets the strategy for an annotation type.
+   */
+  static AnnotationStrategy strategyFor(
+      Class<? extends Annotation> annotationType) {
+    nonNull(annotationType, "annotation type");
+    if (!isMarker(annotationType)) {
+      throw new IllegalArgumentException(annotationType.getName()
+        + " is not a marker annotation, i.e. it has attributes. Please"
+        + " use an Annotation instance or a marker annotation instead.");
+    }
+    return new AnnotationTypeStrategy(annotationType, null);
+  }
+
+  static class AnnotationInstanceStrategy implements AnnotationStrategy {
+
+    final Annotation annotation;
+
+    AnnotationInstanceStrategy(Annotation annotation) {
+      this.annotation = nonNull(annotation, "annotation");
+    }
+
+    public Annotation getAnnotation() {
+      return annotation;
+    }
+
+    public Class<? extends Annotation> getAnnotationType() {
+      return annotation.annotationType();
+    }
+
+    public boolean equals(Object o) {
+      if (!(o instanceof AnnotationInstanceStrategy)) {
+        return false;
+      }
+
+      AnnotationInstanceStrategy other = (AnnotationInstanceStrategy) o;
+      return annotation.equals(other.annotation);
+    }
+
+    public int hashCode() {
+      return annotation.hashCode();
+    }
+
+    public String toString() {
+      return annotation.toString();
+    }
+  }
+
+  static class AnnotationTypeStrategy implements AnnotationStrategy {
+
+    final Class<? extends Annotation> annotationType;
+
+    // Keep the instance around if we have it so the client can request it.
+    final Annotation annotation;
+
+    AnnotationTypeStrategy(Class<? extends Annotation> annotationType,
+        Annotation annotation) {
+      this.annotationType = nonNull(annotationType, "annotation type");
+      this.annotation = annotation;
+    }
+
+    public Annotation getAnnotation() {
+      return annotation;
+    }
+
+    public Class<? extends Annotation> getAnnotationType() {
+      return annotationType;
+    }
+
+    public boolean equals(Object o) {
+      if (!(o instanceof AnnotationTypeStrategy)) {
+        return false;
+      }
+
+      AnnotationTypeStrategy other = (AnnotationTypeStrategy) o;
+      return annotationType.equals(other.annotationType);
+    }
+
+    public int hashCode() {
+      return annotationType.hashCode();
+    }
+
+    public String toString() {
+      return annotationType.toString();
     }
   }
 }
diff --git a/src/com/google/inject/Named.java b/src/com/google/inject/Named.java
new file mode 100644
index 0000000..0a0bd8d
--- /dev/null
+++ b/src/com/google/inject/Named.java
@@ -0,0 +1,31 @@
+/**
+ * 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 java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotates named things.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+@Retention(RUNTIME)
+@ForBinding
+public @interface Named {
+  String value();
+}
diff --git a/src/com/google/inject/NamedImpl.java b/src/com/google/inject/NamedImpl.java
new file mode 100644
index 0000000..c8be943
--- /dev/null
+++ b/src/com/google/inject/NamedImpl.java
@@ -0,0 +1,55 @@
+/**
+ * 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 java.lang.annotation.Annotation;
+import com.google.inject.util.Objects;
+
+class NamedImpl implements Named {
+
+  final String value;
+
+  public NamedImpl(String value) {
+    this.value = Objects.nonNull(value, "name");
+  }
+
+  public String value() {
+    return this.value;
+  }
+
+  public int hashCode() {
+    // This is specified in java.lang.Annotation.
+    return 127 * "value".hashCode() ^ value.hashCode();
+  }
+
+  public boolean equals(Object o) {
+    if (!(o instanceof Named)) {
+      return false;
+    }
+
+    Named other = (Named) o;
+    return value.equals(other.value());
+  }
+
+  public String toString() {
+    return "@Named(\"" + value + "\")";
+  }
+
+  public Class<? extends Annotation> annotationType() {
+    return Named.class;
+  }
+}
diff --git a/src/com/google/inject/util/Objects.java b/src/com/google/inject/util/Objects.java
index 15fbf3f..59b3d25 100644
--- a/src/com/google/inject/util/Objects.java
+++ b/src/com/google/inject/util/Objects.java
@@ -36,4 +36,19 @@
     }
     return t;
   }
+
+  /**
+   * {@code null}-aware equals.
+   */
+  public static boolean equal(Object a, Object b) {
+    if (a == b) {
+      return true;
+    }
+
+    if (a == null || b == null) {
+      return false;
+    }
+
+    return a.equals(b);
+  }
 }