Revision: e39158525475
Author:   Christian Edward Gruber <cgru...@google.com>
Date:     Thu May 16 11:00:54 2013
Log: Change Key so that it upgrades Annotation classes where all methods have default values into an instance of the Annotation with the defaults as values, so that:
  @Retention(RUNTIME)
  @BindingAnnotation @interface AllDefaults {
    int hasDefault() default 1;
  }

  @AllDefaults class Foo {}

  void testKey() {
assertEquals(Key.get(Foo.class, Foo.class.getAnnotation(AllDefaults.class)),
                 Key.get(Foo.class, AllDefaults.class));
  }

Also adds an option to "require exact binding annotations", which disables the error-prone fallback built into Guice whereby a binding for @Named Foo can substitute for @Named("foo") Foo if the latter doesn't exist but the former does.

-----------------
Manually Synced.
COMMIT=45600016

http://code.google.com/p/google-guice/source/detail?r=e39158525475

Added:
 /core/src/com/google/inject/spi/RequireExactBindingAnnotationsOption.java
Modified:
 /core/src/com/google/inject/Binder.java
 /core/src/com/google/inject/Key.java
 /core/src/com/google/inject/internal/Annotations.java
 /core/src/com/google/inject/internal/InjectorImpl.java
 /core/src/com/google/inject/internal/InjectorOptionsProcessor.java
 /core/src/com/google/inject/spi/DefaultElementVisitor.java
 /core/src/com/google/inject/spi/ElementVisitor.java
 /core/src/com/google/inject/spi/Elements.java
 /core/test/com/google/inject/BindingAnnotationTest.java
 /core/test/com/google/inject/KeyTest.java
/extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java /extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java

=======================================
--- /dev/null
+++ /core/src/com/google/inject/spi/RequireExactBindingAnnotationsOption.java Thu May 16 11:00:54 2013
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2010 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.spi;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.inject.Binder;
+
+/**
+ * A request to require exact binding annotations.
+ *
+ * @author sa...@google.com (Sam Berlin)
+ * @since 4.0
+ */
+public final class RequireExactBindingAnnotationsOption implements Element {
+  private final Object source;
+
+  RequireExactBindingAnnotationsOption(Object source) {
+    this.source = checkNotNull(source, "source");
+  }
+
+  public Object getSource() {
+    return source;
+  }
+
+  public void applyTo(Binder binder) {
+    binder.withSource(getSource()).requireExactBindingAnnotations();
+  }
+
+  public <T> T acceptVisitor(ElementVisitor<T> visitor) {
+    return visitor.visit(this);
+  }
+}
=======================================
--- /core/src/com/google/inject/Binder.java     Thu May 31 16:54:04 2012
+++ /core/src/com/google/inject/Binder.java     Thu May 16 11:00:54 2013
@@ -483,4 +483,14 @@
    * @since 4.0
    */
   void requireAtInjectOnConstructors();
+
+  /**
+ * Requires that Guice finds an exactly matching binding annotation. This disables the
+   * error-prone feature in Guice where it can substitute a binding for
+   * <code>{@literal @}Named Foo</code> when attempting to inject
+   * <code>{@literal @}Named("foo") Foo</code>.
+   *
+   * @since 4.0
+   */
+  void requireExactBindingAnnotations();
 }
=======================================
--- /core/src/com/google/inject/Key.java        Thu Jul  7 17:34:16 2011
+++ /core/src/com/google/inject/Key.java        Thu May 16 11:00:54 2013
@@ -18,6 +18,8 @@

 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.inject.internal.Annotations.generateAnnotation;
+import static com.google.inject.internal.Annotations.isAllDefaultMethods;

 import com.google.inject.internal.Annotations;
 import com.google.inject.internal.MoreTypes;
@@ -350,10 +352,15 @@
    * Gets the strategy for an annotation type.
    */
