Mike Ward's inSubpackage feature for issue 75.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@531 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/matcher/Matchers.java b/src/com/google/inject/matcher/Matchers.java
index d4643ea..32c1a2e 100644
--- a/src/com/google/inject/matcher/Matchers.java
+++ b/src/com/google/inject/matcher/Matchers.java
@@ -16,13 +16,14 @@
 
 package com.google.inject.matcher;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
 import java.io.Serializable;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
-import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * Matcher implementations. Supports matching classes and methods.
@@ -90,10 +91,8 @@
   private static void checkForRuntimeRetention(
       Class<? extends Annotation> annotationType) {
     Retention retention = annotationType.getAnnotation(Retention.class);
-    if (retention == null || retention.value() != RetentionPolicy.RUNTIME) {
-      throw new IllegalArgumentException("Annotation "
-          + annotationType.getSimpleName() + " is missing RUNTIME retention");
-    }
+    checkArgument(retention != null && retention.value() == RetentionPolicy.RUNTIME,
+        "Annotation " + annotationType.getSimpleName() + " is missing RUNTIME retention");
   }
 
   /**
@@ -270,7 +269,8 @@
   }
 
   /**
-   * Returns a matcher which matches classes in the given package.
+   * Returns a matcher which matches classes in the given package. Packages are specific to their
+   * classloader, so classes with the same package name may not have the same package at runtime.
    */
   public static Matcher<Class> inPackage(final Package targetPackage) {
     return new InPackage(targetPackage);
@@ -308,6 +308,41 @@
   }
 
   /**
+   * Returns a matcher which matches classes in the given package and its subpackages. Unlike
+   * {@link #inPackage(Package) inPackage()}, this matches classes from any classloader.
+   */
+  public static Matcher<Class> inSubpackage(final String targetPackageName) {
+    return new InSubpackage(targetPackageName);
+  }
+
+  private static class InSubpackage extends AbstractMatcher<Class> implements Serializable {
+    private final String targetPackageName;
+
+    public InSubpackage(String targetPackageName) {
+      this.targetPackageName = targetPackageName;
+    }
+
+    public boolean matches(Class c) {
+      String classPackageName = c.getPackage().getName();
+      return classPackageName.equals(targetPackageName)
+          || classPackageName.startsWith(targetPackageName + ".");
+    }
+
+    @Override public boolean equals(Object other) {
+      return other instanceof InSubpackage
+          && ((InSubpackage) other).targetPackageName.equals(targetPackageName);
+    }
+
+    @Override public int hashCode() {
+      return 37 * targetPackageName.hashCode();
+    }
+
+    @Override public String toString() {
+      return "inSubpackage(" + targetPackageName + ")";
+    }
+  }
+
+  /**
    * Returns a matcher which matches methods with matching return types.
    */
   public static Matcher<Method> returns(
@@ -339,4 +374,4 @@
       return "returns(" + returnType + ")";
     }
   }
-}
\ No newline at end of file
+}
diff --git a/test/com/google/inject/matcher/MatcherTest.java b/test/com/google/inject/matcher/MatcherTest.java
index bc6a3d8..8b3ac98 100644
--- a/test/com/google/inject/matcher/MatcherTest.java
+++ b/test/com/google/inject/matcher/MatcherTest.java
@@ -18,16 +18,23 @@
 
 import static com.google.inject.Asserts.assertEqualWhenReserialized;
 import static com.google.inject.Asserts.assertEqualsBothWays;
-import static com.google.inject.matcher.Matchers.*;
+import static com.google.inject.matcher.Matchers.annotatedWith;
+import static com.google.inject.matcher.Matchers.any;
+import static com.google.inject.matcher.Matchers.identicalTo;
+import static com.google.inject.matcher.Matchers.inPackage;
+import static com.google.inject.matcher.Matchers.inSubpackage;
+import static com.google.inject.matcher.Matchers.not;
+import static com.google.inject.matcher.Matchers.only;
+import static com.google.inject.matcher.Matchers.returns;
+import static com.google.inject.matcher.Matchers.subclassesOf;
 import com.google.inject.name.Named;
 import com.google.inject.name.Names;
-import junit.framework.TestCase;
-
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Method;
 import java.util.AbstractList;
+import junit.framework.TestCase;
 
 /**
  * @author crazybob@google.com (Bob Lee)
@@ -115,6 +122,17 @@
     assertFalse(inPackage(matchersPackage).equals(inPackage(Object.class.getPackage())));
   }
 
+  public void testInSubpackage() {
+    String stringPackageName = String.class.getPackage().getName();
+    assertEquals("inSubpackage(java.lang)", inSubpackage(stringPackageName).toString());
+    assertTrue(inSubpackage(stringPackageName).matches(Object.class));
+    assertTrue(inSubpackage(stringPackageName).matches(Method.class));
+    assertFalse(inSubpackage(stringPackageName).matches(Matchers.class));
+    assertFalse(inSubpackage("jav").matches(Object.class));
+    assertEqualsBothWays(inSubpackage(stringPackageName), inSubpackage(stringPackageName));
+    assertFalse(inSubpackage(stringPackageName).equals(inSubpackage(Matchers.class.getPackage().getName())));
+  }
+
   public void testReturns() throws NoSuchMethodException {
     Matcher<Method> predicate = returns(only(String.class));
     assertTrue(predicate.matches(
@@ -134,6 +152,7 @@
     assertEqualWhenReserialized(only("foo"));
     assertEqualWhenReserialized(identicalTo(Object.class));
     assertEqualWhenReserialized(inPackage(String.class.getPackage()));
+    assertEqualWhenReserialized(inSubpackage(String.class.getPackage().getName()));
     assertEqualWhenReserialized(returns(any()));
     assertEqualWhenReserialized(subclassesOf(AbstractList.class));
     assertEqualWhenReserialized(only("a").or(only("b")));