class AssertionErrorCreator {

  private static final Class<?>[] MSG_ARG_TYPES_FOR_ASSERTION_FAILED_ERROR = array(String.class, Object.class, Object.class);

  private static final Class<?>[] MSG_ARG_TYPES_FOR_COMPARISON_FAILURE = array(String.class, String.class, String.class);

  private static final Class<?>[] MULTIPLE_FAILURES_ERROR_ARGUMENT_TYPES = array(String.class, List.class);

  @VisibleForTesting
  ConstructorInvoker constructorInvoker;

  public AssertionErrorCreator() {
    this(new ConstructorInvoker());
  }

  public AssertionErrorCreator(ConstructorInvoker constructorInvoker) {
    this.constructorInvoker = constructorInvoker;
  }

  // single assertion error

  public AssertionError assertionError(String message, Object actual, Object expected, Representation representation) {
    // @format:off
    return assertionFailedError(message, actual,expected)
                    .orElse(comparisonFailure(message, actual, expected, representation)
                    .orElse(assertionError(message)));
    // @format:on
  }

  private Optional<AssertionError> assertionFailedError(String message, Object actual, Object expected) {<FILL_FUNCTION_BODY>}

  private Optional<AssertionError> comparisonFailure(String message,
                                                     Object actual,
                                                     Object expected,
                                                     Representation representation) {
    try {
      UnambiguousRepresentation unambiguousRepresentation = new UnambiguousRepresentation(representation, actual, expected);

      Object o = constructorInvoker.newInstance("org.junit.ComparisonFailure",
                                                MSG_ARG_TYPES_FOR_COMPARISON_FAILURE,
                                                message,
                                                unambiguousRepresentation.getExpected(),
                                                unambiguousRepresentation.getActual());

      if (o instanceof AssertionError) return Optional.of((AssertionError) o);

    } catch (@SuppressWarnings("unused") Throwable ignored) {}
    return Optional.empty();
  }

  public AssertionError assertionError(String message) {
    return new AssertionError(message);
  }

  // multiple assertions error

  public AssertionError multipleSoftAssertionsError(List<? extends Throwable> errors) {
    Optional<AssertionError> multipleFailuresError = tryBuildingMultipleFailuresError(errors);
    return multipleFailuresError.orElse(new SoftAssertionError(describeErrors(errors)));
  }

  public AssertionError multipleAssertionsError(Description description, List<? extends AssertionError> errors) {
    String heading = headingFrom(description);
    Optional<AssertionError> multipleFailuresError = tryBuildingMultipleFailuresError(heading, errors);
    return multipleFailuresError.orElse(new MultipleAssertionsError(description, errors));
  }

  public void tryThrowingMultipleFailuresError(List<? extends Throwable> errorsCollected) {
    tryBuildingMultipleFailuresError(errorsCollected).ifPresent(AssertionErrorCreator::throwError);
  }

  // syntactic sugar
  private static void throwError(AssertionError error) {
    throw error;
  }

  private static String headingFrom(Description description) {
    return description == null ? null : DescriptionFormatter.instance().format(description);
  }

  private Optional<AssertionError> tryBuildingMultipleFailuresError(List<? extends Throwable> errorsCollected) {
    return tryBuildingMultipleFailuresError(null, errorsCollected);
  }

  private Optional<AssertionError> tryBuildingMultipleFailuresError(String heading,
                                                                    List<? extends Throwable> errorsCollected) {
    if (errorsCollected.isEmpty()) return Optional.empty();
    try {
      Object[] constructorArguments = array(heading, errorsCollected);
      Object multipleFailuresError = constructorInvoker.newInstance("org.opentest4j.MultipleFailuresError",
                                                                    MULTIPLE_FAILURES_ERROR_ARGUMENT_TYPES,
                                                                    constructorArguments);
      if (multipleFailuresError instanceof AssertionError) { // means that we were able to build a MultipleFailuresError
        List<Throwable> failures = extractFailuresOf(multipleFailuresError);
        // we switch to AssertJMultipleFailuresError in order to control the formatting of the error message.
        // we use reflection to avoid making opentest4j a required dependency
        AssertionError assertionError = (AssertionError) constructorInvoker.newInstance("org.assertj.core.error.AssertJMultipleFailuresError",
                                                                                        MULTIPLE_FAILURES_ERROR_ARGUMENT_TYPES,
                                                                                        array(heading, failures));
        Failures.instance().removeAssertJRelatedElementsFromStackTraceIfNeeded(assertionError);
        return Optional.of(assertionError);
      }
    } catch (@SuppressWarnings("unused") Exception e) {
      // do nothing, MultipleFailuresError was not in the classpath
    }
    return Optional.empty();
  }

  @SuppressWarnings("unchecked")
  private static List<Throwable> extractFailuresOf(Object multipleFailuresError) {
    return (List<Throwable>) PropertyOrFieldSupport.EXTRACTION.getValueOf("failures", multipleFailuresError);
  }

}

    try {
      Object o = constructorInvoker.newInstance("org.opentest4j.AssertionFailedError",
                                                MSG_ARG_TYPES_FOR_ASSERTION_FAILED_ERROR,
                                                message,
                                                expected,
                                                actual);

      if (o instanceof AssertionError) return Optional.of((AssertionError) o);

    } catch (@SuppressWarnings("unused") Throwable ignored) {}
    return Optional.empty();
  
<no_super_class>