package nl.ipo.cds.validation.execute;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class Compiler<C> {
private final static MethodHandle mapGetHandle = findMethod (Map.class, "get", MethodType.methodType (Object.class, Object.class));
private final static MethodHandle foldIntObjectsHandle = findStaticMethod (Compiler.class, "fold", MethodType.methodType (Object.class, Integer.TYPE, Object[].class));
private final static MethodHandle foldMapObjectsHandle = findStaticMethod (Compiler.class, "fold", MethodType.methodType (Map.class, int[].class, String.class, Object[].class));
public static MethodHandle findMethod (final Class<?> cls, final String name, final MethodType methodType) {
try {
return MethodHandles.lookup ().findVirtual (cls, name, methodType);
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError (e);
}
}
public static MethodHandle findStaticMethod (final Class<?> cls, final String name, final MethodType methodType) {
try {
return MethodHandles.lookup ().findStatic (cls, name, methodType);
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new AssertionError (e);
}
}
private final static HashMap<Class<?>, Class<?>> primitiveTypeMap = new HashMap<Class<?>, Class<?>> ();
static {
primitiveTypeMap.put (Byte.TYPE, Byte.class);
primitiveTypeMap.put (Character.TYPE, Character.class);
primitiveTypeMap.put (Short.TYPE, Short.class);
primitiveTypeMap.put (Integer.TYPE, Integer.class);
primitiveTypeMap.put (Long.TYPE, Long.class);
primitiveTypeMap.put (Float.TYPE, Float.class);
primitiveTypeMap.put (Double.TYPE, Double.class);
primitiveTypeMap.put (Boolean.TYPE, Boolean.class);
primitiveTypeMap.put (Void.TYPE, Void.class);
}
public final Class<C> contextClass;
private final LinkedHashMap<String, Class<?>> beanClasses;
public Compiler (final Class<C> contextClass) {
this (contextClass, new LinkedHashMap<String, Class<?>> ());
}
private Compiler (
final Class<C> contextClass,
final LinkedHashMap<String, Class<?>> beanClasses) {
if (contextClass == null) {
throw new NullPointerException ("contextClass cannot be null");
}
if (Object.class.equals (contextClass)) {
throw new IllegalArgumentException ("cannot use Object as a context class");
}
this.contextClass = contextClass;
this.beanClasses = beanClasses;
}
public Compiler<C> addMap (final String name) {
return addBean (name, Map.class);
}
public Compiler<C> addBean (final String name, final Class<?> beanClass) {
final LinkedHashMap<String, Class<?>> beans = new LinkedHashMap<String, Class<?>> (beanClasses);
beans.put (name, beanClass);
return new Compiler<C> (contextClass, beans);
}
public ExpressionExecutor<C> createGetAttributeExecutor (final String name, final ExecutableExpression<C, ?> expression) throws CompilerException {
final Class<?> type = expression.getResultType ();
// Numeric attribute names are resolved by looking for a "get" method with a single integer parameter,
// such as the get method of java.util.List.
if (name.matches ("^[0-9]+$")) {
final int index = Integer.parseInt (name);
int n = 0;
for (final Map.Entry<String, Class<?>> entry: beanClasses.entrySet ()) {
final Class<?> beanClass = entry.getValue ();
try {
final Method method = beanClass.getMethod ("get", Integer.TYPE);
if (method.getReturnType ().equals (type)) {
final MethodHandle methodHandle = MethodHandles.lookup ().unreflect (method);
return createGetAttributeListExecutor (expression, n, beanClass, methodHandle, index);
}
} catch (NoSuchMethodException | IllegalAccessException e) {
}
++ n;
}
throw new CompilerException ("No list found for index: " + name);
}
// Locate a getter in one of the input classes:
final String methodName = String.format ("get%s%s", name.substring (0, 1).toUpperCase (), name.substring (1));
int n = 0;
for (final Map.Entry<String, Class<?>> entry: beanClasses.entrySet ()) {
final Class<?> beanClass = entry.getValue ();
final Method method;
try {
method = beanClass.getMethod (methodName);
if (method.getReturnType ().equals (type) || (method.getReturnType ().isPrimitive () && primitiveTypeMap.get (method.getReturnType ()).equals (type))) {
method.setAccessible (true);
final MethodHandle methodHandle = MethodHandles
.lookup ()
.unreflect (method);
return createGetAttributeExecutor (expression, n, beanClass, methodHandle);
}
} catch (NoSuchMethodException | IllegalAccessException e) {
}
++ n;
}
// Locate a getter in the context object:
try {
final Method method = contextClass.getMethod (methodName);
if (method.getReturnType ().equals (type) || (method.getReturnType ().isPrimitive () && primitiveTypeMap.get (method.getReturnType ()).equals (type))) {
method.setAccessible (true);
final MethodHandle methodHandle = MethodHandles
.lookup ()
.unreflect (method);
return createGetAttributeExecutorForContext (expression, methodHandle);
}
} catch (NoSuchMethodException | IllegalAccessException e) {
}
// Fetch attributes from the maps:
final List<Integer> indices = new ArrayList<Integer> ();
n = 0;
for (final Map.Entry<String, Class<?>> entry: beanClasses.entrySet ()) {
final Class<?> beanClass = entry.getValue ();
if (Map.class.isAssignableFrom (beanClass)) {
indices.add (n);
}
++ n;
}
if (indices.isEmpty ()) {
throw new CompilerException (String.format ("Unable to locate attribute `%s`", name));
}
try {
if (indices.size () == 1) {
return createGetAttributeExecutor (expression, indices.get (0), name);
} else {
return createGetAttributeExecutor (expression, indices, name);
}
} catch (NoSuchMethodException e) {
throw new CompilerException (e);
} catch (SecurityException e) {
throw new CompilerException (e);
}
}
private ExpressionExecutor<C> createGetAttributeExecutor (final ExecutableExpression<C, ?> expression, final int n, final Class<?> beanClass, final MethodHandle methodHandle) throws CompilerException, NoSuchMethodException, SecurityException {
// The method may be from a superclass of the beanClass, in which case the accepted "this"
// reference is not of type beanClass and should be cast to the proper type (see asType invocation
// below):
final Class<?> firstParameterType = methodHandle.type ().parameterType (0);
assert (firstParameterType.isAssignableFrom (beanClass));
// Create a method handle that accepts an object array and returns the value from the getter.
// Index N is taken from the object array (using the method referred to by foldIntObjectsHandle).
// The N parameter is bound to the foldIntObjectsHandle methodhandle before using it as a filter.
final MethodHandle handle =
MethodHandles.filterArguments (
methodHandle,
0,
MethodHandles.insertArguments (
foldIntObjectsHandle,
0,
n
).asType (
MethodType.methodType (
firstParameterType,
Object[].class
)
)
);
return new ExpressionExecutor<C> (
expression,
Collections.<ExecutableExpression<C, ?>>emptyList (),
false,
true,
handle,
false
);
}
private ExpressionExecutor<C> createGetAttributeExecutor (final ExecutableExpression<C, ?> expression, final int n, final String name) throws CompilerException, NoSuchMethodException, SecurityException {
final MethodHandle handle =
MethodHandles.insertArguments (
MethodHandles.filterArguments (
mapGetHandle,
0,
MethodHandles.insertArguments (
foldIntObjectsHandle,
0,
n
).asType (
MethodType.methodType (
Map.class,
Object[].class
)
)
),
1,
name
);
return new ExpressionExecutor<C> (
expression,
Collections.<ExecutableExpression<C, ?>>emptyList (),
false,
true,
handle,
false
);
}
private ExpressionExecutor<C> createGetAttributeExecutor (final ExecutableExpression<C, ?> expression, final List<Integer> indicesList, final String name) throws NoSuchMethodException, SecurityException, CompilerException {
final int[] indices = new int[indicesList.size ()];
for (int i = 0; i < indices.length; ++ i) {
indices[i] = indicesList.get (i);
}
final MethodHandle handle =
MethodHandles.insertArguments (
MethodHandles.filterArguments (
mapGetHandle,
0,
MethodHandles.insertArguments (
foldMapObjectsHandle,
0,
indices,
name
).asType (
MethodType.methodType (
Map.class,
Object[].class
)
)
),
1,
name
);
return new ExpressionExecutor<C> (
expression,
Collections.<ExecutableExpression<C, ?>>emptyList (),
false,
true,
handle,
false
);
}
public ExpressionExecutor<C> createGetAttributeListExecutor (final ExecutableExpression<C, ?> expression, final int n, final Class<?> beanClass, final MethodHandle methodHandle, final int index) throws CompilerException, NoSuchMethodException, SecurityException {
final MethodHandle handle =
MethodHandles.insertArguments (
MethodHandles.filterArguments (
methodHandle,
0,
MethodHandles.insertArguments (
foldIntObjectsHandle,
0,
n
).asType (
MethodType.methodType (
beanClass,
Object[].class
)
)
),
1,
Integer.valueOf (index)
);
return new ExpressionExecutor<C> (
expression,
Collections.<ExecutableExpression<C, ?>>emptyList (),
false,
true,
handle,
false
);
}
public ExpressionExecutor<C> createGetAttributeExecutorForContext (final ExecutableExpression<C, ?> expression, final MethodHandle methodHandle) throws CompilerException {
return new ExpressionExecutor<C> (
expression,
Collections.<ExecutableExpression<C, ?>>emptyList (),
false,
true,
methodHandle.asType (MethodType.methodType (expression.getResultType (), contextClass)),
false
);
}
public Executor<C> compile (final ExecutableExpression<C, ?> rootExpression) throws CompilerException {
final ExecutionPlan<C> plan = new ExecutionPlan<C> ();
compileExpression (rootExpression, plan);
final List<Class<?>> beanTypes = new ArrayList<> (beanClasses.size ());
for (final Map.Entry<String, Class<?>> entry: beanClasses.entrySet ()) {
beanTypes.add (entry.getValue ());
}
return new Executor<C> (contextClass, beanTypes, plan);
}
public <I> I compile (final ExecutableExpression<C, ?> rootExpression, final Class<I> iface) throws CompilerException {
return compile (rootExpression).forInterface (iface);
}
private void compileExpression (final ExecutableExpression<C, ?> expression, final ExecutionPlan<C> plan) throws CompilerException {
final ExpressionExecutor<C> existingExecutor = plan.getExecutor (expression);
if (existingExecutor != null) {
return;
}
// Create a new executor for this expression:
final ExpressionExecutor<C> executor = expression.getExecutor (this);
// Compile all children of this expression:
for (final ExecutableExpression<C, ?> inputExpression: executor.inputs) {
compileExpression (inputExpression, plan);
}
// Add this executor to the execution plan:
plan.addExecutor (expression, executor);
plan.addExecutionStep (executor);
}
@SuppressWarnings("unused")
private static Object fold (final int n, final Object[] objects) {
return objects[n];
}
@SuppressWarnings("unused")
private static Map<?, ?> fold (final int[] indices, final String name, final Object[] objects) {
for (final int i: indices) {
final Map<?, ?> map = (Map<?, ?>)objects[i];
if (map.containsKey (name)) {
return map;
}
}
return Collections.emptyMap ();
}
}