All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.dakusui.valid8j_pcond.internals.InternalUtils Maven / Gradle / Ivy

The newest version!
package com.github.dakusui.valid8j_pcond.internals;

import com.github.dakusui.valid8j_pcond.core.Evaluable;
import com.github.dakusui.valid8j_pcond.core.printable.PrintableFunction;
import com.github.dakusui.valid8j_pcond.core.printable.PrintablePredicate;
import com.github.dakusui.valid8j_pcond.forms.Functions;
import com.github.dakusui.valid8j_pcond.forms.Printables;
import com.github.dakusui.valid8j_pcond.validator.Explanation;
import com.github.dakusui.valid8j_pcond.validator.Validator;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;

import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;

public enum InternalUtils {
  ;

  private static final Predicate   DUMMY_PREDICATE = Printables.predicate("DUMMY_PREDICATE:ALWAYSTHROW", v -> {
    throw new UnsupportedOperationException("Testing: '" + v + "' was failed, because this is a dummy predicate.");
  });
  private static final Function DUMMY_FUNCTION  = Printables.function("DUMMY_FUNCTION:ALWAYSTHROW", v -> {
    throw new UnsupportedOperationException("Applying: '" + v + "' was failed, because this is a dummy predicate.");
  });

  public static String formatObject(Object value) {
    return formatObject(value, summarizedStringLength());
  }

  public static String formatObject(Object value, int maxLength) {
    return _formatObject(value, maxLength).replaceAll("[\\r\\n]", " ");
  }

  private static String _formatObject(Object value, int maxLength) {
    if (value == null)
      return "null";
    if (value instanceof Collection) {
      Collection collection = (Collection) value;
      if (collection.size() < 4)
        return format("[%s]",
            collection.stream()
                .map(InternalUtils::formatObject)
                .collect(joining(",")));
      Iterator i = collection.iterator();
      return format("[%s,%s,%s...;%s]",
          formatObject(i.next()),
          formatObject(i.next()),
          formatObject(i.next()),
          collection.size()
      );
    }
    if (value instanceof Object[])
      return formatObject(asList((Object[]) value));
    if (value instanceof Formattable)
      return String.format("%s", value);
    if (value instanceof String) {
      String s = (String) value;
      s = summarizeString(s, maxLength);
      return format("\"%s\"", s);
    }
    if (value instanceof Throwable) {
      Throwable throwable = (Throwable) value;
      String simpleName = summarizeString(throwable.getClass().getSimpleName() + ":", maxLength);
      return simpleName +
          (simpleName.length() < Math.max(12, maxLength) ?
              formatObject(throwable.getMessage(), toNextEven(Math.max(12, maxLength - simpleName.length()))) :
              "");
    }
    if (isToStringOverridden(value))
      return summarizeString(
          value.toString(),
          maxLength + 2 /* 2 for margin for single quotes not necessary for non-strings */
      );
    return value.toString().substring(value.getClass().getPackage().getName().length() + 1);
  }

  public static String explainValue(Object value) {
    StringBuilder b = new StringBuilder();
    if (value instanceof Collection) {
      for (Object each : (Collection) value) {
        explainValue(b, 0, each);
      }
    } else {
      explainValue(b, 0, value);
    }
    return b.toString().trim();
  }

  private static void explainValue(StringBuilder buffer, int level, Object value) {
    if (value instanceof Collection) {
      if (((Collection) value).isEmpty())
        explainValue(buffer, level, "[]");
      else {
        for (Object each : (Collection) value)
          explainValue(buffer, level + 1, each);
      }
    } else {
      buffer.append(String.format("%s%s%n", spaces(level * 2), value));
    }
  }

  private static String spaces(int spaces) {
    if (spaces <= 0)
      return "";
    return String.format("%-" + (spaces) + "s", "");
  }

  private static int toNextEven(int value) {
    if ((value & 1) == 0)
      return value;
    return value + 1;
  }

  private static String summarizeString(String s, int length) {
    assert (length & 1) == 0 : "The length must be an even int, but was <" + length + ">";
    assert length >= 12 : "The length must be greater than or equal to 12. Less than 20 is not recommended. But was <" + length + ">";
    if (s.length() > length) {
      int pre = length / 2 - 2;
      int post = length / 2 - 5;
      s = s.substring(0, length - pre) + "..." + s.substring(s.length() - post);
    }
    return s;
  }

  public static int summarizedStringLength() {
    return Validator.instance().configuration().summarizedStringLength();
  }

  private static boolean isToStringOverridden(Object object) {
    return getMethod(object.getClass(), "toString").getDeclaringClass() != Object.class;
  }

  /**
   * A method to check if assertion is enabled or not.
   *
   * @param v A boolean value to test.
   * @return {@code true} - assertion failed with the given value {@code v} / {@code false} - otherwise.
   */
  public static boolean assertFailsWith(boolean v) {
    boolean ret = false;
    try {
      assert v;
    } catch (AssertionError e) {
      ret = true;
    }
    return ret;
  }

