ExceptionComposer.java
package com.github.dakusui.pcond.validator;
import com.github.dakusui.pcond.validator.exceptions.PostconditionViolationException;
import com.github.dakusui.pcond.validator.exceptions.PreconditionViolationException;
import com.github.dakusui.pcond.validator.exceptions.ValidationException;
import java.lang.reflect.InvocationTargetException;
import static com.github.dakusui.pcond.validator.ExceptionComposer.Utils.createException;
import static com.github.dakusui.pcond.validator.Explanation.reportToString;
/**
* An interface to define how an exception is composed based on a given message,
* a class of a condition that the value violates (non-null constraint, invalid state,
* input validation), and the context the violation happened (pre-, post-condition check,
* test-assertion, etc.).
*
* This interface has methods to return objects, each of which composes actual exceptions.
* Each of them intended to group methods that compose exceptions for a single context.
*/
public interface ExceptionComposer {
/**
* Returns an instance to compose exceptions used with `requireXyz` methods in
* {@code Requires} entry-point class of valid8j library.
*
* @return An object to compose exceptions for methods in {@code Requires}.
*/
ForRequire forRequire();
/**
* Returns an instance to compose exceptions used with `ensureXyz` methods in
* {@code Ensures} entry-point class of valid8j library.
*
* @return An object to compose exceptions for methods in {@code Ensures}.
*/
ForEnsure forEnsure();
/**
* Returns an instance to compose exceptions used with `validateXyz` methods in
* {@code Validates} entry-point class of valid8j library.
*
* @return An object to compose exceptions for methods in {@code Validates}.
*/
ForValidate defaultForValidate();
/**
* Returns an instance to compose exceptions used in `assert` statements.
*
* @return An object to compose exceptions for "precondition" violation.
*/
ForAssertion forAssert();
/**
* Returns an instance to compose exceptions used in `assertThat` and `assumeThat`
* methods in {@code TestAssertions} entry-point class of thincrest library.
* Other entry-point classes provided for use cases of the `pcond` library as
* a test assertion library may also use this method.
*
* @return An object to compose exceptions for test assertions
*/
ForTestAssertion forAssertThat();
/**
* An implementation of the {@link ExceptionComposer} interface.
* You usually do not need to extend this method to customize its behavior.
* Rather you only need to control the arguments passed to its constructor
* through {@link Validator.Configuration.Builder}.
*
* @see Validator.Configuration
* @see Validator.Configuration.Builder
*/
class Impl implements ExceptionComposer {
final private ForRequire forRequire;
final private ForEnsure forEnsure;
final private ForValidate defaultForValidate;
final private ForAssertion forAssert;
final private ForTestAssertion forAssertThat;
public Impl(ForRequire forRequire, ForEnsure forEnsure, ForValidate defaultForValidate, ForAssertion forAssert, ForTestAssertion forAssertThat) {
this.forRequire = forRequire;
this.forEnsure = forEnsure;
this.defaultForValidate = defaultForValidate;
this.forAssert = forAssert;
this.forAssertThat = forAssertThat;
}
@Override
public ForRequire forRequire() {
return this.forRequire;
}
@Override
public ForEnsure forEnsure() {
return this.forEnsure;
}
@Override
public ForValidate defaultForValidate() {
return this.defaultForValidate;
}
@Override
public ForAssertion forAssert() {
return this.forAssert;
}
@Override
public ForTestAssertion forAssertThat() {
return this.forAssertThat;
}
}
/**
* An interface that defines common methods that an object returned by each method
* in the {@link ExceptionComposer} has.
*/
interface Base {
/**
* A method to compose an exception for a "Null-violation".
* When you are checking a "pre-condition", if the value must not be `null`,
* you may prefer a {@link NullPointerException}, rather than an exception
* such as `YourPreconditionException` as Google Guava's `Precondition` does
* so.
*
* This method by default, returns a `NullPointerException`.
*
* In case you prefer to throw `YourPreconditionException` for the sake of uniformity,
* you can override this method for an object returned by {@link ExceptionComposer#forRequire()}
*
* For more detail, please refer to {@link Validator.Configuration}.
*
* @param message A message attached to the composed exception.
* @return A composed exception.
*/
default Throwable exceptionForNonNullViolation(String message) {
return new NullPointerException(message);
}
/**
* A method to compose an exception for a "State-violation".
*
* @param message A message attached to the composed exception.
* @return A composed exception.
* @see Base#exceptionForNonNullViolation(String)
*/
default Throwable exceptionForIllegalState(String message) {
return new IllegalStateException(message);
}
/**
* A method to compose an exception for a general violation.
* An extension-point to customize the exception to be thrown for a certain
* context.
*
* @param message A message attached to the composed exception.
* @return A composed exception.
*/
Throwable exceptionForGeneralViolation(String message);
}
interface ForRequire extends Base {
@Override
default Throwable exceptionForGeneralViolation(String message) {
return new PreconditionViolationException(message);
}
Throwable exceptionForIllegalArgument(String message);
@SuppressWarnings("unused") // Referenced reflectively
class Default implements ForRequire {
@Override
public Throwable exceptionForIllegalArgument(String message) {
return new IllegalArgumentException(message);
}
}
}
interface ForEnsure extends Base {
@Override
default Throwable exceptionForGeneralViolation(String message) {
return new PostconditionViolationException(message);
}
@SuppressWarnings("unused") // Referenced reflectively
class Default implements ForEnsure {
}
}
interface ForValidate extends Base {
@Override
default Throwable exceptionForGeneralViolation(String message) {
return new ValidationException(message);
}
default Throwable exceptionForIllegalArgument(String message) {
return new IllegalArgumentException(message);
}
@SuppressWarnings("unused") // Referenced reflectively
class Default implements ForValidate {
}
}
interface ForAssertion {
default Throwable exceptionPreconditionViolation(String message) {
return new AssertionError(message);
}
default Throwable exceptionInvariantConditionViolation(String message) {
return new AssertionError(message);
}
default Throwable exceptionPostconditionViolation(String message) {
return new AssertionError(message);
}
@SuppressWarnings("unused") // Referenced reflectively
class Default implements ForAssertion {
}
}
interface ForTestAssertion {
<T extends RuntimeException> T testSkippedException(String message, ReportComposer reportComposer);
default <T extends RuntimeException> T testSkippedException(Explanation explanation, ReportComposer reportComposer) {
return testSkippedException(explanation.toString(), reportComposer);
}
<T extends Error> T testFailedException(Explanation explanation, ReportComposer reportComposer);
@SuppressWarnings("unused") // Referenced reflectively
class JUnit4 implements ForTestAssertion {
@SuppressWarnings("unchecked")
@Override
public <T extends RuntimeException> T testSkippedException(String message, ReportComposer reportComposer) {
throw (T) createException(
"org.junit.AssumptionViolatedException",
reportComposer.explanationFromMessage(message),
(c, exp) -> c.getConstructor(String.class).newInstance(exp.message()));
}
@SuppressWarnings("unchecked")
@Override
public <T extends Error> T testFailedException(Explanation explanation, ReportComposer reportComposer) {
throw (T) createException(
"org.junit.ComparisonFailure",
explanation,
(c, exp) -> c.getConstructor(String.class, String.class, String.class).newInstance(exp.message(), reportToString(exp.expected()), reportToString(exp.actual())));
}
}
@SuppressWarnings("unused") // Referenced reflectively
class Opentest4J implements ForTestAssertion {
@SuppressWarnings("unchecked")
@Override
public <T extends RuntimeException> T testSkippedException(String message, ReportComposer reportComposer) {
throw (T) createException("org.opentest4j.TestSkippedException", reportComposer.explanationFromMessage(message), (c, exp) ->
c.getConstructor(String.class).newInstance(exp.message()));
}
@SuppressWarnings("unchecked")
@Override
public <T extends Error> T testFailedException(Explanation explanation, ReportComposer reportComposer) {
throw (T) createException("org.opentest4j.AssertionFailedError", explanation, (c, exp) ->
c.getConstructor(String.class, Object.class, Object.class).newInstance(exp.message(), reportToString(exp.expected()), reportToString(exp.actual())));
}
}
}
enum Utils {
;
@SuppressWarnings("unchecked")
public static <T extends Throwable> T createException(String className, Explanation explanation, ReflectiveExceptionFactory<T> reflectiveExceptionFactory) {
try {
return reflectiveExceptionFactory.apply((Class<T>) Class.forName(className), explanation);
} catch (ClassNotFoundException e) {
throw new RuntimeException("FAILED TO INSTANTIATE EXCEPTION: '" + className + "' (NOT FOUND)", e);
}
}
@FunctionalInterface
public
interface ReflectiveExceptionFactory<T extends Throwable> {
T create(Class<T> c, Explanation explanation) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException;
default T apply(Class<T> c, Explanation explanation) {
try {
return create(c, explanation);
} catch (InvocationTargetException | InstantiationException |
IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException("FAILED TO INSTANTIATE EXCEPTION: '" + c.getCanonicalName() + "'", e);
}
}
}
}
}