static AnnotationStrategy strategyFor(Class<? extends Annotation> annotationType) {
+    annotationType = Annotations.canonicalizeIfNamed(annotationType);
+    if (isAllDefaultMethods(annotationType)) {
+      return strategyFor(generateAnnotation(annotationType));
+    }
+
     checkNotNull(annotationType, "annotation type");
     ensureRetainedAtRuntime(annotationType);
     ensureIsBindingAnnotation(annotationType);
- return new AnnotationTypeStrategy(Annotations.canonicalizeIfNamed(annotationType), null);
+    return new AnnotationTypeStrategy(annotationType, null);

   }

=======================================
--- /core/src/com/google/inject/internal/Annotations.java Thu Jul 7 17:34:16 2011 +++ /core/src/com/google/inject/internal/Annotations.java Thu May 16 11:00:54 2013
@@ -17,7 +17,12 @@
 package com.google.inject.internal;

 import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Joiner.MapJoiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.MapMaker;
+import com.google.common.collect.Maps;
 import com.google.inject.BindingAnnotation;
 import com.google.inject.Key;
 import com.google.inject.ScopeAnnotation;
@@ -29,7 +34,10 @@
 import java.lang.annotation.Annotation;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Map;
@@ -49,6 +57,115 @@
public static boolean isMarker(Class<? extends Annotation> annotationType) {
     return annotationType.getDeclaredMethods().length == 0;
   }
