InternalUtils.java

package com.github.dakusui.actionunit.utils;

import com.github.dakusui.actionunit.exceptions.ActionTimeOutException;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.function.Supplier;

import static com.github.dakusui.actionunit.exceptions.ActionException.wrap;
import static com.github.dakusui.actionunit.utils.Checks.checkNotNull;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;

public enum InternalUtils {
  ;
  public static final Method OBJECT_TO_STRING_METHOD = objectToStringMethod();

  public static void sleep(long duration, TimeUnit timeUnit) {
    try {
      checkNotNull(timeUnit).sleep(duration);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw wrap(e);
    }
  }

  public static <T> T runWithTimeout(Callable<T> callable, Supplier<String> description, Supplier<String> timeoutDescription, long timeout, TimeUnit timeUnit) {
    final ExecutorService executor = Executors.newSingleThreadExecutor();
    final Future<T> future = executor.submit(callable);
    executor.shutdown(); // This does not cancel the already-scheduled task.
    try {
      Thread.interrupted();
      return future.get(timeout, timeUnit);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw wrap(e);
    } catch (TimeoutException e) {
      future.cancel(true);
      throw new ActionTimeOutException(
          String.format("Action: <%s>; %s with message: <%s>", description.get(), timeoutDescription.get(), e.getMessage()),
          e);
    } catch (ExecutionException e) {
      //unwrap the root cause
      Throwable cause = e.getCause();
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      ////
      // It's safe to directly cast to RuntimeException, because a Callable can only
      // throw an Error or a RuntimeException.
      throw (RuntimeException) cause;
    } finally {
      executor.shutdownNow();
    }
  }

  public static String indent(int level, int indentWidth) {
    StringBuilder b = new StringBuilder();
    for (int i = 0; i < level; i++)
      b.append(spaces(indentWidth));
    return b.toString();
  }

  public static String formatNumberOfTimes(int i) {
    if (i == 1)
      return "once";
    if (i == 2)
      return "twice";
    return String.format("%s times", i);
  }


  public static String formatDuration(long durationInNanos) {
    TimeUnit timeUnit = chooseTimeUnit(durationInNanos);
    return format("%d [%s]", timeUnit.convert(durationInNanos, TimeUnit.NANOSECONDS), timeUnit.toString().toLowerCase());
  }

  private static TimeUnit chooseTimeUnit(long intervalInNanos) {
    // TimeUnit.values() returns elements of TimeUnit in declared order
    // And they are declared in ascending order.
    for (TimeUnit timeUnit : TimeUnit.values()) {
      if (1000 > timeUnit.convert(intervalInNanos, TimeUnit.NANOSECONDS)) {
        return timeUnit;
      }
    }
    return TimeUnit.DAYS;
  }

  public static String spaces(int numSpaces) {
    StringBuilder ret = new StringBuilder();
    for (int i = 0; i < numSpaces; i++) {
      ret.append(" ");
    }
    return ret.toString();
  }

  public static String summary(String s) {
    return requireNonNull(s).length() > 40
        ? s.substring(0, 40) + "..."
        : s;
  }

  public static String toStringIfOverriddenOrNoname(Object o) {
    return objectToStringIfOverridden(o, fallbackFormatter());
  }

  public static Function<Object, String> fallbackFormatter() {
    return obj -> "(noname)";
  }

  public static String objectToStringIfOverridden(Object o, Function<Object, String> formatter) {
    requireNonNull(formatter);
    try {
      return !Objects.equals(o.getClass().getMethod("toString"), OBJECT_TO_STRING_METHOD) ?
          o.toString() :
          formatter.apply(o);
    } catch (NoSuchMethodException e) {
      return formatter.apply(o);
    }
  }

  private static Method objectToStringMethod() {
    try {
      return Object.class.getMethod("toString");
    } catch (NoSuchMethodException e) {
      throw wrap(e);
    }
  }

  public static <T, R> Function<T, R> memoize(Function<T, R> function) {
    Map<T, R> memo = new ConcurrentHashMap<>();
    return t -> memo.computeIfAbsent(t, function);
  }
}