package org.cryptocoinpartners.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
/**
* Utility methods to simplify comparisons.
* <p>
* This is a thread-safe static utility class.
*/
public final class CompareUtils {
/**
* Restricted constructor.
*/
private CompareUtils() {
}
//-------------------------------------------------------------------------
/**
* Compares two objects finding the maximum.
*
* @param <T> the object type
* @param a item that compareTo is called on, may be null
* @param b item that is being compared, may be null
* @return the maximum of the two objects, null if both null
*/
public static <T extends Comparable<? super T>> T max(T a, T b) {
if (a != null && b != null) {
return a.compareTo(b) >= 0 ? a : b;
}
if (a == null) {
if (b == null) {
return null;
} else {
return b;
}
} else {
return a;
}
}
/**
* Compares two objects finding the minimum.
*
* @param <T> the object type
* @param a item that compareTo is called on, may be null
* @param b item that is being compared, may be null
* @return the minimum of the two objects, null if both null
*/
public static <T extends Comparable<? super T>> T min(T a, T b) {
if (a != null && b != null) {
return a.compareTo(b) <= 0 ? a : b;
}
if (a == null) {
if (b == null) {
return null;
} else {
return b;
}
} else {
return a;
}
}
//-------------------------------------------------------------------------
/**
* Compares two objects, either of which might be null, sorting nulls low.
*
* @param <E> the type of object being compared
* @param a item that compareTo is called on
* @param b item that is being compared
* @return negative when a less than b, zero when equal, positive when greater
*/
public static <E> int compareWithNullLow(Comparable<E> a, E b) {
if (a == null) {
return b == null ? 0 : -1;
} else if (b == null) {
return 1; // a not null
} else {
return a.compareTo(b);
}
}
/**
* Compares two objects, either of which might be null, sorting nulls high.
*
* @param <E> the type of object being compared
* @param a item that compareTo is called on
* @param b item that is being compared
* @return negative when a less than b, zero when equal, positive when greater
*/
public static <E> int compareWithNullHigh(Comparable<E> a, E b) {
if (a == null) {
return b == null ? 0 : 1;
} else if (b == null) {
return -1; // a not null
} else {
return a.compareTo(b);
}
}
//-------------------------------------------------------------------------
/**
* Compare two doubles to see if they're 'closely' equal.
* <p>
* This handles rounding errors which can mean the results of double precision computations
* lead to small differences in results.
* The definition 'close' is that the difference is less than 10^-15 (1E-15).
* If a different maximum allowed difference is required, use the other version of this method.
*
* @param a the first value
* @param b the second value
* @return true, if a and b are equal to within 10^-15, false otherwise
*/
public static boolean closeEquals(double a, double b) {
if (Double.isInfinite(a)) {
return (a == b);
}
return (Math.abs(a - b) < 1E-15);
}
/**
* Compare two doubles to see if they're 'closely' equal.
* <p>
* This handles rounding errors which can mean the results of double precision computations
* lead to small differences in results.
* The definition 'close' is that the absolute difference is less than the specified difference.
*
* @param a the first value
* @param b the second value
* @param maxDifference the maximum difference to allow
* @return true, if a and b are equal to within the tolerance
*/
public static boolean closeEquals(double a, double b, double maxDifference) {
if (Double.isInfinite(a)) {
return (a == b);
}
return (Math.abs(a - b) < maxDifference);
}
/**
* Compare two doubles to see if they're 'closely' equal.
* <p>
* This handles rounding errors which can mean the results of double precision computations
* lead to small differences in results.
* This method returns the difference to indicate how the first differs from the second.
*
* @param a the first value
* @param b the second value
* @param maxDifference the maximum difference to allow while still considering the values equal
* @return the value 0 if a and b are equal to within the tolerance; a value less than 0 if a is numerically less
* than b; and a value greater than 0 if a is numerically greater than b.
*/
public static int compareWithTolerance(double a, double b, double maxDifference) {
if (a == Double.POSITIVE_INFINITY) {
return (a == b ? 0 : 1);
} else if (a == Double.NEGATIVE_INFINITY) {
return (a == b ? 0 : -1);
} else if (b == Double.POSITIVE_INFINITY) {
return (a == b ? 0 : -1);
} else if (b == Double.NEGATIVE_INFINITY) {
return (a == b ? 0 : 1);
}
if (Math.abs(a - b) < maxDifference) {
return 0;
}
return (a < b) ? -1 : 1;
}
/**
* Compare two items, with the ordering determined by a list of those items.
* <p>
* Nulls are permitted and sort low, and if a or b are not in the list, then
* the result of comparing the toString() output is used instead.
*
* @param <T> the list type
* @param list the list, not null
* @param a the first object, may be null
* @param b the second object, may be null
* @return 0, if equal, -1 if a < b, +1 if a > b
*/
public static <T> int compareByList(List<T> list, T a, T b) {
if (a == null) {
if (b == null) {
return 0;
} else {
return -1;
}
} else {
if (b == null) {
return 1;
} else {
if (list.contains(a) && list.contains(b)) {
return list.indexOf(a) - list.indexOf(b);
} else {
return compareWithNullLow(a.toString(), b.toString());
}
}
}
}
private static final Comparator<? super Constructor<?>> CTOR_COMPARATOR = new Comparator<Constructor<?>>() {
@Override
public int compare(Constructor<?> ctorA, Constructor<?> ctorB) {
Class<?>[] params1 = ctorA.getParameterTypes();
Class<?>[] params2 = ctorB.getParameterTypes();
if (params1.length != params2.length)
throw new IllegalArgumentException(ctorA + " can't be compared to " + ctorB);
for (int i = 0; i < params1.length; i++) {
Class<?> aClass = params1[i];
Class<?> bClass = params2[i];
if (!aClass.equals(bClass)) {
if (aClass.isAssignableFrom(bClass))
return 1;
if (bClass.isAssignableFrom(aClass))
return -1;
throw new IllegalArgumentException(ctorA + " can't be compared to " + ctorB + ": args at pos " + i + " aren't comparable: " + aClass
+ " vs " + bClass);
}
}
return 0;
}
};
public static <T> T tryToCreateBestMatch(Class<T> aClass, Object[] oa) throws InstantiationException, IllegalAccessException, InvocationTargetException {
//noinspection unchecked
Constructor<T>[] declaredConstructors = (Constructor<T>[]) aClass.getDeclaredConstructors();
Class<?>[] argClasses = getClasses(oa);
List<Constructor<T>> matchedCtors = new ArrayList<>();
for (Constructor<T> ctr : declaredConstructors) {
Class<?>[] parameterTypes = ctr.getParameterTypes();
if (ctorMatches(parameterTypes, argClasses)) {
matchedCtors.add(ctr);
}
}
if (matchedCtors.isEmpty())
return null;
Collections.sort(matchedCtors, CTOR_COMPARATOR);
return matchedCtors.get(0).newInstance(oa);
}
private static boolean ctorMatches(Class<?>[] ctrParamTypes, Class<?>[] argClasses) {
if (ctrParamTypes.length != argClasses.length)
return false;
for (int i = 0; i < ctrParamTypes.length; i++) {
Class<?> ctrParamType = ctrParamTypes[i];
Class<?> argClass = argClasses[i];
if (!compatible(ctrParamType, argClass))
return false;
}
return true;
}
private static boolean compatible(Class<?> ctrParamType, Class<?> argClass) {
if (ctrParamType.isAssignableFrom(argClass))
return true;
if (ctrParamType.isPrimitive())
return compareAgainstPrimitive(ctrParamType.getName(), argClass);
return false;
}
private static boolean compareAgainstPrimitive(String primitiveType, Class<?> argClass) {
switch (primitiveType) {
case "short":
case "byte":
case "int":
case "long":
return INTEGER_WRAPPERS.contains(argClass.getName());
case "float":
case "dobule":
return FP_WRAPPERS.contains(argClass.getName());
}
throw new IllegalArgumentException("Unexpected primitive type?!?!: " + primitiveType);
}
private static final HashSet<String> INTEGER_WRAPPERS = new HashSet<>(Arrays.asList("java.lang.Integer", "java.lang.Short", "java.lang.Byte",
"java.lang.Long"));
private static final HashSet<String> FP_WRAPPERS = new HashSet<>(Arrays.asList("java.lang.Float", "java.lang.Double"));
private static Class<?>[] getClasses(Object[] oa) {
if (oa == null)
return new Class[0];
Class<?>[] ret = new Class[oa.length];
for (int i = 0; i < oa.length; i++) {
ret[i] = oa[i] == null ? Object.class : oa[i].getClass();
}
return ret;
}
}