+
+ public static boolean isAllDefaultMethods(Class<? extends Annotation> annotationType) {
+    boolean hasMethods = false;
+    for (Method m : annotationType.getDeclaredMethods()) {
+      hasMethods = true;
+      if (m.getDefaultValue() == null) {
+        return false;
+      }
+    }
+    return hasMethods;
+  }
+
+  private static final Map<Class<? extends Annotation>, Annotation> cache =
+      new MapMaker().weakKeys().makeComputingMap(
+          new Function<Class<? extends Annotation>, Annotation>() {
+            @Override
+            public Annotation apply(Class<? extends Annotation> input) {
+              return generateAnnotationImpl(input);
+            }
+          });
+
+  /**
+ * Generates an Annotation for the annotation class. Requires that the annotation is all
+   * optionals.
+   */
+ public static <T extends Annotation> T generateAnnotation(Class<T> annotationType) {
+    Preconditions.checkState(
+ isAllDefaultMethods(annotationType), "%s is not all default methods", annotationType);
+    return (T)cache.get(annotationType);
+  }
+
+ private static <T extends Annotation> T generateAnnotationImpl(final Class<T> annotationType) {
+    final Map<String, Object> members = resolveMembers(annotationType);
+    return annotationType.cast(Proxy.newProxyInstance(
+        annotationType.getClassLoader(),
+        new Class<?>[] { annotationType },
+        new InvocationHandler() {
+          @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
+            String name = method.getName();
+            if (name.equals("annotationType")) {
+              return annotationType;
+            } else if (name.equals("toString")) {
+              return annotationToString(annotationType, members);
+            } else if (name.equals("hashCode")) {
+              return annotationHashCode(annotationType, members);
+            } else if (name.equals("equals")) {
+              return annotationEquals(annotationType, members, args[0]);
+            } else {
+              return members.get(name);
+            }
+          }
+        }));
+  }
+
+  private static ImmutableMap<String, Object> resolveMembers(
+      Class<? extends Annotation> annotationType) {
+    ImmutableMap.Builder<String, Object> result = ImmutableMap.builder();
+    for (Method method : annotationType.getDeclaredMethods()) {
+      result.put(method.getName(), method.getDefaultValue());
+    }
+    return result.build();
+  }
+
+  /** Implements {@link Annotation#equals}. */
+  private static boolean annotationEquals(Class<? extends Annotation> type,
+      Map<String, Object> members, Object other) throws Exception {
+    if (!type.isInstance(other)) {
+      return false;
+    }
+    for (Method method : type.getDeclaredMethods()) {
+      String name = method.getName();
+      if (!Arrays.deepEquals(
+ new Object[] {method.invoke(other)}, new Object[] {members.get(name)})) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /** Implements {@link Annotation#hashCode}. */
+  private static int annotationHashCode(Class<? extends Annotation> type,
+      Map<String, Object> members) throws Exception {
+    int result = 0;
+    for (Method method : type.getDeclaredMethods()) {
+      String name = method.getName();
+      Object value = members.get(name);
+ result += (127 * name.hashCode()) ^ (Arrays.deepHashCode(new Object[] {value}) - 31);
+    }
+    return result;
+  }
+
+ private static final MapJoiner JOINER = Joiner.on(", ").withKeyValueSeparator("=");
+
+ private static final Function<Object, String> DEEP_TO_STRING_FN = new Function<Object, String>() {
+    @Override
+    public String apply(Object arg) {
+      String s = Arrays.deepToString(new Object[] {arg});
+      return s.substring(1, s.length() - 1); // cut off brackets
+    }
+  };
+
+  /** Implements {@link Annotation#toString}. */
+ private static String annotationToString(Class<? extends Annotation> type,
+      Map<String, Object> members) throws Exception {
+ StringBuilder sb = new StringBuilder().append("@").append(type.getName()).append("(");
+    JOINER.appendTo(sb, Maps.transformValues(members, DEEP_TO_STRING_FN));
+    return sb.append(")").toString();
+  }

   /**
    * Returns true if the given annotation is retained at runtime.
=======================================
--- /core/src/com/google/inject/internal/InjectorImpl.java Thu May 31 16:54:04 2012 +++ /core/src/com/google/inject/internal/InjectorImpl.java Thu May 16 11:00:54 2013
@@ -72,13 +72,15 @@
     final boolean jitDisabled;
     final boolean disableCircularProxies;
     final boolean atInjectRequired;
+    final boolean exactBindingAnnotationsRequired;

InjectorOptions(Stage stage, boolean jitDisabled, boolean disableCircularProxies,
-        boolean atInjectRequired) {
+ boolean atInjectRequired, boolean exactBindingAnnotationsRequired) {
       this.stage = stage;
       this.jitDisabled = jitDisabled;
       this.disableCircularProxies = disableCircularProxies;
       this.atInjectRequired = atInjectRequired;
+ this.exactBindingAnnotationsRequired = exactBindingAnnotationsRequired;
     }

     @Override
@@ -88,6 +90,7 @@
           .add("jitDisabled", jitDisabled)
           .add("disableCircularProxies", disableCircularProxies)
           .add("atInjectRequired", atInjectRequired)
+ .add("exactBindingAnnotationsRequired", exactBindingAnnotationsRequired)
           .toString();
     }
   }
@@ -853,7 +856,7 @@
     // If the key has an annotation...
     if (key.getAnnotationType() != null) {
       // Look for a binding without annotation attributes or return null.
-      if (key.hasAttributes()) {
+ if (key.hasAttributes() && !options.exactBindingAnnotationsRequired) {
         try {
           Errors ignored = new Errors();
return getBindingOrThrow(key.withoutAttributes(), ignored, JitLimitation.NO_JIT);
=======================================
--- /core/src/com/google/inject/internal/InjectorOptionsProcessor.java Thu May 31 16:54:04 2012 +++ /core/src/com/google/inject/internal/InjectorOptionsProcessor.java Thu May 16 11:00:54 2013
@@ -23,6 +23,7 @@
 import com.google.inject.internal.InjectorImpl.InjectorOptions;
 import com.google.inject.spi.DisableCircularProxiesOption;
 import com.google.inject.spi.RequireAtInjectOnConstructorsOption;
+import com.google.inject.spi.RequireExactBindingAnnotationsOption;
 import com.google.inject.spi.RequireExplicitBindingsOption;

 /**
@@ -35,6 +36,7 @@
   private boolean disableCircularProxies = false;
   private boolean jitDisabled = false;
   private boolean atInjectRequired = false;
+  private boolean exactBindingAnnotationsRequired = false;

   InjectorOptionsProcessor(Errors errors) {
     super(errors);
@@ -57,6 +59,12 @@
     atInjectRequired = true;
     return true;
   }
+
+  @Override
+  public Boolean visit(RequireExactBindingAnnotationsOption option) {
+    exactBindingAnnotationsRequired = true;
+    return true;
+  }

   InjectorOptions getOptions(Stage stage, InjectorOptions parentOptions) {
     checkNotNull(stage, "stage must be set");
@@ -65,14 +73,16 @@
           stage,
           jitDisabled,
           disableCircularProxies,
-          atInjectRequired);
+          atInjectRequired,
+          exactBindingAnnotationsRequired);
     } else {
checkState(stage == parentOptions.stage, "child & parent stage don't match");
       return new InjectorOptions(
           stage,
           jitDisabled || parentOptions.jitDisabled,
           disableCircularProxies || parentOptions.disableCircularProxies,
-          atInjectRequired || parentOptions.atInjectRequired);
+          atInjectRequired || parentOptions.atInjectRequired,
+ exactBindingAnnotationsRequired || parentOptions.exactBindingAnnotationsRequired);
     }
   }

=======================================
--- /core/src/com/google/inject/spi/DefaultElementVisitor.java Thu May 31 16:54:04 2012 +++ /core/src/com/google/inject/spi/DefaultElementVisitor.java Thu May 16 11:00:54 2013
@@ -98,4 +98,8 @@
   public V visit(RequireAtInjectOnConstructorsOption option) {
     return visitOther(option);
   }
+
+  public V visit(RequireExactBindingAnnotationsOption option) {
+    return visitOther(option);
+  }
 }
=======================================
--- /core/src/com/google/inject/spi/ElementVisitor.java Thu May 31 16:54:04 2012 +++ /core/src/com/google/inject/spi/ElementVisitor.java Thu May 16 11:00:54 2013
@@ -113,4 +113,11 @@
    * @since 4.0
    */
   V visit(RequireAtInjectOnConstructorsOption option);
+
+  /**
+   * Visit a require exact binding annotations command.
+   *
+   * @since 4.0
+   */
+  V visit(RequireExactBindingAnnotationsOption option);
 }
=======================================
--- /core/src/com/google/inject/spi/Elements.java       Thu May 31 16:54:04 2012
+++ /core/src/com/google/inject/spi/Elements.java       Thu May 16 11:00:54 2013
@@ -319,6 +319,10 @@
     public void requireAtInjectOnConstructors() {
       elements.add(new RequireAtInjectOnConstructorsOption(getSource()));
     }
+
+    public void requireExactBindingAnnotations() {
+      elements.add(new RequireExactBindingAnnotationsOption(getSource()));
+    }

     public void expose(Key<?> key) {
       exposeInternal(key);
=======================================
--- /core/test/com/google/inject/BindingAnnotationTest.java Thu Jul 7 17:34:16 2011 +++ /core/test/com/google/inject/BindingAnnotationTest.java Thu May 16 11:00:54 2013
@@ -29,27 +29,75 @@
  */
 public class BindingAnnotationTest extends TestCase {

-  public void testAnnotationWithValueMatchesKeyWithTypeOnly() throws
-      CreationException {
+ public void testAnnotationWithValueMatchesKeyWithTypeOnly() throws CreationException {
     Injector c = Guice.createInjector(new AbstractModule() {
+      @Override
       protected void configure() {
         bindConstant().annotatedWith(Blue.class).to("foo");
-        bind(Foo.class);
+        bind(BlueFoo.class);
       }
     });

-    Foo foo = c.getInstance(Foo.class);
+    BlueFoo foo = c.getInstance(BlueFoo.class);

     assertEquals("foo", foo.s);
   }
+
+  public void testRequireExactAnnotationsDisablesFallback() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        @Override
+        protected void configure() {
+          binder().requireExactBindingAnnotations();
+          bindConstant().annotatedWith(Blue.class).to("foo");
+          bind(BlueFoo.class);
+        }
+      });
+      fail();
+    } catch (CreationException expected) {
+ assertContains(expected.getMessage(), "No implementation for java.lang.String annotated with",
+          "BindingAnnotationTest$Blue(value=5) was bound",
+ "at " + BindingAnnotationTest.class.getName(), ".configure(BindingAnnotationTest.java:");
+    }
+  }
+
+  public void testRequireExactAnnotationsDoesntBreakIfDefaultsExist() {
+       Guice.createInjector(new AbstractModule() {
+        @Override
+        protected void configure() {
+          binder().requireExactBindingAnnotations();
+          bindConstant().annotatedWith(Red.class).to("foo");
+          bind(RedFoo.class);
+        }
+      }).getInstance(RedFoo.class);
+  }
+
+  public void testRequireExactAnnotationsRequireAllOptionals() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        @Override
+        protected void configure() {
+          binder().requireExactBindingAnnotations();
+          bindConstant().annotatedWith(Color.class).to("foo");
+          bind(ColorFoo.class);
+        }
+      });
+      fail();
+    } catch (CreationException expected) {
+ assertContains(expected.getMessage(), "No implementation for java.lang.String annotated with",
+          "BindingAnnotationTest$Color",
+ "at " + BindingAnnotationTest.class.getName(), ".configure(BindingAnnotationTest.java:");
+    }
+  }

   public void testAnnotationWithValueThatDoesntMatch() {
     try {
       Guice.createInjector(new AbstractModule() {
+        @Override
         protected void configure() {
           bindConstant().annotatedWith(createBlue(6)).to("six");
           bind(String.class).toInstance("bar");
-          bind(Foo.class);
+          bind(BlueFoo.class);
         }
       });
       fail();
@@ -60,15 +108,39 @@
     }
   }

