com.github.dakusui.crest.utils.InternalUtils Maven / Gradle / Ivy
package com.github.dakusui.crest.utils;
import com.github.dakusui.crest.core.Call.Arg;
import com.github.dakusui.crest.core.Report;
import com.github.dakusui.crest.utils.printable.Functions;
import org.junit.ComparisonFailure;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Consumer;
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.stream.Collectors.toList;
public enum InternalUtils {
;
public static final Consumer NOP = e -> {
};
public static final Class>[][] PRIMITIVE_WRAPPER_TABLE = {
{ boolean.class, Boolean.class },
{ byte.class, Byte.class },
{ char.class, Character.class },
{ short.class, Short.class },
{ int.class, Integer.class },
{ long.class, Long.class },
{ float.class, Float.class },
{ double.class, Double.class },
};
/**
* Tries to find a method whose name is {@code methodName} from a given class {@code aClass}
* and that can be invoked with parameter values {@code args}.
*
* Unless one and only one method is found appropriate, an exception will be
* thrown.
*
* In this version, boxing/unboxing and casting are not attempted to determine
* the methodto be returned during the search. This means, if there are overloaded
* methods of the {@code methodName} that can be invoked with {@code args}, this
* method will fail. Also even if there is a method of the {@code methodName}
* that can be invoked if boxing/unboxing happens, this method will fail.
*
* @param aClass A class from which the method is searched.
* @param methodName A name of the method
* @param args Arguments which should be given to the method
* @return A method for given class {@code aClass}, {@code method}, and {@code args}.
*/
public static Method findMethod(Class> aClass, String methodName, Object[] args) {
MethodSelector methodSelector = new MethodSelector.Default()
.andThen(new MethodSelector.PreferNarrower())
.andThen(new MethodSelector.PreferExact());
return getIfOnlyOneElseThrow(
methodSelector,
methodSelector.select(
Arrays.stream(
getMethods(aClass)
).filter(
(Method m) -> m.getName().equals(methodName)
).collect(
LinkedList::new,
InternalUtils::addMethodIfNecessary,
(List methods, List methods2) -> methods2.forEach(
method -> {
addMethodIfNecessary(methods, method);
})),
args
),
aClass,
methodName,
args
);
}
/*
* Based on BaseDescription#appendValue() of Hamcrest
*
* http://hamcrest.org/JavaHamcrest/
*/
public static String formatValue(Object value) {
if (value == null)
return "null";
if (value instanceof String)
return String.format("\"%s\"", toJavaSyntax((String) value));
if (value instanceof Character)
return String.format("\"%s\"", toJavaSyntax((Character) value));
if (value instanceof Short)
return String.format("<%ss>", value);
if (value instanceof Long)
return String.format("<%sL>", value);
if (value instanceof Float)
return String.format("<%sF>", value);
if (value.getClass().isArray())
return arrayToString(value);
if (value instanceof Throwable)
return formatThrowable(getRootCause((Throwable) value));
return format("<%s>", summarize(value));
}
private static Throwable getRootCause(Throwable throwable) {
if (throwable.getCause() == null)
return throwable;
return getRootCause(throwable.getCause());
}
private static String formatThrowable(Throwable throwable) {
return String.format("%s(%s)", throwable.getClass().getCanonicalName(), throwable.getMessage());
}
@SuppressWarnings("unchecked")
public static String summarize(Object value) {
if (value == null)
return "null";
if (value instanceof Collection) {
Collection collection = (Collection) value;
if (collection.size() < 4)
return String.format("(%s)",
String.join(
",",
(List) collection.stream().map(InternalUtils::summarize).collect(toList())
));
Iterator> i = collection.iterator();
return format("(%s,%s,%s...;%s)",
summarize(i.next()),
summarize(i.next()),
summarize(i.next()),
collection.size()
);
}
if (value instanceof Object[])
return summarize(asList((Object[]) value));
if (value instanceof String) {
String s = (String) value;
if (s.length() > 20)
s = s.substring(0, 12) + "..." + s.substring(s.length() - 5);
return String.format("\"%s\"", s);
}
String ret = value.toString();
ret = ret.contains("$")
? ret.substring(ret.lastIndexOf("$") + 1)
: ret;
return ret;
}
@SuppressWarnings("unchecked")
public static R invokeMethod(Object target, String methodName, Object[] args) {
try {
Method m = findMethod(Objects.requireNonNull(target).getClass(), methodName, replaceTargetInArray(target, args));
boolean accessible = m.isAccessible();
try {
m.setAccessible(true);
return (R) m.invoke(target, replaceTargetInArray(target, replaceArgInArray(args)));
} finally {
m.setAccessible(accessible);
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
@SuppressWarnings("unchecked")
public static R invokeStaticMethod(Class klass, Object target, String methodName, Object[] args) {
try {
Method m = findMethod(Objects.requireNonNull(klass), methodName, replaceTargetInArray(target, args));
boolean accessible = m.isAccessible();
try {
m.setAccessible(true);
return (R) m.invoke(null, replaceTargetInArray(target, replaceArgInArray(args)));
} finally {
m.setAccessible(accessible);
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getTargetException());
}
}
public static String formatFunction(Function, ?> function, @SuppressWarnings("SameParameterValue") String variableName) {
return format("%s%s", variableName, function.toString());
}
@SuppressWarnings("unchecked")
public static boolean areArgsCompatible(Class[] formalParameters, Object[] args) {
if (formalParameters.length != args.length)
return false;
for (int i = 0; i < args.length; i++) {
if (args[i] == null)
if (formalParameters[i].isPrimitive())
return false;
else
continue;
if (!withBoxingIsAssignableFrom(formalParameters[i], toClass(args[i])))
return false;
}
return true;
}
static boolean withBoxingIsAssignableFrom(Class> a, Class> b) {
if (a.isAssignableFrom(b))
return true;
return toWrapperIfPrimitive(a).isAssignableFrom(toWrapperIfPrimitive(b));
}
private static Method[] getMethods(Class> aClass) {
return aClass.getMethods();
}
private static void addMethodIfNecessary(List methods, Method method) {
Optional found = methods.stream().filter(
each -> Arrays.equals(each.getParameterTypes(), method.getParameterTypes())
).findAny();
if (found.isPresent()) {
if (found.get().getDeclaringClass().isAssignableFrom(method.getDeclaringClass()))
methods.remove(found.get());
}
methods.add(method);
}
private static Class> toClass(Object value) {
if (value == null)
return null;
if (value instanceof Arg)
return ((Arg) value).type();
return value.getClass();
}
public static String toSimpleClassName(Object value) {
Class klass = toClass(value);
return klass == null
? null
: klass.getSimpleName();
}
private static Class> toWrapperIfPrimitive(Class> in) {
for (Class>[] pair : PRIMITIVE_WRAPPER_TABLE) {
if (Objects.equals(in, pair[0]))
return pair[1];
}
return in;
}
private static Method getIfOnlyOneElseThrow(MethodSelector selector, List foundMethods, Class> aClass, String methodName, Object[] args) {
if (foundMethods.isEmpty())
throw new RuntimeException(String.format(
"Method matching '%s%s' was not found by selector=%s in %s.",
methodName,
Arrays.asList(args),
selector,
aClass.getCanonicalName()
));
if (foundMethods.size() == 1)
return foundMethods.get(0);
throw new RuntimeException(String.format(
"Methods matching '%s%s' were found more than one in %s by selector=%s.: %s ",
methodName,
summarize(args),
aClass.getCanonicalName(),
selector,
summarizeMethods(foundMethods)
));
}
private static List summarizeMethods(List methods) {
return methods.stream().map(
method -> method.toString().replace(
method.getDeclaringClass().getCanonicalName() + "." + method.getName(),
method.getName()
)
).collect(toList());
}
private static String arrayToString(Object arr) {
StringBuilder b = new StringBuilder();
b.append("[");
int length = Array.getLength(arr);
if (length > 0) {
for (int i = 0; i < length - 1; i++) {
b.append(Array.get(arr, i));
b.append(",");
}
b.append(Array.get(arr, length - 1));
}
b.append("]");
return b.toString();
}
private static String toJavaSyntax(String unformatted) {
StringBuilder b = new StringBuilder();
for (int i = 0; i < unformatted.length(); i++) {
b.append(toJavaSyntax(unformatted.charAt(i)));
}
return b.toString();
}
private static String toJavaSyntax(char ch) {
switch (ch) {
case '"':
return "\\\"";
case '\n':
return ("\\n");
case '\r':
return ("\\r");
case '\t':
return ("\\t");
default:
return Character.toString(ch);
}
}
public static String composeComparisonText(String message, Report report) {
return new ComparisonFailure(message, report.expectation(), report.mismatch()).getMessage();
}
public static RuntimeException rethrow(Throwable e) {
if (e instanceof RuntimeException)
throw (RuntimeException) e;
if (e instanceof Error)
throw (Error) e;
return new RuntimeException(e);
}
public static Object[] replaceArgInArray(Object[] args) {
return Arrays.stream(args)
.map(e -> e instanceof Arg
? ((Arg) e).value()
: e)
.toArray();
}
public static Object[] replaceTargetInArray(Object target, Object[] args) {
return Arrays.stream(args)
.map(e -> replaceTarget(e, target)).toArray();
}
public static Object replaceTarget(Object on, I target) {
return on == Functions.THIS ?
target :
on instanceof Object[] ?
replaceTargetInArray(target, (Object[]) on) :
on;
}
public static T require(T value, Predicate condition, Function exceptionFactory) {
if (condition.test(value))
return value;
throw exceptionFactory.apply(String.format("Value <%s> did not meet the requirement <%s>", value, condition));
}
public static T requireArgument(T value, Predicate condition) {
return require(value, condition, IllegalArgumentException::new);
}
public static String spaces(int size) {
return times(' ', size);
}
public static String times(char c, int size) {
StringBuilder b = new StringBuilder();
for (int i = 0; i < size; i++)
b.append(c);
return b.toString();
}
}