/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.test.util.impl;
import static org.hamcrest.CoreMatchers.containsString;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
import org.hamcrest.TypeSafeMatcher;
import org.hibernate.search.util.impl.CollectionHelper;
/**
* @author Yoann Rodiere
*/
public class ExceptionMatcherBuilder {
public static ExceptionMatcherBuilder isException(Class<? extends Throwable> clazz) {
return new ExceptionMatcherBuilder( clazz );
}
private final List<Matcher<?>> matchers = new ArrayList<>();
private final List<Matcher<?>> suppressedMatchers = new ArrayList<>();
private ExceptionMatcherBuilder(Class<? extends Throwable> clazz) {
matchers.add( CoreMatchers.<Throwable>instanceOf( clazz ) );
}
public ExceptionMatcherBuilder withMainOrSuppressed(Matcher<?> matcher) {
return matching( mainOrSuppressed( matcher ) );
}
public ExceptionMatcherBuilder withSuppressed(Matcher<?> matcher) {
suppressedMatchers.add( matcher );
return this;
}
public ExceptionMatcherBuilder causedBy(Class<? extends Throwable> clazz) {
return new NestedExceptionCauseMatcherBuilder( clazz );
}
public Matcher<? super Throwable> build() {
if ( !suppressedMatchers.isEmpty() ) {
@SuppressWarnings("unchecked")
Matcher<? super Throwable>[] suppressedMatchersAsArray = castedSuppressedMatchers().toArray( new Matcher[suppressedMatchers.size()] );
ExceptionMatcherBuilder.this.matching( hasSuppressed( CoreMatchers.hasItems( suppressedMatchersAsArray ) ) );
suppressedMatchers.clear();
}
if ( matchers.size() == 1 ) {
return castedMatchers().get( 0 );
}
else {
return CoreMatchers.allOf( castedMatchers() );
}
}
@SuppressWarnings({ "unchecked", "rawtypes" }) // Same hack as in JUnit's internal ExpectedExceptionMatcherBuilder
private List<Matcher<? super Throwable>> castedMatchers() {
return new ArrayList<Matcher<? super Throwable>>( (List) matchers );
}
@SuppressWarnings({ "unchecked", "rawtypes" }) // Same hack as in JUnit's internal ExpectedExceptionMatcherBuilder
private List<Matcher<? super Throwable>> castedSuppressedMatchers() {
return new ArrayList<Matcher<? super Throwable>>( (List) suppressedMatchers );
}
public ExceptionMatcherBuilder matching(final Matcher<?> throwableMatcher) {
matchers.add( throwableMatcher );
return this;
}
public ExceptionMatcherBuilder withMessage(String contained) {
return withMessage( containsString( contained ) );
}
public ExceptionMatcherBuilder withMessage(final Matcher<String> messageMatcher) {
return matching( hasMessage( messageMatcher ) );
}
/**
* @author Yoann Rodiere
*/
private class NestedExceptionCauseMatcherBuilder extends ExceptionMatcherBuilder {
public NestedExceptionCauseMatcherBuilder(Class<? extends Throwable> clazz) {
super( clazz );
}
@Override
public Matcher<? super Throwable> build() {
Matcher<? super Throwable> myMatcher = super.build();
ExceptionMatcherBuilder.this.matching( hasCause( myMatcher ) );
return ExceptionMatcherBuilder.this.build();
}
}
/**
* Copied from the internal class from JUnit 4.11, itself licensed under EPL.
* Altered to fix the uselessly precise generic type parameter.
*/
public static class ThrowableMessageMatcher extends TypeSafeMatcher<Throwable> {
private final Matcher<String> fMatcher;
public ThrowableMessageMatcher(Matcher<String> matcher) {
fMatcher = matcher;
}
@Override
public void describeTo(Description description) {
description.appendText( "exception with message " );
description.appendDescriptionOf( fMatcher );
}
@Override
protected boolean matchesSafely(Throwable item) {
return fMatcher.matches( item.getMessage() );
}
@Override
protected void describeMismatchSafely(Throwable item, Description description) {
description.appendText( "message " );
fMatcher.describeMismatch( item.getMessage(), description );
}
}
private static Matcher<Throwable> hasMessage(final Matcher<String> matcher) {
return new ThrowableMessageMatcher( matcher );
}
/**
* Copied from the internal class from JUnit 4.11, itself licensed under EPL.
* Altered to fix the incorrect generic type parameter.
*/
public static class ThrowableCauseMatcher extends TypeSafeMatcher<Throwable> {
private final Matcher<?> causeMatcher;
public ThrowableCauseMatcher(Matcher<?> causeMatcher) {
this.causeMatcher = causeMatcher;
}
@Override
public void describeTo(Description description) {
description.appendText( "exception with cause " );
description.appendDescriptionOf( causeMatcher );
}
@Override
protected boolean matchesSafely(Throwable item) {
return causeMatcher.matches( item.getCause() );
}
@Override
protected void describeMismatchSafely(Throwable item, Description description) {
description.appendText( "cause " );
causeMatcher.describeMismatch( item.getCause(), description );
}
}
private static Matcher<Throwable> hasCause(final Matcher<?> matcher) {
return new ThrowableCauseMatcher( matcher );
}
public static class ThrowableSuppressedMatcher extends TypeSafeMatcher<Throwable> {
private final Matcher<?> suppressedMatcher;
public ThrowableSuppressedMatcher(Matcher<?> suppressedMatcher) {
this.suppressedMatcher = suppressedMatcher;
}
@Override
public void describeTo(Description description) {
description.appendText( "exception with suppressed " );
description.appendDescriptionOf( suppressedMatcher );
}
@Override
protected boolean matchesSafely(Throwable item) {
return suppressedMatcher.matches( Arrays.asList( item.getSuppressed() ) );
}
@Override
protected void describeMismatchSafely(Throwable item, Description description) {
description.appendText( "suppressed " );
suppressedMatcher.describeMismatch( Arrays.asList( item.getSuppressed() ), description );
}
}
private static Matcher<Throwable> hasSuppressed(Matcher<?> suppressedMatcher) {
return new ThrowableSuppressedMatcher( suppressedMatcher );
}
public static class ThrowableMainOrSuppressedMatcher extends TypeSafeDiagnosingMatcher<Throwable> {
private final Matcher<?> mainOrSuppressedMatcher;
public ThrowableMainOrSuppressedMatcher(Matcher<?> suppressedMatcher) {
this.mainOrSuppressedMatcher = suppressedMatcher;
}
@Override
public void describeTo(Description description) {
description.appendText( "exception (or one of its suppressed exceptions) " );
description.appendDescriptionOf( mainOrSuppressedMatcher );
}
@Override
protected boolean matchesSafely(Throwable item, Description mismatchDescription) {
Throwable[] suppressedArray = item.getSuppressed();
List<Throwable> mainAndSuppressed = CollectionHelper.newArrayList( suppressedArray.length + 1 );
mainAndSuppressed.add( item );
for ( Throwable suppressed : suppressedArray ) {
mainAndSuppressed.add( suppressed );
}
boolean first = true;
for ( Throwable element : mainAndSuppressed ) {
if ( mainOrSuppressedMatcher.matches( element ) ) {
return true;
}
if ( !first ) {
mismatchDescription.appendText( ", " );
}
mainOrSuppressedMatcher.describeMismatch( element, mismatchDescription );
first = false;
}
return false;
}
}
private static Matcher<Throwable> mainOrSuppressed(Matcher<?> mainOrSuppressedMatcher) {
return new ThrowableMainOrSuppressedMatcher( mainOrSuppressedMatcher );
}
}