Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ThrowableVerifier#causedByAll & ThrowableVerifier#causedByAny #19

Merged
merged 1 commit into from
Feb 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/main/java/io/skelp/verifier/type/StringVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,7 @@ public StringVerifier match(final Pattern pattern) {
* @throws VerifierException
* If the verification fails while not negated or passes while negated.
* @see #matchAll(Pattern...)
* @since 0.2.0
*/
public StringVerifier matchAll(final CharSequence... regexes) {
final String value = verification().getValue();
Expand Down Expand Up @@ -870,6 +871,7 @@ public StringVerifier matchAll(final CharSequence... regexes) {
* @throws VerifierException
* If the verification fails while not negated or passes while negated.
* @see #matchAll(CharSequence...)
* @since 0.2.0
*/
public StringVerifier matchAll(final Pattern... patterns) {
final String value = verification().getValue();
Expand Down Expand Up @@ -902,6 +904,7 @@ public StringVerifier matchAll(final Pattern... patterns) {
* @throws VerifierException
* If the verification fails while not negated or passes while negated.
* @see #matchAny(Pattern...)
* @since 0.2.0
*/
public StringVerifier matchAny(final CharSequence... regexes) {
final String value = verification().getValue();
Expand Down Expand Up @@ -934,6 +937,7 @@ public StringVerifier matchAny(final CharSequence... regexes) {
* @throws VerifierException
* If the verification fails while not negated or passes while negated.
* @see #matchAny(CharSequence...)
* @since 0.2.0
*/
public StringVerifier matchAny(final Pattern... patterns) {
final String value = verification().getValue();
Expand Down
163 changes: 143 additions & 20 deletions src/main/java/io/skelp/verifier/type/ThrowableVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
package io.skelp.verifier.type;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import io.skelp.verifier.AbstractCustomVerifier;
import io.skelp.verifier.VerifierException;
Expand All @@ -38,6 +41,15 @@
*/
public final class ThrowableVerifier extends AbstractCustomVerifier<Throwable, ThrowableVerifier> {

private static boolean isCausedBy(final Collection<Class<?>> throwableTypes, final Class<?> type) {
return throwableTypes.stream()
.anyMatch(type::isAssignableFrom);
}

private static boolean isCausedBy(final Collection<Throwable> throwables, final Throwable throwable) {
return throwables.contains(throwable);
}

private static List<Throwable> getThrowables(Throwable throwable) {
final List<Throwable> throwables = new ArrayList<>();
while (throwable != null && !throwables.contains(throwable)) {
Expand All @@ -48,6 +60,12 @@ private static List<Throwable> getThrowables(Throwable throwable) {
return throwables;
}

private static Set<Class<?>> getThrowableTypes(final Throwable throwable) {
return getThrowables(throwable).stream()
.map(Throwable::getClass)
.collect(Collectors.toSet());
}

/**
* <p>
* Creates an instance of {@link ThrowableVerifier} based on the {@code verification} provided.
Expand All @@ -62,7 +80,7 @@ public ThrowableVerifier(final Verification<Throwable> verification) {

/**
* <p>
* Verifies that the value is or has been caused by the {@code Class} provided.
* Verifies that the value is or has been caused by a throwable of the {@code type} provided.
* </p>
* <p>
* {@literal null} references are handled gracefully without exceptions.
Expand All @@ -88,13 +106,7 @@ public ThrowableVerifier(final Verification<Throwable> verification) {
*/
public ThrowableVerifier causedBy(final Class<?> type) {
final Throwable value = verification().getValue();
boolean result = false;
for (final Throwable throwable : getThrowables(value)) {
if (type.isAssignableFrom(throwable.getClass())) {
result = true;
break;
}
}
final boolean result = isCausedBy(getThrowableTypes(value), type);

verification().report(result, MessageKeys.CAUSED_BY, type);

Expand All @@ -109,19 +121,19 @@ public ThrowableVerifier causedBy(final Class<?> type) {
* {@literal null} references are handled gracefully without exceptions.
* </p>
* <pre>
* Verifier.verify((Throwable) null).causedBy(*) =&gt; FAIL
* Verifier.verify(*).causedBy((Throwable) null) =&gt; FAIL
* Verifier.verify(ex = new IOException()).causedBy(new IOException()) =&gt; FAIL
* Verifier.verify(ex = new IOException()).causedBy(ex) =&gt; PASS
* Verifier.verify(new NullPointerException(ex = new IOException())).causedBy(ex) =&gt; PASS
* Verifier.verify((Throwable) null).causedBy(*) =&gt; FAIL
* Verifier.verify(*).causedBy((Throwable) null) =&gt; FAIL
* Verifier.verify(ex = new IOException("foo")).causedBy(new IOException()) =&gt; FAIL
* Verifier.verify(ex = new IOException("foo")).causedBy(ex) =&gt; PASS
* Verifier.verify(new NullPointerException(ex = new IOException("foo"))).causedBy(ex) =&gt; PASS
* </pre>
*
* @param cause
* the {@code Throwable} to compare against the value or its causes (may be {@literal null})
* @return A reference to this {@link ThrowableVerifier} for chaining purposes.
* @throws VerifierException
* If the verification fails while not negated or passes while negated.
* @see #causedBy(Throwable)
* @see #causedBy(Class)
* @see #causedBy(Throwable, Object)
*/
public ThrowableVerifier causedBy(final Throwable cause) {
Expand All @@ -142,11 +154,11 @@ public ThrowableVerifier causedBy(final Throwable cause) {
* {@literal null} references are handled gracefully without exceptions.
* </p>
* <pre>
* Verifier.verify((Throwable) null).causedBy(*, *) =&gt; FAIL
* Verifier.verify(*).causedBy(null, *) =&gt; FAIL
* Verifier.verify(ex = new IOException()).causedBy(new IOException(), *) =&gt; FAIL
* Verifier.verify(ex = new IOException()).causedBy(ex, *) =&gt; PASS
* Verifier.verify(new NullPointerException(ex = new IOException())).causedBy(ex, *) =&gt; PASS
* Verifier.verify((Throwable) null).causedBy(*, *) =&gt; FAIL
* Verifier.verify(*).causedBy(null, *) =&gt; FAIL
* Verifier.verify(ex = new IOException("foo")).causedBy(new IOException(), *) =&gt; FAIL
* Verifier.verify(ex = new IOException("foo")).causedBy(ex, *) =&gt; PASS
* Verifier.verify(new NullPointerException(ex = new IOException("foo"))).causedBy(ex, *) =&gt; PASS
* </pre>
*
* @param cause
Expand All @@ -161,13 +173,122 @@ public ThrowableVerifier causedBy(final Throwable cause) {
*/
public ThrowableVerifier causedBy(final Throwable cause, final Object name) {
final Throwable value = verification().getValue();
final boolean result = getThrowables(value).contains(cause);
final boolean result = isCausedBy(getThrowables(value), cause);

verification().report(result, MessageKeys.CAUSED_BY, name);

return this;
}

/**
* <p>
* Verifies that the value is or has been caused by a throwable of <b>all</b> of the {@code types} provided.
* </p>
* <p>
* {@literal null} references are handled gracefully without exceptions.
* </p>
* <pre>
* Verifier.verify(*).causedByAll() =&gt; PASS
* Verifier.verify(*).causedByAll((Class[]) null) =&gt; PASS
* Verifier.verify(*).causedByAll(*, null) =&gt; FAIL
* Verifier.verify((Throwable) null).causedByAll(*) =&gt; FAIL
* Verifier.verify(new IOException()).causedByAll(IOException.class, Exception.class) =&gt; PASS
* Verifier.verify(new IOException()).causedByAll(IOException.class, IllegalArgumentException.class) =&gt; FAIL
* Verifier.verify(new NullPointerException(new IOException())).causedByAll(IOException.class, NullPointerException.class, Exception.class) =&gt; PASS
* Verifier.verify(new NullPointerException(new IOException())).causedByAll(IOException.class, IllegalArgumentException.class, Exception.class) =&gt; FAIL
* </pre>
*
* @param types
* the classes to which the value or one of its cause must be assignable (may be {@literal null} or contain
* {@literal null} references)
* @return A reference to this {@link ThrowableVerifier} for chaining purposes.
* @throws VerifierException
* If the verification fails while not negated or passes while negated.
* @since 0.2.0
*/
public ThrowableVerifier causedByAll(final Class<?>... types) {
final Throwable value = verification().getValue();
final Set<Class<?>> throwableTypes = getThrowableTypes(value);
final boolean result = matchAll(types, input -> input != null && isCausedBy(throwableTypes, input));

verification().report(result, MessageKeys.CAUSED_BY_ALL, (Object) types);

return this;
}

/**
* <p>
* Verifies that the value is or has been caused by a throwable of <b>any</b> of the {@code types} provided.
* </p>
* <p>
* {@literal null} references are handled gracefully without exceptions.
* </p>
* <pre>
* Verifier.verify(*).causedByAny() =&gt; FAIL
* Verifier.verify(*).causedByAny((Class[]) null) =&gt; FAIL
* Verifier.verify(*).causedByAny(*, Throwable.class) =&gt; PASS
* Verifier.verify((Throwable) null).causedByAny(*) =&gt; FAIL
* Verifier.verify(new IOException()).causedByAny(IllegalArgumentException.class, IOException.class) =&gt; PASS
* Verifier.verify(new IOException()).causedByAny(IllegalArgumentException.class, NullPointerException.class) =&gt; FAIL
* Verifier.verify(new NullPointerException(new IOException())).causedByAny(IllegalArgumentException.class, IllegalStateException.class, IOException.class) =&gt; PASS
* Verifier.verify(new NullPointerException(new IOException())).causedByAny(ArrayIndexOutOfBoundsException.class, IllegalArgumentException.class, IllegalStateException.class) =&gt; FAIL
* </pre>
*
* @param types
* the classes to which the value or one of its cause must be assignable (may be {@literal null} or contain
* {@literal null} references)
* @return A reference to this {@link ThrowableVerifier} for chaining purposes.
* @throws VerifierException
* If the verification fails while not negated or passes while negated.
* @see #causedByAny(Throwable...)
* @since 0.2.0
*/
public ThrowableVerifier causedByAny(final Class<?>... types) {
final Throwable value = verification().getValue();
final Set<Class<?>> throwableTypes = getThrowableTypes(value);
final boolean result = matchAny(types, input -> input != null && isCausedBy(throwableTypes, input));

verification().report(result, MessageKeys.CAUSED_BY_ANY, (Object) types);

return this;
}

/**
* <p>
* Verifies that the value is or has been caused by the <b>any</b> {@code causes} provided.
* </p>
* <p>
* {@literal null} references are handled gracefully without exceptions.
* </p>
* <pre>
* Verifier.verify(*).causedByAny() =&gt; FAIL
* Verifier.verify(*).causedByAny((Throwable[]) null) =&gt; FAIL
* Verifier.verify((Throwable) null).causedByAny(*) =&gt; FAIL
* Verifier.verify(ex = new IOException("foo")).causedByAny(new IllegalArgumentException(), ex) =&gt; PASS
* Verifier.verify(ex = new IOException("foo")).causedByAny(new IllegalArgumentException(), new IOException()) =&gt; FAIL
* Verifier.verify(new NullPointerException(ex = new IOException("foo"))).causedByAny(new IllegalArgumentException(), ex) =&gt; PASS
* Verifier.verify(new NullPointerException(ex = new IOException("foo"))).causedByAny(new IllegalArgumentException(), new IOException()) =&gt; FAIL
* </pre>
*
* @param causes
* the {@code Throwables} to compare against the value or its causes (may be {@literal null} or contain
* {@literal null} references)
* @return A reference to this {@link ThrowableVerifier} for chaining purposes.
* @throws VerifierException
* If the verification fails while not negated or passes while negated.
* @see #causedByAny(Class...)
* @since 0.2.0
*/
public ThrowableVerifier causedByAny(final Throwable... causes) {
final Throwable value = verification().getValue();
final List<Throwable> throwables = getThrowables(value);
final boolean result = matchAny(causes, input -> isCausedBy(throwables, input));

verification().report(result, MessageKeys.CAUSED_BY_ANY, (Object) causes);

return this;
}

/**
* <p>
* Verifies that the value is a checked exception.
Expand Down Expand Up @@ -262,6 +383,8 @@ public ThrowableVerifier unchecked() {
enum MessageKeys implements MessageKey {

CAUSED_BY("io.skelp.verifier.type.ThrowableVerifier.causedBy"),
CAUSED_BY_ALL("io.skelp.verifier.type.ThrowableVerifier.causedByAll"),
CAUSED_BY_ANY("io.skelp.verifier.type.ThrowableVerifier.causedByAny"),
CHECKED("io.skelp.verifier.type.ThrowableVerifier.checked"),
MESSAGE("io.skelp.verifier.type.ThrowableVerifier.message"),
UNCHECKED("io.skelp.verifier.type.ThrowableVerifier.unchecked");
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/Verifier.properties
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ io.skelp.verifier.type.StringVerifier.startWithIgnoreCase=start with ''{0}'' (ig
io.skelp.verifier.type.StringVerifier.upperCase=be all upper case

io.skelp.verifier.type.ThrowableVerifier.causedBy=have been caused by ''{0}''
io.skelp.verifier.type.ThrowableVerifier.causedByAll=have been caused by all {0}
io.skelp.verifier.type.ThrowableVerifier.causedByAny=have been caused by any {0}
io.skelp.verifier.type.ThrowableVerifier.checked=be checked
io.skelp.verifier.type.ThrowableVerifier.message=have message ''{0}''
io.skelp.verifier.type.ThrowableVerifier.unchecked=be unchecked
4 changes: 2 additions & 2 deletions src/test/java/io/skelp/verifier/type/ClassVerifierTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ public void testAssignableFromAllWhenNoTypes() {

@Test
public void testAssignableFromAllWhenTypeIsNull() {
testAssignableFromAllHelper(Collection.class, createArray((Class<?>) null), false);
testAssignableFromAllHelper(Collection.class, createArray((Class) null), false);
}

@Test
Expand Down Expand Up @@ -422,7 +422,7 @@ public void testAssignableFromAnyWhenNoTypes() {

@Test
public void testAssignableFromAnyWhenTypeIsNull() {
testAssignableFromAnyHelper(Collection.class, createArray((Class<?>) null), false);
testAssignableFromAnyHelper(Collection.class, createArray((Class) null), false);
}

@Test
Expand Down
Loading