merge in oc-release history after reset to master
diff --git a/README.version b/README.version
index 41e2c6f..fd9d421 100644
--- a/README.version
+++ b/README.version
@@ -5,5 +5,4 @@
 Local Changes:
     Remove DisableOnDebug (new in 4.12) as it is not supported on Android
     Remove support for stuck threads
-    Revert matchers back to 4.10 to compile against Hamcrest 1.1
     Extra generic type information to aid certain javacs.
diff --git a/src/main/java/org/junit/Assume.java b/src/main/java/org/junit/Assume.java
index c96ff99..b7687f7 100644
--- a/src/main/java/org/junit/Assume.java
+++ b/src/main/java/org/junit/Assume.java
@@ -1,12 +1,12 @@
 package org.junit;
 
 import static java.util.Arrays.asList;
+import static org.hamcrest.CoreMatchers.everyItem;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
 
 import org.hamcrest.Matcher;
-import org.junit.internal.matchers.Each;
 
 /**
  * A set of methods useful for stating assumptions about the conditions in which a test is meaningful.
@@ -66,13 +66,12 @@
         assumeTrue(message, !b);
     }
 
-	/**
-	 * If called with one or more null elements in <code>objects</code>, the test will halt and be ignored.
-	 * @param objects
-	 */
-	public static void assumeNotNull(Object... objects) {
-		assumeThat(asList(objects), Each.each(notNullValue()));
-	}
+    /**
+     * If called with one or more null elements in <code>objects</code>, the test will halt and be ignored.
+     */
+    public static void assumeNotNull(Object... objects) {
+        assumeThat(asList(objects), everyItem(notNullValue()));
+    }
 
     /**
      * Call to assume that <code>actual</code> satisfies the condition specified by <code>matcher</code>.
diff --git a/src/main/java/org/junit/internal/matchers/CombinableMatcher.java b/src/main/java/org/junit/internal/matchers/CombinableMatcher.java
deleted file mode 100644
index e9e6947..0000000
--- a/src/main/java/org/junit/internal/matchers/CombinableMatcher.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.junit.internal.matchers;
-
-import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.anyOf;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-
-public class CombinableMatcher<T> extends BaseMatcher<T> {
-
-	private final Matcher<? extends T> fMatcher;
-
-	public CombinableMatcher(Matcher<? extends T> matcher) {
-		fMatcher= matcher;
-	}
-
-	public boolean matches(Object item) {
-		return fMatcher.matches(item);
-	}
-
-	public void describeTo(Description description) {
-		description.appendDescriptionOf(fMatcher);
-	}
-	
-	@SuppressWarnings("unchecked")
-	public CombinableMatcher<T> and(Matcher<? extends T> matcher) {
-		return new CombinableMatcher<T>(allOf(matcher, fMatcher));
-	}
-
-	@SuppressWarnings("unchecked")
-	public CombinableMatcher<T> or(Matcher<? extends T> matcher) {
-		return new CombinableMatcher<T>(anyOf(matcher, fMatcher));
-	}
-}
\ No newline at end of file
diff --git a/src/main/java/org/junit/internal/matchers/Each.java b/src/main/java/org/junit/internal/matchers/Each.java
deleted file mode 100644
index 527db3b..0000000
--- a/src/main/java/org/junit/internal/matchers/Each.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.junit.internal.matchers;
-
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.internal.matchers.IsCollectionContaining.hasItem;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-
-public class Each {
-	public static <T> Matcher<Iterable<T>> each(final Matcher<T> individual) {
-		final Matcher<Iterable<T>> allItemsAre = not(hasItem(not(individual)));
-		
-		return new BaseMatcher<Iterable<T>>() {
-			public boolean matches(Object item) {
-				return allItemsAre.matches(item);
-			}
-			
-			public void describeTo(Description description) {
-				description.appendText("each ");
-				individual.describeTo(description);
-			}
-		};
-	}
-}
diff --git a/src/main/java/org/junit/internal/matchers/IsCollectionContaining.java b/src/main/java/org/junit/internal/matchers/IsCollectionContaining.java
deleted file mode 100644
index 4436a83..0000000
--- a/src/main/java/org/junit/internal/matchers/IsCollectionContaining.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.junit.internal.matchers;
-
-import static org.hamcrest.core.AllOf.allOf;
-import static org.hamcrest.core.IsEqual.equalTo;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-import org.hamcrest.Description;
-import org.hamcrest.Factory;
-import org.hamcrest.Matcher;
-
-// Copied (hopefully temporarily) from hamcrest-library
-public class IsCollectionContaining<T> extends TypeSafeMatcher<Iterable<T>> {
-    private final Matcher<? extends T> elementMatcher;
-
-    public IsCollectionContaining(Matcher<? extends T> elementMatcher) {
-        this.elementMatcher = elementMatcher;
-    }
-
-    @Override
-	public boolean matchesSafely(Iterable<T> collection) {
-        for (T item : collection) {
-            if (elementMatcher.matches(item)){
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public void describeTo(Description description) {
-        description
-        	.appendText("a collection containing ")
-        	.appendDescriptionOf(elementMatcher);
-    }
-
-    @Factory
-    public static <T> Matcher<Iterable<T>> hasItem(Matcher<? extends T> elementMatcher) {
-        return new IsCollectionContaining<T>(elementMatcher);
-    }
-
-    @Factory
-    public static <T> Matcher<Iterable<T>> hasItem(T element) {
-        return hasItem(equalTo(element));
-    }
-
-    @Factory
-    public static <T> Matcher<Iterable<T>> hasItems(Matcher<? extends T>... elementMatchers) {
-        Collection<Matcher<? extends Iterable<T>>> all
-                = new ArrayList<Matcher<? extends Iterable<T>>>(elementMatchers.length);
-        for (Matcher<? extends T> elementMatcher : elementMatchers) {
-            all.add(hasItem(elementMatcher));
-        }
-        return allOf(all);
-    }
-
-    @Factory
-    public static <T> Matcher<Iterable<T>> hasItems(T... elements) {
-        Collection<Matcher<? extends Iterable<T>>> all
-                = new ArrayList<Matcher<? extends Iterable<T>>>(elements.length);
-        for (T element : elements) {
-            all.add(hasItem(element));
-        }
-        return allOf(all);
-    }
-
-}
diff --git a/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java b/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java
new file mode 100644
index 0000000..5d45ba3
--- /dev/null
+++ b/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java
@@ -0,0 +1,56 @@
+package org.junit.internal.matchers;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+
+/**
+ * A matcher that delegates to throwableMatcher and in addition appends the
+ * stacktrace of the actual Throwable in case of a mismatch.
+ */
+public class StacktracePrintingMatcher<T extends Throwable> extends
+        org.hamcrest.TypeSafeMatcher<T> {
+
+    private final Matcher<T> throwableMatcher;
+
+    public StacktracePrintingMatcher(Matcher<T> throwableMatcher) {
+        this.throwableMatcher = throwableMatcher;
+    }
+
+    public void describeTo(Description description) {
+        throwableMatcher.describeTo(description);
+    }
+
+    @Override
+    protected boolean matchesSafely(T item) {
+        return throwableMatcher.matches(item);
+    }
+
+    @Override
+    protected void describeMismatchSafely(T item, Description description) {
+        throwableMatcher.describeMismatch(item, description);
+        description.appendText("\nStacktrace was: ");
+        description.appendText(readStacktrace(item));
+    }
+
+    private String readStacktrace(Throwable throwable) {
+        StringWriter stringWriter = new StringWriter();
+        throwable.printStackTrace(new PrintWriter(stringWriter));
+        return stringWriter.toString();
+    }
+
+    @Factory
+    public static <T extends Throwable> Matcher<T> isThrowable(
+            Matcher<T> throwableMatcher) {
+        return new StacktracePrintingMatcher<T>(throwableMatcher);
+    }
+
+    @Factory
+    public static <T extends Exception> Matcher<T> isException(
+            Matcher<T> exceptionMatcher) {
+        return new StacktracePrintingMatcher<T>(exceptionMatcher);
+    }
+}
diff --git a/src/main/java/org/junit/internal/matchers/StringContains.java b/src/main/java/org/junit/internal/matchers/StringContains.java
deleted file mode 100644
index e5f5334..0000000
--- a/src/main/java/org/junit/internal/matchers/StringContains.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*  Copyright (c) 2000-2006 hamcrest.org
- */
-package org.junit.internal.matchers;
-
-import org.hamcrest.Factory;
-import org.hamcrest.Matcher;
-
-/**
- * Tests if the argument is a string that contains a substring.
- */
-public class StringContains extends SubstringMatcher {
-    public StringContains(String substring) {
-        super(substring);
-    }
-
-    @Override
-	protected boolean evalSubstringOf(String s) {
-        return s.indexOf(substring) >= 0;
-    }
-
-    @Override
-	protected String relationship() {
-        return "containing";
-    }
-
-    @Factory
-    public static Matcher<String> containsString(String substring) {
-        return new StringContains(substring);
-    }
-
-}
\ No newline at end of file
diff --git a/src/main/java/org/junit/internal/matchers/SubstringMatcher.java b/src/main/java/org/junit/internal/matchers/SubstringMatcher.java
deleted file mode 100644
index 1c65240..0000000
--- a/src/main/java/org/junit/internal/matchers/SubstringMatcher.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.junit.internal.matchers;
-
-import org.hamcrest.Description;
-
-public abstract class SubstringMatcher extends TypeSafeMatcher<String> {
-
-    protected final String substring;
-
-    protected SubstringMatcher(final String substring) {
-        this.substring = substring;
-    }
-
-    @Override
-	public boolean matchesSafely(String item) {
-        return evalSubstringOf(item);
-    }
-
-    public void describeTo(Description description) {
-        description.appendText("a string ")
-                .appendText(relationship())
-                .appendText(" ")
-                .appendValue(substring);
-    }
-
-    protected abstract boolean evalSubstringOf(String string);
-
-    protected abstract String relationship();
-}
\ No newline at end of file
diff --git a/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java b/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java
new file mode 100644
index 0000000..22ce8bd
--- /dev/null
+++ b/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java
@@ -0,0 +1,50 @@
+package org.junit.internal.matchers;
+
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * A matcher that applies a delegate matcher to the cause of the current Throwable, returning the result of that
+ * match.
+ *
+ * @param <T> the type of the throwable being matched
+ */
+public class ThrowableCauseMatcher<T extends Throwable> extends
+        TypeSafeMatcher<T> {
+
+    private final Matcher<? extends Throwable> causeMatcher;
+
+    public ThrowableCauseMatcher(Matcher<? extends Throwable> causeMatcher) {
+        this.causeMatcher = causeMatcher;
+    }
+
+    public void describeTo(Description description) {
+        description.appendText("exception with cause ");
+        description.appendDescriptionOf(causeMatcher);
+    }
+
+    @Override
+    protected boolean matchesSafely(T item) {
+        return causeMatcher.matches(item.getCause());
+    }
+
+    @Override
+    protected void describeMismatchSafely(T item, Description description) {
+        description.appendText("cause ");
+        causeMatcher.describeMismatch(item.getCause(), description);
+    }
+
+    /**
+     * Returns a matcher that verifies that the outer exception has a cause for which the supplied matcher
+     * evaluates to true.
+     *
+     * @param matcher to apply to the cause of the outer exception
+     * @param <T> type of the outer exception
+     */
+    @Factory
+    public static <T extends Throwable> Matcher<T> hasCause(final Matcher<? extends Throwable> matcher) {
+        return new ThrowableCauseMatcher<T>(matcher);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java b/src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java
new file mode 100644
index 0000000..74386a8
--- /dev/null
+++ b/src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java
@@ -0,0 +1,37 @@
+package org.junit.internal.matchers;
+
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class ThrowableMessageMatcher<T extends Throwable> extends
+        TypeSafeMatcher<T> {
+
+    private final Matcher<String> matcher;
+
+    public ThrowableMessageMatcher(Matcher<String> matcher) {
+        this.matcher = matcher;
+    }
+
+    public void describeTo(Description description) {
+        description.appendText("exception with message ");
+        description.appendDescriptionOf(matcher);
+    }
+
+    @Override
+    protected boolean matchesSafely(T item) {
+        return matcher.matches(item.getMessage());
+    }
+
+    @Override
+    protected void describeMismatchSafely(T item, Description description) {
+        description.appendText("message ");
+        matcher.describeMismatch(item.getMessage(), description);
+    }
+
+    @Factory
+    public static <T extends Throwable> Matcher<T> hasMessage(final Matcher<String> matcher) {
+        return new ThrowableMessageMatcher<T>(matcher);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java b/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java
index 794a174..4e2cc12 100644
--- a/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java
+++ b/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java
@@ -3,13 +3,16 @@
 import java.lang.reflect.Method;
 
 import org.hamcrest.BaseMatcher;
+import org.junit.internal.MethodSorter;
 
 /**
  * Convenient base class for Matchers that require a non-null value of a specific type.
  * This simply implements the null check, checks the type and then casts.
  *
  * @author Joe Walnes
+ * @deprecated Please use {@link org.hamcrest.TypeSafeMatcher}.
  */
+@Deprecated
 public abstract class TypeSafeMatcher<T> extends BaseMatcher<T> {
 
     private Class<?> expectedType;
@@ -23,27 +26,27 @@
     protected TypeSafeMatcher() {
         expectedType = findExpectedType(getClass());
     }
-    
+
     private static Class<?> findExpectedType(Class<?> fromClass) {
         for (Class<?> c = fromClass; c != Object.class; c = c.getSuperclass()) {
-            for (Method method : c.getDeclaredMethods()) {
+            for (Method method : MethodSorter.getDeclaredMethods(c)) {
                 if (isMatchesSafelyMethod(method)) {
                     return method.getParameterTypes()[0];
                 }
             }
         }
-        
+
         throw new Error("Cannot determine correct type for matchesSafely() method.");
     }
-    
+
     private static boolean isMatchesSafelyMethod(Method method) {
-        return method.getName().equals("matchesSafely") 
-            && method.getParameterTypes().length == 1
-            && !method.isSynthetic(); 
+        return method.getName().equals("matchesSafely")
+                && method.getParameterTypes().length == 1
+                && !method.isSynthetic();
     }
-    
+
     protected TypeSafeMatcher(Class<T> expectedType) {
-    	this.expectedType = expectedType;
+        this.expectedType = expectedType;
     }
 
     /**
diff --git a/src/main/java/org/junit/matchers/JUnitMatchers.java b/src/main/java/org/junit/matchers/JUnitMatchers.java
index 847a347..5bb48d7 100644
--- a/src/main/java/org/junit/matchers/JUnitMatchers.java
+++ b/src/main/java/org/junit/matchers/JUnitMatchers.java
@@ -1,83 +1,113 @@
 package org.junit.matchers;
 
+import org.hamcrest.CoreMatchers;
 import org.hamcrest.Matcher;
-import org.junit.internal.matchers.CombinableMatcher;
-import org.junit.internal.matchers.Each;
-import org.junit.internal.matchers.IsCollectionContaining;
-import org.junit.internal.matchers.StringContains;
+import org.hamcrest.core.CombinableMatcher.CombinableBothMatcher;
+import org.hamcrest.core.CombinableMatcher.CombinableEitherMatcher;
+import org.junit.internal.matchers.StacktracePrintingMatcher;
 
 /**
  * Convenience import class: these are useful matchers for use with the assertThat method, but they are
  * not currently included in the basic CoreMatchers class from hamcrest.
+ *
+ * @since 4.4
  */
 public class JUnitMatchers {
-	/**
-	 * @param element
-	 * @return A matcher matching any collection containing element
-	 */
-	public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItem(T element) {
-		return IsCollectionContaining.hasItem(element);
-	}
+    /**
+     * @return A matcher matching any collection containing element
+     * @deprecated Please use {@link CoreMatchers#hasItem(Object)} instead.
+     */
+    @Deprecated
+    public static <T> Matcher<Iterable<? super T>> hasItem(T element) {
+        return CoreMatchers.hasItem(element);
+    }
 
-	/**
-	 * @param elementMatcher
-	 * @return A matcher matching any collection containing an element matching elementMatcher
-	 */
-	public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItem(org.hamcrest.Matcher<? extends T> elementMatcher) {
-		return IsCollectionContaining.<T>hasItem(elementMatcher);
-	}
+    /**
+     * @return A matcher matching any collection containing an element matching elementMatcher
+     * @deprecated Please use {@link CoreMatchers#hasItem(Matcher)} instead.
+     */
+    @Deprecated
+    public static <T> Matcher<Iterable<? super T>> hasItem(Matcher<? super T> elementMatcher) {
+        return CoreMatchers.<T>hasItem(elementMatcher);
+    }
 
-	/**
-	 * @param elements
-	 * @return A matcher matching any collection containing every element in elements
-	 */
-	public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(T... elements) {
-		return IsCollectionContaining.hasItems(elements);
-	}
+    /**
+     * @return A matcher matching any collection containing every element in elements
+     * @deprecated Please use {@link CoreMatchers#hasItems(Object...)} instead.
+     */
+    @Deprecated
+    public static <T> Matcher<Iterable<T>> hasItems(T... elements) {
+        return CoreMatchers.hasItems(elements);
+    }
 
-	/**
-	 * @param elementMatchers
-	 * @return A matcher matching any collection containing at least one element that matches 
-	 *         each matcher in elementMatcher (this may be one element matching all matchers,
-	 *         or different elements matching each matcher)
-	 */
-	public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(org.hamcrest.Matcher<? extends T>... elementMatchers) {
-		return IsCollectionContaining.<T>hasItems(elementMatchers);
-	}
+    /**
+     * @return A matcher matching any collection containing at least one element that matches
+     *         each matcher in elementMatcher (this may be one element matching all matchers,
+     *         or different elements matching each matcher)
+     * @deprecated Please use {@link CoreMatchers#hasItems(Matcher...)} instead.
+     */
+    @Deprecated
+    public static <T> Matcher<Iterable<T>> hasItems(Matcher<? super T>... elementMatchers) {
+        return CoreMatchers.<T>hasItems(elementMatchers);
+    }
 
-	/**
-	 * @param elementMatcher
-	 * @return A matcher matching any collection in which every element matches elementMatcher
-	 */
-	public static <T> Matcher<Iterable<T>> everyItem(final Matcher<T> elementMatcher) {
-		return Each.each(elementMatcher);
-	}
+    /**
+     * @return A matcher matching any collection in which every element matches elementMatcher
+     * @deprecated Please use {@link CoreMatchers#everyItem(Matcher)} instead.
+     */
+    @Deprecated
+    public static <T> Matcher<Iterable<T>> everyItem(final Matcher<T> elementMatcher) {
+        return CoreMatchers.everyItem((Matcher) elementMatcher);
+    }
 
-	/**
-	 * @param substring
-	 * @return a matcher matching any string that contains substring
-	 */
-	public static org.hamcrest.Matcher<java.lang.String> containsString(java.lang.String substring) {
-		return StringContains.containsString(substring);
-	}
-	
-	/**
-	 * This is useful for fluently combining matchers that must both pass.  For example:
-	 * <pre>
-	 *   assertThat(string, both(containsString("a")).and(containsString("b")));
-	 * </pre>
-	 */
-	public static <T> CombinableMatcher<T> both(Matcher<T> matcher) {
-		return new CombinableMatcher<T>(matcher);
-	}
-	
-	/**
-	 * This is useful for fluently combining matchers where either may pass, for example:
-	 * <pre>
-	 *   assertThat(string, either(containsString("a")).or(containsString("b")));
-	 * </pre>
-	 */
-	public static <T> CombinableMatcher<T> either(Matcher<T> matcher) {
-		return new CombinableMatcher<T>(matcher);
-	}
+    /**
+     * @return a matcher matching any string that contains substring
+     * @deprecated Please use {@link CoreMatchers#containsString(String)} instead.
+     */
+    @Deprecated
+    public static Matcher<java.lang.String> containsString(java.lang.String substring) {
+        return CoreMatchers.containsString(substring);
+    }
+
+    /**
+     * This is useful for fluently combining matchers that must both pass.  For example:
+     * <pre>
+     *   assertThat(string, both(containsString("a")).and(containsString("b")));
+     * </pre>
+     *
+     * @deprecated Please use {@link CoreMatchers#both(Matcher)} instead.
+     */
+    @Deprecated
+    public static <T> CombinableBothMatcher<T> both(Matcher<? super T> matcher) {
+        return CoreMatchers.both(matcher);
+    }
+
+    /**
+     * This is useful for fluently combining matchers where either may pass, for example:
+     * <pre>
+     *   assertThat(string, either(containsString("a")).or(containsString("b")));
+     * </pre>
+     *
+     * @deprecated Please use {@link CoreMatchers#either(Matcher)} instead.
+     */
+    @Deprecated
+    public static <T> CombinableEitherMatcher<T> either(Matcher<? super T> matcher) {
+        return CoreMatchers.either(matcher);
+    }
+
+    /**
+     * @return A matcher that delegates to throwableMatcher and in addition
+     *         appends the stacktrace of the actual Throwable in case of a mismatch.
+     */
+    public static <T extends Throwable> Matcher<T> isThrowable(Matcher<T> throwableMatcher) {
+        return StacktracePrintingMatcher.isThrowable(throwableMatcher);
+    }
+
+    /**
+     * @return A matcher that delegates to exceptionMatcher and in addition
+     *         appends the stacktrace of the actual Exception in case of a mismatch.
+     */
+    public static <T extends Exception> Matcher<T> isException(Matcher<T> exceptionMatcher) {
+        return StacktracePrintingMatcher.isException(exceptionMatcher);
+    }
 }
diff --git a/src/main/java/org/junit/rules/ExpectedException.java b/src/main/java/org/junit/rules/ExpectedException.java
index bac2fba..4d61712 100644
--- a/src/main/java/org/junit/rules/ExpectedException.java
+++ b/src/main/java/org/junit/rules/ExpectedException.java
@@ -1,136 +1,270 @@
 package org.junit.rules;
 
+import static java.lang.String.format;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.matchers.JUnitMatchers.both;
-import static org.junit.matchers.JUnitMatchers.containsString;
-import org.hamcrest.Description;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.internal.matchers.ThrowableCauseMatcher.hasCause;
+import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage;
+
 import org.hamcrest.Matcher;
 import org.hamcrest.StringDescription;
-import org.junit.Assert;
-import org.junit.internal.matchers.TypeSafeMatcher;
+import org.junit.AssumptionViolatedException;
 import org.junit.runners.model.Statement;
 
 /**
- * The ExpectedException Rule allows in-test specification of expected exception
- * types and messages:
+ * The {@code ExpectedException} rule allows you to verify that your code
+ * throws a specific exception.
+ *
+ * <h3>Usage</h3>
+ *
+ * <pre> public class SimpleExpectedExceptionTest {
+ *     &#064;Rule
+ *     public ExpectedException thrown= ExpectedException.none();
+ *
+ *     &#064;Test
+ *     public void throwsNothing() {
+ *         // no exception expected, none thrown: passes.
+ *     }
+ *
+ *     &#064;Test
+ *     public void throwsExceptionWithSpecificType() {
+ *         thrown.expect(NullPointerException.class);
+ *         throw new NullPointerException();
+ *     }
+ * }</pre>
  * 
- * <pre>
- * // These tests all pass.
- * public static class HasExpectedException {
- * 	&#064;Rule
- * 	public ExpectedException thrown= ExpectedException.none();
- * 
- * 	&#064;Test
- * 	public void throwsNothing() {
- *    // no exception expected, none thrown: passes.
- * 	}
- * 
- * 	&#064;Test
- * 	public void throwsNullPointerException() {
- * 		thrown.expect(NullPointerException.class);
- * 		throw new NullPointerException();
- * 	}
- * 
- * 	&#064;Test
- * 	public void throwsNullPointerExceptionWithMessage() {
- * 		thrown.expect(NullPointerException.class);
- * 		thrown.expectMessage(&quot;happened?&quot;);
- * 		thrown.expectMessage(startsWith(&quot;What&quot;));
- * 		throw new NullPointerException(&quot;What happened?&quot;);
- * 	}
- * }
- * </pre>
+ * <p>
+ * You have to add the {@code ExpectedException} rule to your test.
+ * This doesn't affect your existing tests (see {@code throwsNothing()}).
+ * After specifiying the type of the expected exception your test is
+ * successful when such an exception is thrown and it fails if a
+ * different or no exception is thrown.
+ *
+ * <p>
+ * Instead of specifying the exception's type you can characterize the
+ * expected exception based on other criterias, too:
+ *
+ * <ul>
+ *   <li>The exception's message contains a specific text: {@link #expectMessage(String)}</li>
+ *   <li>The exception's message complies with a Hamcrest matcher: {@link #expectMessage(Matcher)}</li>
+ *   <li>The exception's cause complies with a Hamcrest matcher: {@link #expectCause(Matcher)}</li>
+ *   <li>The exception itself complies with a Hamcrest matcher: {@link #expect(Matcher)}</li>
+ * </ul>
+ *
+ * <p>
+ * You can combine any of the presented expect-methods. The test is
+ * successful if all specifications are met.
+ * <pre> &#064;Test
+ * public void throwsException() {
+ *     thrown.expect(NullPointerException.class);
+ *     thrown.expectMessage(&quot;happened&quot;);
+ *     throw new NullPointerException(&quot;What happened?&quot;);
+ * }</pre>
+ *
+ * <h3>AssumptionViolatedExceptions</h3>
+ * <p>
+ * JUnit uses {@link AssumptionViolatedException}s for indicating that a test
+ * provides no useful information. (See {@link org.junit.Assume} for more
+ * information.) You have to call {@code assume} methods before you set
+ * expectations of the {@code ExpectedException} rule. In this case the rule
+ * will not handle consume the exceptions and it can be handled by the
+ * framework. E.g. the following test is ignored by JUnit's default runner.
+ *
+ * <pre> &#064;Test
+ * public void ignoredBecauseOfFailedAssumption() {
+ *     assumeTrue(false); // throws AssumptionViolatedException
+ *     thrown.expect(NullPointerException.class);
+ * }</pre>
+ *
+ * <h3>AssertionErrors</h3>
+ *
+ * <p>
+ * JUnit uses {@link AssertionError}s for indicating that a test is failing. You
+ * have to call {@code assert} methods before you set expectations of the
+ * {@code ExpectedException} rule, if they should be handled by the framework.
+ * E.g. the following test fails because of the {@code assertTrue} statement.
+ *
+ * <pre> &#064;Test
+ * public void throwsUnhandled() {
+ *     assertTrue(false); // throws AssertionError
+ *     thrown.expect(NullPointerException.class);
+ * }</pre>
+ *
+ * <h3>Missing Exceptions</h3>
+ * <p>
+ * By default missing exceptions are reported with an error message
+ * like "Expected test to throw an instance of foo". You can configure a different
+ * message by means of {@link #reportMissingExceptionWithMessage(String)}. You
+ * can use a {@code %s} placeholder for the description of the expected
+ * exception. E.g. "Test doesn't throw %s." will fail with the error message
+ * "Test doesn't throw an instance of foo.".
+ *
+ * @since 4.7
  */
 public class ExpectedException implements TestRule {
-	/**
-	 * @return a Rule that expects no exception to be thrown
-	 * (identical to behavior without this Rule)
-	 */
-	public static ExpectedException none() {
-		return new ExpectedException();
-	}
+    /**
+     * Returns a {@linkplain TestRule rule} that expects no exception to
+     * be thrown (identical to behavior without this rule).
+     */
+    public static ExpectedException none() {
+        return new ExpectedException();
+    }
 
-	private Matcher<Object> fMatcher= null;
+    private final ExpectedExceptionMatcherBuilder matcherBuilder = new ExpectedExceptionMatcherBuilder();
 
-	private ExpectedException() {
-		
-	}
-	
-	public Statement apply(Statement base,
-			org.junit.runner.Description description) {
-		return new ExpectedExceptionStatement(base);
-	}
+    private String missingExceptionMessage= "Expected test to throw %s";
 
-	/**
-	 * Adds {@code matcher} to the list of requirements for any thrown exception.
-	 */
-	// Should be able to remove this suppression in some brave new hamcrest world.
-	@SuppressWarnings("unchecked")
-	public void expect(Matcher<?> matcher) {
-		if (fMatcher == null)
-			fMatcher= (Matcher<Object>) matcher;
-		else
-			fMatcher= both(fMatcher).and(matcher);
-	}
+    private ExpectedException() {
+    }
 
-	/**
-	 * Adds to the list of requirements for any thrown exception that it
-	 * should be an instance of {@code type}
-	 */
-	public void expect(Class<? extends Throwable> type) {
-		expect(instanceOf(type));
-	}
+    /**
+     * This method does nothing. Don't use it.
+     * @deprecated AssertionErrors are handled by default since JUnit 4.12. Just
+     *             like in JUnit &lt;= 4.10.
+     */
+    @Deprecated
+    public ExpectedException handleAssertionErrors() {
+        return this;
+    }
 
-	/**
-	 * Adds to the list of requirements for any thrown exception that it
-	 * should <em>contain</em> string {@code substring}
-	 */
-	public void expectMessage(String substring) {
-		expectMessage(containsString(substring));
-	}
+    /**
+     * This method does nothing. Don't use it.
+     * @deprecated AssumptionViolatedExceptions are handled by default since
+     *             JUnit 4.12. Just like in JUnit &lt;= 4.10.
+     */
+    @Deprecated
+    public ExpectedException handleAssumptionViolatedExceptions() {
+        return this;
+    }
 
-	/**
-	 * Adds {@code matcher} to the list of requirements for the message 
-	 * returned from any thrown exception.
-	 */
-	public void expectMessage(Matcher<String> matcher) {
-		expect(hasMessage(matcher));
-	}
+    /**
+     * Specifies the failure message for tests that are expected to throw 
+     * an exception but do not throw any. You can use a {@code %s} placeholder for
+     * the description of the expected exception. E.g. "Test doesn't throw %s."
+     * will fail with the error message
+     * "Test doesn't throw an instance of foo.".
+     *
+     * @param message exception detail message
+     * @return the rule itself
+     */
+    public ExpectedException reportMissingExceptionWithMessage(String message) {
+        missingExceptionMessage = message;
+        return this;
+    }
 
-	private class ExpectedExceptionStatement extends Statement {
-		private final Statement fNext;
+    public Statement apply(Statement base,
+            org.junit.runner.Description description) {
+        return new ExpectedExceptionStatement(base);
+    }
 
-		public ExpectedExceptionStatement(Statement base) {
-			fNext= base;
-		}
+    /**
+     * Verify that your code throws an exception that is matched by
+     * a Hamcrest matcher.
+     * <pre> &#064;Test
+     * public void throwsExceptionThatCompliesWithMatcher() {
+     *     NullPointerException e = new NullPointerException();
+     *     thrown.expect(is(e));
+     *     throw e;
+     * }</pre>
+     */
+    public void expect(Matcher<?> matcher) {
+        matcherBuilder.add(matcher);
+    }
 
-		@Override
-		public void evaluate() throws Throwable {
-			try {
-				fNext.evaluate();
-			} catch (Throwable e) {
-				if (fMatcher == null)
-					throw e;
-				Assert.assertThat(e, fMatcher);
-				return;
-			}
-			if (fMatcher != null)
-				throw new AssertionError("Expected test to throw "
-						+ StringDescription.toString(fMatcher));
-		}
-	}
+    /**
+     * Verify that your code throws an exception that is an
+     * instance of specific {@code type}.
+     * <pre> &#064;Test
+     * public void throwsExceptionWithSpecificType() {
+     *     thrown.expect(NullPointerException.class);
+     *     throw new NullPointerException();
+     * }</pre>
+     */
+    public void expect(Class<? extends Throwable> type) {
+        expect(instanceOf(type));
+    }
 
-	private Matcher<Throwable> hasMessage(final Matcher<String> matcher) {
-		return new TypeSafeMatcher<Throwable>() {
-			public void describeTo(Description description) {
-				description.appendText("exception with message ");
-				description.appendDescriptionOf(matcher);
-			}
-		
-			@Override
-			public boolean matchesSafely(Throwable item) {
-				return matcher.matches(item.getMessage());
-			}
-		};
-	}
+    /**
+     * Verify that your code throws an exception whose message contains
+     * a specific text.
+     * <pre> &#064;Test
+     * public void throwsExceptionWhoseMessageContainsSpecificText() {
+     *     thrown.expectMessage(&quot;happened&quot;);
+     *     throw new NullPointerException(&quot;What happened?&quot;);
+     * }</pre>
+     */
+    public void expectMessage(String substring) {
+        expectMessage(containsString(substring));
+    }
+
+    /**
+     * Verify that your code throws an exception whose message is matched 
+     * by a Hamcrest matcher.
+     * <pre> &#064;Test
+     * public void throwsExceptionWhoseMessageCompliesWithMatcher() {
+     *     thrown.expectMessage(startsWith(&quot;What&quot;));
+     *     throw new NullPointerException(&quot;What happened?&quot;);
+     * }</pre>
+     */
+    public void expectMessage(Matcher<String> matcher) {
+        expect(hasMessage(matcher));
+    }
+
+    /**
+     * Verify that your code throws an exception whose cause is matched by 
+     * a Hamcrest matcher.
+     * <pre> &#064;Test
+     * public void throwsExceptionWhoseCauseCompliesWithMatcher() {
+     *     NullPointerException expectedCause = new NullPointerException();
+     *     thrown.expectCause(is(expectedCause));
+     *     throw new IllegalArgumentException(&quot;What happened?&quot;, cause);
+     * }</pre>
+     */
+    public void expectCause(Matcher<? extends Throwable> expectedCause) {
+        expect(hasCause(expectedCause));
+    }
+
+    private class ExpectedExceptionStatement extends Statement {
+        private final Statement next;
+
+        public ExpectedExceptionStatement(Statement base) {
+            next = base;
+        }
+
+        @Override
+        public void evaluate() throws Throwable {
+            try {
+                next.evaluate();
+            } catch (Throwable e) {
+                handleException(e);
+                return;
+            }
+            if (isAnyExceptionExpected()) {
+                failDueToMissingException();
+            }
+        }
+    }
+
+    private void handleException(Throwable e) throws Throwable {
+        if (isAnyExceptionExpected()) {
+            assertThat(e, matcherBuilder.build());
+        } else {
+            throw e;
+        }
+    }
+
+    private boolean isAnyExceptionExpected() {
+        return matcherBuilder.expectsThrowable();
+    }
+
+    private void failDueToMissingException() throws AssertionError {
+        fail(missingExceptionMessage());
+    }
+    
+    private String missingExceptionMessage() {
+        String expectation= StringDescription.toString(matcherBuilder.build());
+        return format(missingExceptionMessage, expectation);
+    }
 }
diff --git a/src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java b/src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java
new file mode 100644
index 0000000..e7d94c4
--- /dev/null
+++ b/src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java
@@ -0,0 +1,46 @@
+package org.junit.rules;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.junit.matchers.JUnitMatchers.isThrowable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hamcrest.Matcher;
+
+/**
+ * Builds special matcher used by {@link ExpectedException}.
+ */
+class ExpectedExceptionMatcherBuilder {
+
+    private final List<Matcher<?>> matchers = new ArrayList<Matcher<?>>();
+
+    void add(Matcher<?> matcher) {
+        matchers.add(matcher);
+    }
+
+    boolean expectsThrowable() {
+        return !matchers.isEmpty();
+    }
+
+    Matcher<Throwable> build() {
+        return isThrowable(allOfTheMatchers());
+    }
+
+    private Matcher<Throwable> allOfTheMatchers() {
+        if (matchers.size() == 1) {
+            return cast(matchers.get(0));
+        }
+        return allOf(castedMatchers());
+    }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    private List<Matcher<? super Throwable>> castedMatchers() {
+        return new ArrayList<Matcher<? super Throwable>>((List) matchers);
+    }
+
+    @SuppressWarnings("unchecked")
+    private Matcher<Throwable> cast(Matcher<?> singleMatcher) {
+        return (Matcher<Throwable>) singleMatcher;
+    }
+}