-  static class Foo {
+  static class BlueFoo {
     @Inject @Blue(5) String s;
   }
+
+  static class RedFoo {
+    @Inject @Red String s;
+  }
+
+  static class ColorFoo {
+    @Inject @Color(b=2) String s;
+  }

   @Retention(RUNTIME)
   @BindingAnnotation
   @interface Blue {
     int value();
   }
+
+  @Retention(RUNTIME)
+  @BindingAnnotation
+  @interface Red {
+    int r() default 42;
+    int g() default 42;
+    int b() default 42;
+  }
+
+  @Retention(RUNTIME)
+  @BindingAnnotation
+  @interface Color {
+    int r() default 0;
+    int g() default 0;
+    int b();
+  }

   public Blue createBlue(final int value) {
     return new Blue() {
=======================================
--- /core/test/com/google/inject/KeyTest.java   Thu Jul  7 17:34:16 2011
+++ /core/test/com/google/inject/KeyTest.java   Thu May 16 11:00:54 2013
@@ -203,4 +203,47 @@
class HasTypeParameters<A, B extends List<A> & Runnable, C extends Runnable> {
     A a; B b; C c;
   }
+
+  public void testKeysWithDefaultAnnotations() {
+ AllDefaults allDefaults = HasAnnotations.class.getAnnotation(AllDefaults.class); + assertEquals(Key.get(Foo.class, allDefaults), Key.get(Foo.class, AllDefaults.class));
+
+    Marker marker = HasAnnotations.class.getAnnotation(Marker.class);
+ assertEquals(Key.get(Foo.class, marker), Key.get(Foo.class, Marker.class));
+
+    Key<?> noDefaults = Key.get(Foo.class, NoDefaults.class);
+    assertNull(noDefaults.getAnnotation());
+    assertEquals(NoDefaults.class, noDefaults.getAnnotationType());
+
+    Key<?> someDefaults = Key.get(Foo.class, SomeDefaults.class);
+    assertNull(someDefaults.getAnnotation());
+    assertEquals(SomeDefaults.class, someDefaults.getAnnotationType());
+  }
+
+  @Retention(RUNTIME)
+  @BindingAnnotation @interface AllDefaults {
+    int v1() default 1;
+    String v2() default "foo";
+  }
+
+  @Retention(RUNTIME)
+  @BindingAnnotation @interface SomeDefaults {
+    int v1() default 1;
+    String v2() default "foo";
+    Class<?> clazz();
+  }
+
+  @Retention(RUNTIME)
+  @BindingAnnotation @interface NoDefaults {
+    int value();
+  }
+
+  @Retention(RUNTIME)
+  @BindingAnnotation @interface Marker {
+  }
+
+  @AllDefaults
+  @Marker
+  class HasAnnotations {}
+
 }
=======================================
--- /extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java Wed May 15 19:11:47 2013 +++ /extensions/assistedinject/src/com/google/inject/assistedinject/FactoryProvider2.java Thu May 16 11:00:54 2013
@@ -41,6 +41,7 @@
 import com.google.inject.internal.BytecodeGen;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
+import com.google.inject.internal.UniqueAnnotations;
 import com.google.inject.internal.util.Classes;
 import com.google.inject.spi.BindingTargetVisitor;
 import com.google.inject.spi.Dependency;
@@ -576,7 +577,7 @@
     final Key<?> returnType = data.returnType;

     // We ignore any pre-existing binding annotation.
- final Key<?> assistedReturnType = Key.get(returnType.getTypeLiteral(), Assisted.class); + final Key<?> returnKey = Key.get(returnType.getTypeLiteral(), UniqueAnnotations.create());

     Module assistedModule = new AbstractModule() {
@Override @SuppressWarnings("unchecked") // raw keys are necessary for the args array and return value
@@ -601,7 +602,7 @@
         // but if it isn't, we'll end up throwing a fairly good error
         // message for the user.
         if(constructor != null) {
-          binder.bind(assistedReturnType)
+          binder.bind(returnKey)
.toConstructor(constructor, (TypeLiteral)data.implementationType) .in(Scopes.NO_SCOPE); // make sure we erase any scope on the implementation type
         }
@@ -609,7 +610,7 @@
     };

     Injector forCreate = injector.createChildInjector(assistedModule);
-    Binding binding = forCreate.getBinding(assistedReturnType);
+    Binding binding = forCreate.getBinding(returnKey);
// If we have providers cached in data, cache the binding for future optimizations.
     if(data.optimized) {
       data.cachedBinding = binding;
=======================================
--- /extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java Sun May 27 10:38:30 2012 +++ /extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java Thu May 16 11:00:54 2013
@@ -1040,5 +1040,31 @@
Color getColor() { return injector.getInstance(Key.get(Color.class, FactoryProvider2.DEFAULT_ANNOTATION)); }
   }

+  public void testReturnValueMatchesParamValue() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      @Override
+      public void configure() {
+        install(new FactoryModuleBuilder().build(Delegater.Factory.class));
+      }
+    });
+    Delegater delegate = new Delegater();
+ Delegater user = injector.getInstance(Delegater.Factory.class).create(delegate);
+    assertSame(delegate, user.delegate);
+  }
+
+  static class Delegater {
+    interface Factory {
+      Delegater create(Delegater delegate);
+    }
+
+    private final Delegater delegate;

+    @Inject Delegater(@Assisted Delegater delegater) {
+      this.delegate = delegater;
+    }
+
+    Delegater() {
+      this.delegate = null;
+    }
+  }
 }

--
You received this message because you are subscribed to the Google Groups 
"google-guice-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to google-guice-dev+unsubscr...@googlegroups.com.
To post to this group, send email to google-guice-dev@googlegroups.com.
Visit this group at http://groups.google.com/group/google-guice-dev?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to