MethodUtils.java

package com.github.dakusui.osynth.core.utils;

import com.github.dakusui.osynth.core.InvocationController;
import com.github.dakusui.osynth.core.MethodHandler;
import com.github.dakusui.osynth.core.MethodSignature;
import com.github.dakusui.osynth.core.SynthesizedObject;
import com.github.dakusui.osynth.exceptions.OsynthException;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

import static com.github.dakusui.osynth.core.utils.MessageUtils.messageForMissingMethodHandler;
import static com.github.dakusui.pcond.forms.Predicates.isNotNull;
import static com.github.dakusui.pcond.forms.Predicates.transform;
import static com.github.dakusui.valid8j.Assertions.that;
import static java.util.stream.Collectors.joining;

public enum MethodUtils {
  ;

  public static MethodHandler createMethodHandlerFromFallbackObject(final Object fallbackObject, MethodSignature methodSignature) {
    assert that(fallbackObject, isNotNull());
    return createMethodHandlerDelegatingToObject(fallbackObject, methodSignature);
  }

  public static MethodHandler createMethodHandlerDelegatingToObject(Object object, MethodSignature methodSignature) {
    assert object != null;
    return (synthesizedObject, args) -> execute(() -> {
      Method method = getMethodFromClass(synthesizedObject, object.getClass(), methodSignature.name(), methodSignature.parameterTypes());
      method.setAccessible(true);
      return method.invoke(object, args);
    });
  }

  public static Optional<MethodHandler> createMethodHandlerFromInterfaceClass(Class<?> fromClass, MethodSignature methodSignature) {
    return findMethodHandleFor(methodSignature, fromClass).map(MethodUtils::toMethodHandler);
  }

  static MethodHandler toMethodHandler(MethodHandle methodHandle) {
    return (SynthesizedObject synthesizedObject, Object[] arguments) -> execute(
        () -> methodHandle.bindTo(synthesizedObject).invokeWithArguments(arguments));
  }

  static Optional<MethodHandle> findMethodHandleFor(MethodSignature methodSignature, Class<?> fromClass) {
    return findMethodMatchingWith(methodSignature, fromClass).filter(Method::isDefault).map(m -> methodHandleFor(m, fromClass));
  }

  private static MethodHandle methodHandleFor(Method m, Class<?> fromClass) {
    assert that(fromClass, transform(AssertionUtils.classGetMethod(m.getName(), m.getParameterTypes())).check(isNotNull()));
    return execute(() -> createMethodHandlesLookupFor(fromClass).in(fromClass).unreflectSpecial(m, fromClass));
  }

  public static synchronized MethodHandles.Lookup createMethodHandlesLookupFor(Class<?> anInterfaceClass) {
    return execute(() -> {
      Constructor<MethodHandles.Lookup> constructor;
      constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);
      constructor.setAccessible(true);
      return constructor.newInstance(anInterfaceClass);
    });
  }

  /**
   * A method to uniform a way to handle exceptions.
   *
   * @param block A block to execute hand to handle possible exceptions.
   * @param <T>   A type of the returned value.
   * @return A returned value from the block.
   */
  public static <T> T execute(FailableSupplier<T> block) {
    return execute(block, Throwable::getMessage);
  }

  public static <T> T execute(FailableSupplier<T> block, Function<Throwable, String> messageComposerOnFailure) {
    try {
      return block.get();
    } catch (Error e) {
      throw e;
    } catch (Throwable e) {
      throw OsynthException.from(messageComposerOnFailure.apply(e), e);
    }
  }

  public static Object[] toEmptyArrayIfNull(Object[] args) {
    if (args == null)
      return InvocationController.EMPTY_ARGS;
    return args;
  }

  public static Optional<MethodHandler> createMethodHandlerFromInterfaces(List<Class<?>> interfaces, MethodSignature methodSignature) {
    return interfaces.stream()
        .map((Class<?> eachInterfaceClass) -> createMethodHandlerFromInterfaceClass(eachInterfaceClass, methodSignature))
        .filter(Optional::isPresent)
        .map(Optional::get)
        .findFirst();
  }

  public static boolean isToStringOverridden(Class<?> aClass) {
    return !getMethodFromClass(aClass, "toString").getDeclaringClass().equals(Object.class);
  }

  public static Method getMethodFromClass(Class<?> aClass, String methodName, Class<?>... parameterTypes) {
    return getMethodFromClass(aClass, aClass, methodName, parameterTypes);
  }

  private static Method getMethodFromClass(Object objectForErrorMessageFormatting, Class<?> aClass, String methodName, Class<?>... parameterTypes) {
    try {
      return aClass.getMethod(methodName, parameterTypes);
    } catch (NoSuchMethodException e) {
      throw new UnsupportedOperationException(messageForMissingMethodHandler(methodName, parameterTypes, objectForErrorMessageFormatting, e), e);
    }
  }

  public static String simpleClassNameOf(Class<?> aClass) {
    if (aClass.getSimpleName().length() > 0 && !aClass.isSynthetic())
      return aClass.getSimpleName();
    final String label;
    final Optional<String> m;
    if (aClass.isSynthetic()) {
      label = "lambda";
      m = Optional.of(enclosingClassNameOfLambda(aClass.getCanonicalName()));
    } else {
      label = "anonymous";
      m = Optional.empty();
    }
    return streamSupertypes(aClass)
        .filter(each -> !Objects.equals(Object.class, each))
        .map(MethodUtils::simpleClassNameOf)
        .collect(joining(",", label + ":(", ")")) +
        m.map(v -> ":declared in " + v).orElse("");
  }

  public static String prettierToString(Object object) {
    if (object == null)
      return "null";
    Class<?> aClass = object.getClass();
    return isToStringOverridden(aClass) ?
        object.toString() :
        simpleClassNameOf(aClass) + "@" + System.identityHashCode(object);
  }

  private static String enclosingClassNameOfLambda(String canonicalNameOfLambda) {
    String ret = canonicalNameOfLambda.substring(0, canonicalNameOfLambda.lastIndexOf("$$"));
    int b = ret.lastIndexOf("$");
    if (b < 0)
      return ret;
    return ret.substring(b + "$".length());
  }

  private static Stream<Class<?>> streamSupertypes(Class<?> klass) {
    return Stream.concat(
            Stream.of(klass.getSuperclass()),
            Arrays.stream(klass.getInterfaces()))
        .filter(Objects::nonNull);
  }

  /**
   * An interface to define a block which possibly throws an exception.
   * Intended to be used with {@link MethodUtils#execute} method.
   *
   * @param <T> A type of the value retuned by the block.
   */
  public interface FailableSupplier<T> {
    T get() throws Throwable;
  }

  private static Optional<Method> findMethodMatchingWith(MethodSignature methodSignature, Class<?> fromClass) {
    try {
      return Optional.of(fromClass.getMethod(methodSignature.name(), methodSignature.parameterTypes()));
    } catch (NoSuchMethodException e) {
      return Optional.empty();
    }
  }

  public static MethodHandler withName(String name, MethodHandler methodHandler) {
    return new MethodHandler() {
      @Override
      public Object handle(SynthesizedObject synthesizedObject, Object[] objects) throws Throwable {
        return methodHandler.handle(synthesizedObject, objects);
      }

      @Override
      public String toString() {
        return name;
      }
    };
  }
}