first commit
This commit is contained in:
201
java/queue/ReflectionTest.java
Normal file
201
java/queue/ReflectionTest.java
Normal file
@@ -0,0 +1,201 @@
|
||||
package queue;
|
||||
|
||||
import base.Asserts;
|
||||
import base.TestCounter;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
||||
*/
|
||||
public class ReflectionTest {
|
||||
private static final boolean DEBUG = false;
|
||||
public static final Collector<CharSequence, ?, String> JOINER = Collectors.joining(", ", "(", ")");
|
||||
|
||||
protected final Set<Method> methods = new HashSet<>();
|
||||
|
||||
public ReflectionTest() {
|
||||
Asserts.checkAssert(getClass());
|
||||
}
|
||||
|
||||
protected void checkResult(final String call, final Object expected, final Object actual) {
|
||||
Asserts.assertEquals(call, expected, actual);
|
||||
}
|
||||
|
||||
protected <T> T checking(final TestCounter counter, final Class<T> type, final T reference, final T tested) {
|
||||
return proxy(type, (proxy, method, args) -> {
|
||||
final String call = method.getName() + (args == null ? "()" : args(args));
|
||||
if (DEBUG) {
|
||||
counter.format("\t%s%n", call);
|
||||
}
|
||||
methods.add(method);
|
||||
final Object expected;
|
||||
final Object actual;
|
||||
try {
|
||||
expected = method.invoke(reference, args);
|
||||
actual = method.invoke(tested, args);
|
||||
} catch (final InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
checkResult(call, expected, actual);
|
||||
return actual;
|
||||
});
|
||||
}
|
||||
|
||||
protected static String args(final Object[] args) {
|
||||
return Stream.of(args).map(Objects::toString).collect(JOINER);
|
||||
}
|
||||
|
||||
protected static <T> T proxy(final Class<T> type, final InvocationHandler handler) {
|
||||
return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, handler));
|
||||
}
|
||||
|
||||
|
||||
private interface IMethod {
|
||||
Object invoke(final Object instance, final Object[] args) throws InvocationTargetException, IllegalAccessException;
|
||||
}
|
||||
|
||||
|
||||
protected enum Mode {
|
||||
MODULE("Module") {
|
||||
@Override
|
||||
IMethod lookupMethod(final Class<?> type, final Method method) {
|
||||
return findMethod(true, type, method);
|
||||
}
|
||||
},
|
||||
ADT("ADT") {
|
||||
@Override
|
||||
IMethod lookupMethod(final Class<?> type, final Method method) {
|
||||
final Class<?>[] argTypes = Stream.concat(Stream.of(type), Stream.of(method.getParameterTypes()))
|
||||
.toArray(Class<?>[]::new);
|
||||
final Method actual = findMethod(true, type, method.getName(), argTypes);
|
||||
|
||||
final Object[] a = new Object[method.getParameterTypes().length + 1];
|
||||
return (instance, args) -> {
|
||||
a[0] = instance;
|
||||
if (args != null) {
|
||||
System.arraycopy(args, 0, a, 1, args.length);
|
||||
}
|
||||
return actual.invoke(null, a);
|
||||
};
|
||||
}
|
||||
},
|
||||
CLASS("") {
|
||||
@Override
|
||||
IMethod lookupMethod(final Class<?> type, final Method method) {
|
||||
return findMethod(false, type, method);
|
||||
}
|
||||
};
|
||||
|
||||
private static IMethod findMethod(final boolean isStatic, final Class<?> type, final Method method) {
|
||||
return findMethod(isStatic, type, method.getName(), method.getParameterTypes())::invoke;
|
||||
}
|
||||
|
||||
private static Method findMethod(final boolean isStatic, final Class<?> type, final String name, final Class<?>[] parameterTypes) {
|
||||
final String description = name + args(parameterTypes);
|
||||
final Method method;
|
||||
try {
|
||||
method = type.getMethod(name, parameterTypes);
|
||||
} catch (final NoSuchMethodException e) {
|
||||
throw Asserts.error("Missing method %s in %s", description, type);
|
||||
}
|
||||
if (isStatic != Modifier.isStatic(method.getModifiers())) {
|
||||
throw Asserts.error("Method %s in %s %s be static", description, type, isStatic ? "must" : "must not");
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
private final String suffix;
|
||||
|
||||
Mode(final String suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
abstract IMethod lookupMethod(final Class<?> type, final Method method);
|
||||
|
||||
private Class<?> loadClass(final String baseName) {
|
||||
final String className = baseName + suffix;
|
||||
try {
|
||||
final URL url = Paths.get(".").toUri().toURL();
|
||||
//noinspection resource
|
||||
return new URLClassLoader(new URL[]{url}).loadClass(className);
|
||||
} catch (final MalformedURLException e) {
|
||||
throw new AssertionError("Cannot load classes from .", e);
|
||||
} catch (final ClassNotFoundException e) {
|
||||
throw new AssertionError("Cannot find class %s: %s".formatted(className, e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static class ProxyFactory<T> {
|
||||
private final Class<T> type;
|
||||
private final Map<Method, IMethod> methods;
|
||||
private final Constructor<?> constructor;
|
||||
protected final Class<?> implementation;
|
||||
|
||||
protected ProxyFactory(final Class<T> type, final Mode mode, final String baseName) {
|
||||
implementation = mode.loadClass(baseName);
|
||||
try {
|
||||
constructor = implementation.getConstructor();
|
||||
} catch (final NoSuchMethodException e) {
|
||||
throw Asserts.error("%s should have default constructor", implementation.getName());
|
||||
}
|
||||
|
||||
this.type = type;
|
||||
methods = Stream.of(type.getMethods())
|
||||
.filter(method -> !method.isAnnotationPresent(Ignore.class))
|
||||
.collect(Collectors.toMap(Function.identity(), method -> mode.lookupMethod(implementation, method)));
|
||||
}
|
||||
|
||||
public T create() {
|
||||
try {
|
||||
return wrap(constructor.newInstance());
|
||||
} catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new AssertionError("Cannot create " + implementation.getName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private T wrap(final Object instance) {
|
||||
Asserts.assertEquals("same class", implementation, instance.getClass());
|
||||
|
||||
return proxy(type, (proxy, method, args) -> {
|
||||
if (method.getName().equals("toString")) {
|
||||
return instance.toString();
|
||||
}
|
||||
final Object result;
|
||||
try {
|
||||
result = methods.get(method).invoke(instance, args);
|
||||
} catch (final InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
if (method.isAnnotationPresent(Wrap.class)) {
|
||||
return wrap(result);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Inherited
|
||||
protected @interface Ignore {}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Inherited
|
||||
protected @interface Wrap {}
|
||||
}
|
||||
Reference in New Issue
Block a user