  @SuppressWarnings("unchecked")
  public static  T createInstanceFromClassName(Class expectedClass, String requestedClassName, Object... args) {
    try {
      Class loadedClass = Class.forName(requestedClassName);
      try {
        return (T) expectedClass.cast(loadedClass.getDeclaredConstructor(Arrays.stream(args).map(Object::getClass).toArray(Class[]::new)).newInstance(args));
      } catch (ClassCastException e) {
        throw executionFailure("The requested class:'" + requestedClassName +
                "' was found but not an instance of " + expectedClass.getCanonicalName() + ".: " +
                "It was '" + loadedClass.getCanonicalName() + "'.",
            e);
      } catch (NoSuchMethodException e) {
        throw executionFailure("Matching public constructor for " + Arrays.toString(args) + " was not found in " + requestedClassName, e);
      } catch (InvocationTargetException e) {
        throw executionFailure("Matching public constructor was found in " + requestedClassName + " but threw an exception", e.getCause());
      }
    } catch (InstantiationException | IllegalAccessException |
        ClassNotFoundException e) {
      throw executionFailure("The requested class was not found or not accessible.: " + requestedClassName, e);
    }
  }

  public static InternalException executionFailure(String message, Throwable cause) {
    throw executionFailure(Explanation.fromMessage(message), cause);
  }

  public static InternalException executionFailure(Explanation explanation, Throwable cause) {
    throw new InternalException(explanation.toString(), cause);
  }

  public static InternalException wrapIfNecessary(Throwable cause) {
    if (cause instanceof Error)
      throw (Error) cause;
    if (cause instanceof RuntimeException)
      throw (RuntimeException) cause;
    throw executionFailure(cause.getMessage(), cause);
  }

  public static List append(List list, Object p) {
    return unmodifiableList(new ArrayList(list) {{
      add(p);
    }});
  }

  @SuppressWarnings("unchecked")
  public static  Evaluable toEvaluableIfNecessary(Predicate p) {
    requireNonNull(p);
    if (p instanceof Evaluable)
      return (Evaluable) p;
    // We know that Printable.predicate returns a PrintablePredicate object, which is an Evaluable.
    return (Evaluable) Printables.predicate(p::toString, p);
  }

  public static  Evaluable toEvaluableIfNecessary(Function f) {
    return toEvaluableWithFormatterIfNecessary(f, Object::toString);
  }

  @SuppressWarnings("unchecked")
  public static  Evaluable toEvaluableWithFormatterIfNecessary(Function f, Function, String> formatter) {
    requireNonNull(f);
    if (f instanceof Evaluable)
      return (Evaluable) f;
    // We know that Printable.predicate returns a PrintableFunction object, which is an Evaluable.
    return (Evaluable) Printables.function(() -> formatter.apply(f), f);
  }

  public static Class wrapperClassOf(Class clazz) {
    if (clazz == Integer.TYPE)
      return Integer.class;
    if (clazz == Long.TYPE)
      return Long.class;
    if (clazz == Boolean.TYPE)
      return Boolean.class;
    if (clazz == Byte.TYPE)
      return Byte.class;
    if (clazz == Character.TYPE)
      return Character.class;
    if (clazz == Float.TYPE)
      return Float.class;
    if (clazz == Double.TYPE)
      return Double.class;
    if (clazz == Short.TYPE)
      return Short.class;
    if (clazz == Void.TYPE)
      return Void.class;
    throw new IllegalArgumentException("Unsupported type:" + (clazz != null ? clazz.getName() : "null") + " was given.");
  }

  public static Method getMethod(Class aClass, String methodName, Class... parameterTypes) {
    try {
      return aClass.getMethod(methodName, parameterTypes);
    } catch (NoSuchMethodException e) {
      throw executionFailure(format("Requested method: %s(%s) was not found in %s", methodName, Arrays.stream(parameterTypes).map(Class::getName).collect(joining(",")), aClass.getName()), e);
    }
  }

  @SuppressWarnings("unchecked")
  public static  Predicate dummyPredicate() {
    return (Predicate) DUMMY_PREDICATE;
  }

  @SuppressWarnings("unchecked")
  public static  Function dummyFunction() {
    return (Function) DUMMY_FUNCTION;
  }

  public static boolean isDummyFunction(Function function) {
    return function == DUMMY_FUNCTION;
  }

  public static Object toNonStringObject(String s) {
    return new Object() {
      @Override
      public String toString() {
        return s;
      }
    };
  }

  public static String indent(int level) {
    return level == 0 ?
        "" :
        format("%" + (level * 2) + "s", "");
  }

  public static String newLine() {
    return format("%n");
  }

  /**
   * Marks "trivial" a given function.
   * A predicate marked trivial will not appear in an execution report.
   *
   * @param predicate A predicate to be marked.
   * @param       Input type of the function.
   * @return A predicate marked trivial.
   */
  public static  Predicate makeSquashable(Predicate predicate) {
    return ((PrintablePredicate) predicate).makeTrivial();
  }

  /**
   * Marks "trivial" given function.
   * A function marked trivial will not appear in an execution report.
   *
   * @param function A function to marked.
   * @param       Input type of the function.
   * @param       Output type of the function.
   * @return A function marked trivial.
   */
  public static  Function makeSquashable(Function function) {
    return ((PrintableFunction) function).makeTrivial();
  }

  public static  Function trivialIdentityFunction() {
    return Functions.identity();
  }
}