MethodSelector.java
package com.github.dakusui.pcond.core.refl;
import com.github.dakusui.pcond.internals.InternalChecks;
import com.github.dakusui.pcond.internals.InternalUtils;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.IntStream;
import static com.github.dakusui.pcond.core.refl.MethodSelector.Utils.isAssignableWithBoxingFrom;
import static com.github.dakusui.pcond.internals.InternalChecks.requireArgument;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
/**
* //@formater:off
* An interface representing an object that selects {@link Method}s from given ones.
*
* This interface is used to choose methods that are appropriate to invoke with
* given arguments.
* //@formater:on
*/
public interface MethodSelector extends Formattable {
/**
* Selects methods that can be invoked with given {@code args}.
*
* @param methods Methods from which returned methods are selected.
* @param args Arguments to be passed to selected methods.
* @return Selected methods.
*/
List<Method> select(List<Method> methods, Object[] args);
/**
* Returns a string that describes this object.
*
* @return A description of this object
*/
String describe();
/**
* Returns a composed {@link MethodSelector} that first applies this and
* then applies {@code another}.
*
* @param another The method selector to apply after this.
* @return The composed method selector
*/
default MethodSelector andThen(MethodSelector another) {
return new MethodSelector() {
@Override
public List<Method> select(List<Method> methods, Object[] args) {
return another.select(MethodSelector.this.select(methods, args), args);
}
@Override
public String describe() {
return format("%s&&%s", MethodSelector.this.describe(), another.describe());
}
};
}
/**
* Formats this object using the {@link MethodSelector#describe()} method.
*/
@Override
default void formatTo(Formatter formatter, int flags, int width, int precision) {
formatter.format("%s", this.describe());
}
class Default implements MethodSelector {
@Override
public List<Method> select(List<Method> methods, Object[] args) {
return methods
.stream()
.filter(m -> areArgsCompatible(m.getParameterTypes(), args))
.collect(toList());
}
@Override
public String describe() {
return "default";
}
private 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 (!isAssignableWithBoxingFrom(formalParameters[i], Utils.toClass(args[i])))
return false;
}
return true;
}
}
/**
* A method selector that selects "narrower" methods over "wider" ones.
*/
class PreferNarrower implements MethodSelector {
@Override
public List<Method> select(List<Method> methods, Object[] args) {
if (methods.size() < 2)
return methods;
List<Method> ret = new LinkedList<>();
for (Method i : methods) {
if (methods.stream().filter(j -> j != i).noneMatch(j -> compareNarrowness(j, i) > 0))
ret.add(i);
}
return ret;
}
@Override
public String describe() {
return "preferNarrower";
}
/**
* If {@code a} is 'narrower' than {@code b}, positive integer will be returned.
* If {@code b} is 'narrower' than {@code a}, negative integer will be returned.
* Otherwise {@code zero}.
*
* 'Narrower' means that every parameter of {@code a} is assignable to corresponding
* one of {@code b}, but any of {@code b} cannot be assigned to {@code a}'s
* corresponding parameter.
*
* @param a A method.
* @param b A method to be compared with {@code a}.
* @return a negative integer, zero, or a positive integer as method {@code a}
* is less compatible than, as compatible as, or more compatible than
* the method {@code b} object.
*/
private static int compareNarrowness(Method a, Method b) {
if (isCompatibleWith(a, b) && isCompatibleWith(b, a))
return 0;
if (!isCompatibleWith(a, b) && !isCompatibleWith(b, a))
return 0;
return isCompatibleWith(a, b)
? -1
: 1;
}
private static boolean isCompatibleWith(Method a, Method b) {
requireSameParameterCounts(a, b);
if (Objects.equals(a, b))
return true;
return IntStream
.range(0, a.getParameterCount())
.allMatch(i -> isAssignableWithBoxingFrom(a.getParameterTypes()[i], b.getParameterTypes()[i]));
}
private static void requireSameParameterCounts(Method a, Method b) {
requireArgument(
requireNonNull(a),
(Method v) -> v.getParameterCount() == requireNonNull(b).getParameterCount(),
() -> format("Parameter counts are different: a: %s, b: %s", a, b));
}
}
class PreferExact implements MethodSelector {
@Override
public List<Method> select(List<Method> methods, Object[] args) {
if (methods.size() < 2)
return methods;
List<Method> work = methods;
for (Object ignored : args) {
List<Method> tmp = new ArrayList<>(work);
if (!tmp.isEmpty()) {
work = tmp;
break;
}
}
return work;
}
@Override
public String describe() {
return "preferExact";
}
}
enum Utils {
;
static boolean isAssignableWithBoxingFrom(Class<?> a, Class<?> b) {
if (a.isAssignableFrom(b))
return true;
if (InternalChecks.isPrimitiveWrapperClassOrPrimitive(a) && InternalChecks.isPrimitiveWrapperClassOrPrimitive(b))
return InternalChecks.isWiderThanOrEqualTo(toWrapperIfPrimitive(a), toWrapperIfPrimitive(b));
return false;
}
private static Class<?> toWrapperIfPrimitive(Class<?> in) {
if (in.isPrimitive())
return InternalUtils.wrapperClassOf(in);
return in;
}
private static Class<?> toClass(Object value) {
return value.getClass();
}
}
}