|
@@ -19,371 +19,302 @@
|
|
|
|
|
|
package org.elasticsearch.painless;
|
|
|
|
|
|
-import org.elasticsearch.index.fielddata.ScriptDocValues;
|
|
|
-import org.elasticsearch.painless.Definition.Cast;
|
|
|
-import org.elasticsearch.painless.Definition.Field;
|
|
|
import org.elasticsearch.painless.Definition.Method;
|
|
|
-import org.elasticsearch.painless.Definition.Struct;
|
|
|
-import org.elasticsearch.painless.Definition.Transform;
|
|
|
-import org.elasticsearch.painless.Definition.Type;
|
|
|
+import org.elasticsearch.painless.Definition.RuntimeClass;
|
|
|
|
|
|
import java.lang.invoke.MethodHandle;
|
|
|
+import java.lang.invoke.MethodHandles;
|
|
|
+import java.lang.invoke.MethodType;
|
|
|
+import java.lang.invoke.MethodHandles.Lookup;
|
|
|
import java.lang.reflect.Array;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
|
|
|
+/**
|
|
|
+ * Support for dynamic type (def).
|
|
|
+ * <p>
|
|
|
+ * Dynamic types can invoke methods, load/store fields, and be passed as parameters to operators without
|
|
|
+ * compile-time type information.
|
|
|
+ * <p>
|
|
|
+ * Dynamic methods, loads, and stores involve locating the appropriate field or method depending
|
|
|
+ * on the receiver's class. For these, we emit an {@code invokedynamic} instruction that, for each new
|
|
|
+ * type encountered will query a corresponding {@code lookupXXX} method to retrieve the appropriate method.
|
|
|
+ * In most cases, the {@code lookupXXX} methods here will only be called once for a given call site, because
|
|
|
+ * caching ({@link DynamicCallSite}) generally works: usually all objects at any call site will be consistently
|
|
|
+ * the same type (or just a few types). In extreme cases, if there is type explosion, they may be called every
|
|
|
+ * single time, but simplicity is still more valuable than performance in this code.
|
|
|
+ * <p>
|
|
|
+ * Dynamic array loads and stores and operator functions (e.g. {@code +}) are called directly
|
|
|
+ * with {@code invokestatic}. Because these features cannot be overloaded in painless, they are hardcoded
|
|
|
+ * decision trees based on the only types that are possible. This keeps overhead low, and seems to be as fast
|
|
|
+ * on average as the more adaptive methodhandle caching.
|
|
|
+ */
|
|
|
public class Def {
|
|
|
- public static Object methodCall(final Object owner, final String name, final Definition definition,
|
|
|
- final Object[] arguments, final boolean[] typesafe) {
|
|
|
- final Method method = getMethod(owner, name, definition);
|
|
|
-
|
|
|
- if (method == null) {
|
|
|
- throw new IllegalArgumentException("Unable to find dynamic method [" + name + "] " +
|
|
|
- "for class [" + owner.getClass().getCanonicalName() + "].");
|
|
|
- }
|
|
|
|
|
|
- final MethodHandle handle = method.handle;
|
|
|
- final List<Type> types = method.arguments;
|
|
|
- final Object[] parameters = new Object[arguments.length + 1];
|
|
|
+ /**
|
|
|
+ * Looks up handle for a dynamic method call.
|
|
|
+ * <p>
|
|
|
+ * A dynamic method call for variable {@code x} of type {@code def} looks like:
|
|
|
+ * {@code x.method(args...)}
|
|
|
+ * <p>
|
|
|
+ * This method traverses {@code recieverClass}'s class hierarchy (including interfaces)
|
|
|
+ * until it finds a matching whitelisted method. If one is not found, it throws an exception.
|
|
|
+ * Otherwise it returns a handle to the matching method.
|
|
|
+ * <p>
|
|
|
+ * @param receiverClass Class of the object to invoke the method on.
|
|
|
+ * @param name Name of the method.
|
|
|
+ * @param definition Whitelist to check.
|
|
|
+ * @return pointer to matching method to invoke. never returns null.
|
|
|
+ * @throws IllegalArgumentException if no matching whitelisted method was found.
|
|
|
+ */
|
|
|
+ static MethodHandle lookupMethod(Class<?> receiverClass, String name, Definition definition) {
|
|
|
+ // check whitelist for matching method
|
|
|
+ for (Class<?> clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) {
|
|
|
+ RuntimeClass struct = definition.runtimeMap.get(clazz);
|
|
|
|
|
|
- parameters[0] = owner;
|
|
|
+ if (struct != null) {
|
|
|
+ Method method = struct.methods.get(name);
|
|
|
+ if (method != null) {
|
|
|
+ return method.handle;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (types.size() != arguments.length) {
|
|
|
- throw new IllegalArgumentException("When dynamically calling [" + name + "] from class " +
|
|
|
- "[" + owner.getClass() + "] expected [" + types.size() + "] arguments," +
|
|
|
- " but found [" + arguments.length + "].");
|
|
|
- }
|
|
|
+ for (Class<?> iface : clazz.getInterfaces()) {
|
|
|
+ struct = definition.runtimeMap.get(iface);
|
|
|
|
|
|
- try {
|
|
|
- for (int count = 0; count < arguments.length; ++count) {
|
|
|
- if (typesafe[count]) {
|
|
|
- parameters[count + 1] = arguments[count];
|
|
|
- } else {
|
|
|
- final Transform transform = getTransform(arguments[count].getClass(), types.get(count).clazz, definition);
|
|
|
- parameters[count + 1] = transform == null ? arguments[count] : transform.method.handle.invoke(arguments[count]);
|
|
|
+ if (struct != null) {
|
|
|
+ Method method = struct.methods.get(name);
|
|
|
+ if (method != null) {
|
|
|
+ return method.handle;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- return handle.invokeWithArguments(parameters);
|
|
|
- } catch (Throwable throwable) {
|
|
|
- throw new IllegalArgumentException("Error invoking method [" + name + "] " +
|
|
|
- "with owner class [" + owner.getClass().getCanonicalName() + "].", throwable);
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- @SuppressWarnings({ "unchecked", "rawtypes" })
|
|
|
- public static void fieldStore(final Object owner, Object value, final String name,
|
|
|
- final Definition definition, final boolean typesafe) {
|
|
|
- final Field field = getField(owner, name, definition);
|
|
|
- MethodHandle handle = null;
|
|
|
+ // no matching methods in whitelist found
|
|
|
+ throw new IllegalArgumentException("Unable to find dynamic method [" + name + "] " +
|
|
|
+ "for class [" + receiverClass.getCanonicalName() + "].");
|
|
|
+ }
|
|
|
|
|
|
- if (field == null) {
|
|
|
- final String set = "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
|
|
|
- final Method method = getMethod(owner, set, definition);
|
|
|
+ /** pointer to Array.getLength(Object) */
|
|
|
+ private static final MethodHandle ARRAY_LENGTH;
|
|
|
+ /** pointer to Map.get(Object) */
|
|
|
+ private static final MethodHandle MAP_GET;
|
|
|
+ /** pointer to Map.put(Object,Object) */
|
|
|
+ private static final MethodHandle MAP_PUT;
|
|
|
+ /** pointer to List.get(int) */
|
|
|
+ private static final MethodHandle LIST_GET;
|
|
|
+ /** pointer to List.set(int,Object) */
|
|
|
+ private static final MethodHandle LIST_SET;
|
|
|
+ static {
|
|
|
+ Lookup lookup = MethodHandles.publicLookup();
|
|
|
+ try {
|
|
|
+ // TODO: maybe specialize handles for different array types. this may be slower, but simple :)
|
|
|
+ ARRAY_LENGTH = lookup.findStatic(Array.class, "getLength",
|
|
|
+ MethodType.methodType(int.class, Object.class));
|
|
|
+ MAP_GET = lookup.findVirtual(Map.class, "get",
|
|
|
+ MethodType.methodType(Object.class, Object.class));
|
|
|
+ MAP_PUT = lookup.findVirtual(Map.class, "put",
|
|
|
+ MethodType.methodType(Object.class, Object.class, Object.class));
|
|
|
+ LIST_GET = lookup.findVirtual(List.class, "get",
|
|
|
+ MethodType.methodType(Object.class, int.class));
|
|
|
+ LIST_SET = lookup.findVirtual(List.class, "set",
|
|
|
+ MethodType.methodType(Object.class, int.class, Object.class));
|
|
|
+ } catch (ReflectiveOperationException e) {
|
|
|
+ throw new AssertionError(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Looks up handle for a dynamic field getter (field load)
|
|
|
+ * <p>
|
|
|
+ * A dynamic field load for variable {@code x} of type {@code def} looks like:
|
|
|
+ * {@code y = x.field}
|
|
|
+ * <p>
|
|
|
+ * The following field loads are allowed:
|
|
|
+ * <ul>
|
|
|
+ * <li>Whitelisted {@code field} from receiver's class or any superclasses.
|
|
|
+ * <li>Whitelisted method named {@code getField()} from receiver's class/superclasses/interfaces.
|
|
|
+ * <li>Whitelisted method named {@code isField()} from receiver's class/superclasses/interfaces.
|
|
|
+ * <li>The {@code length} field of an array.
|
|
|
+ * <li>The value corresponding to a map key named {@code field} when the receiver is a Map.
|
|
|
+ * <li>The value in a list at element {@code field} (integer) when the receiver is a List.
|
|
|
+ * </ul>
|
|
|
+ * <p>
|
|
|
+ * This method traverses {@code recieverClass}'s class hierarchy (including interfaces)
|
|
|
+ * until it finds a matching whitelisted getter. If one is not found, it throws an exception.
|
|
|
+ * Otherwise it returns a handle to the matching getter.
|
|
|
+ * <p>
|
|
|
+ * @param receiverClass Class of the object to retrieve the field from.
|
|
|
+ * @param name Name of the field.
|
|
|
+ * @param definition Whitelist to check.
|
|
|
+ * @return pointer to matching field. never returns null.
|
|
|
+ * @throws IllegalArgumentException if no matching whitelisted field was found.
|
|
|
+ */
|
|
|
+ static MethodHandle lookupGetter(Class<?> receiverClass, String name, Definition definition) {
|
|
|
+ // first try whitelist
|
|
|
+ for (Class<?> clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) {
|
|
|
+ RuntimeClass struct = definition.runtimeMap.get(clazz);
|
|
|
|
|
|
- if (method != null) {
|
|
|
- handle = method.handle;
|
|
|
+ if (struct != null) {
|
|
|
+ MethodHandle handle = struct.getters.get(name);
|
|
|
+ if (handle != null) {
|
|
|
+ return handle;
|
|
|
+ }
|
|
|
}
|
|
|
- } else {
|
|
|
- handle = field.setter;
|
|
|
- }
|
|
|
|
|
|
- if (handle != null) {
|
|
|
- try {
|
|
|
- if (!typesafe) {
|
|
|
- final Transform transform = getTransform(value.getClass(), handle.type().parameterType(1), definition);
|
|
|
+ for (final Class<?> iface : clazz.getInterfaces()) {
|
|
|
+ struct = definition.runtimeMap.get(iface);
|
|
|
|
|
|
- if (transform != null) {
|
|
|
- value = transform.method.handle.invoke(value);
|
|
|
+ if (struct != null) {
|
|
|
+ MethodHandle handle = struct.getters.get(name);
|
|
|
+ if (handle != null) {
|
|
|
+ return handle;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- handle.invoke(owner, value);
|
|
|
- } catch (Throwable throwable) {
|
|
|
- throw new IllegalArgumentException("Error storing value [" + value + "] " +
|
|
|
- "in field [" + name + "] with owner class [" + owner.getClass() + "].", throwable);
|
|
|
}
|
|
|
- } else if (owner instanceof Map) {
|
|
|
- ((Map)owner).put(name, value);
|
|
|
- } else if (owner instanceof List) {
|
|
|
+ }
|
|
|
+ // special case: arrays, maps, and lists
|
|
|
+ if (receiverClass.isArray() && "length".equals(name)) {
|
|
|
+ // arrays expose .length as a read-only getter
|
|
|
+ return ARRAY_LENGTH;
|
|
|
+ } else if (Map.class.isAssignableFrom(receiverClass)) {
|
|
|
+ // maps allow access like mymap.key
|
|
|
+ // wire 'key' as a parameter, its a constant in painless
|
|
|
+ return MethodHandles.insertArguments(MAP_GET, 1, name);
|
|
|
+ } else if (List.class.isAssignableFrom(receiverClass)) {
|
|
|
+ // lists allow access like mylist.0
|
|
|
+ // wire '0' (index) as a parameter, its a constant. this also avoids
|
|
|
+ // parsing the same integer millions of times!
|
|
|
try {
|
|
|
- final int index = Integer.parseInt(name);
|
|
|
- ((List)owner).set(index, value);
|
|
|
+ int index = Integer.parseInt(name);
|
|
|
+ return MethodHandles.insertArguments(LIST_GET, 1, index);
|
|
|
} catch (NumberFormatException exception) {
|
|
|
throw new IllegalArgumentException( "Illegal list shortcut value [" + name + "].");
|
|
|
}
|
|
|
- } else {
|
|
|
- throw new IllegalArgumentException("Unable to find dynamic field [" + name + "] " +
|
|
|
- "for class [" + owner.getClass().getCanonicalName() + "].");
|
|
|
}
|
|
|
+
|
|
|
+ throw new IllegalArgumentException("Unable to find dynamic field [" + name + "] " +
|
|
|
+ "for class [" + receiverClass.getCanonicalName() + "].");
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Looks up handle for a dynamic field setter (field store)
|
|
|
+ * <p>
|
|
|
+ * A dynamic field store for variable {@code x} of type {@code def} looks like:
|
|
|
+ * {@code x.field = y}
|
|
|
+ * <p>
|
|
|
+ * The following field stores are allowed:
|
|
|
+ * <ul>
|
|
|
+ * <li>Whitelisted {@code field} from receiver's class or any superclasses.
|
|
|
+ * <li>Whitelisted method named {@code setField()} from receiver's class/superclasses/interfaces.
|
|
|
+ * <li>The value corresponding to a map key named {@code field} when the receiver is a Map.
|
|
|
+ * <li>The value in a list at element {@code field} (integer) when the receiver is a List.
|
|
|
+ * </ul>
|
|
|
+ * <p>
|
|
|
+ * This method traverses {@code recieverClass}'s class hierarchy (including interfaces)
|
|
|
+ * until it finds a matching whitelisted setter. If one is not found, it throws an exception.
|
|
|
+ * Otherwise it returns a handle to the matching setter.
|
|
|
+ * <p>
|
|
|
+ * @param receiverClass Class of the object to retrieve the field from.
|
|
|
+ * @param name Name of the field.
|
|
|
+ * @param definition Whitelist to check.
|
|
|
+ * @return pointer to matching field. never returns null.
|
|
|
+ * @throws IllegalArgumentException if no matching whitelisted field was found.
|
|
|
+ */
|
|
|
+ static MethodHandle lookupSetter(Class<?> receiverClass, String name, Definition definition) {
|
|
|
+ // first try whitelist
|
|
|
+ for (Class<?> clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) {
|
|
|
+ RuntimeClass struct = definition.runtimeMap.get(clazz);
|
|
|
|
|
|
- @SuppressWarnings("rawtypes")
|
|
|
- public static Object fieldLoad(final Object owner, final String name, final Definition definition) {
|
|
|
- final Class<?> clazz = owner.getClass();
|
|
|
- if (clazz.isArray() && "length".equals(name)) {
|
|
|
- return Array.getLength(owner);
|
|
|
- } else {
|
|
|
- // TODO: remove this fast-path, once we speed up dynamics some more
|
|
|
- if ("value".equals(name) && owner instanceof ScriptDocValues) {
|
|
|
- if (clazz == ScriptDocValues.Doubles.class) {
|
|
|
- return ((ScriptDocValues.Doubles)owner).getValue();
|
|
|
- } else if (clazz == ScriptDocValues.Longs.class) {
|
|
|
- return ((ScriptDocValues.Longs)owner).getValue();
|
|
|
- } else if (clazz == ScriptDocValues.Strings.class) {
|
|
|
- return ((ScriptDocValues.Strings)owner).getValue();
|
|
|
- } else if (clazz == ScriptDocValues.GeoPoints.class) {
|
|
|
- return ((ScriptDocValues.GeoPoints)owner).getValue();
|
|
|
+ if (struct != null) {
|
|
|
+ MethodHandle handle = struct.setters.get(name);
|
|
|
+ if (handle != null) {
|
|
|
+ return handle;
|
|
|
}
|
|
|
}
|
|
|
- final Field field = getField(owner, name, definition);
|
|
|
- MethodHandle handle;
|
|
|
|
|
|
- if (field == null) {
|
|
|
- final String get = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
|
|
|
- final Method method = getMethod(owner, get, definition);
|
|
|
+ for (final Class<?> iface : clazz.getInterfaces()) {
|
|
|
+ struct = definition.runtimeMap.get(iface);
|
|
|
|
|
|
- if (method != null) {
|
|
|
- handle = method.handle;
|
|
|
- } else if (owner instanceof Map) {
|
|
|
- return ((Map)owner).get(name);
|
|
|
- } else if (owner instanceof List) {
|
|
|
- try {
|
|
|
- final int index = Integer.parseInt(name);
|
|
|
-
|
|
|
- return ((List)owner).get(index);
|
|
|
- } catch (NumberFormatException exception) {
|
|
|
- throw new IllegalArgumentException( "Illegal list shortcut value [" + name + "].");
|
|
|
+ if (struct != null) {
|
|
|
+ MethodHandle handle = struct.setters.get(name);
|
|
|
+ if (handle != null) {
|
|
|
+ return handle;
|
|
|
}
|
|
|
- } else {
|
|
|
- throw new IllegalArgumentException("Unable to find dynamic field [" + name + "] " +
|
|
|
- "for class [" + clazz.getCanonicalName() + "].");
|
|
|
}
|
|
|
- } else {
|
|
|
- handle = field.getter;
|
|
|
}
|
|
|
-
|
|
|
- if (handle == null) {
|
|
|
- throw new IllegalArgumentException(
|
|
|
- "Unable to read from field [" + name + "] with owner class [" + clazz + "].");
|
|
|
- } else {
|
|
|
- try {
|
|
|
- return handle.invoke(owner);
|
|
|
- } catch (final Throwable throwable) {
|
|
|
- throw new IllegalArgumentException("Error loading value from " +
|
|
|
- "field [" + name + "] with owner class [" + clazz + "].", throwable);
|
|
|
- }
|
|
|
+ }
|
|
|
+ // special case: maps, and lists
|
|
|
+ if (Map.class.isAssignableFrom(receiverClass)) {
|
|
|
+ // maps allow access like mymap.key
|
|
|
+ // wire 'key' as a parameter, its a constant in painless
|
|
|
+ return MethodHandles.insertArguments(MAP_PUT, 1, name);
|
|
|
+ } else if (List.class.isAssignableFrom(receiverClass)) {
|
|
|
+ // lists allow access like mylist.0
|
|
|
+ // wire '0' (index) as a parameter, its a constant. this also avoids
|
|
|
+ // parsing the same integer millions of times!
|
|
|
+ try {
|
|
|
+ int index = Integer.parseInt(name);
|
|
|
+ return MethodHandles.insertArguments(LIST_SET, 1, index);
|
|
|
+ } catch (NumberFormatException exception) {
|
|
|
+ throw new IllegalArgumentException( "Illegal list shortcut value [" + name + "].");
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ throw new IllegalArgumentException("Unable to find dynamic field [" + name + "] " +
|
|
|
+ "for class [" + receiverClass.getCanonicalName() + "].");
|
|
|
}
|
|
|
|
|
|
+ // NOTE: below methods are not cached, instead invoked directly because they are performant.
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Performs an actual array store.
|
|
|
+ * @param array array object
|
|
|
+ * @param index map key, array index (integer), or list index (integer)
|
|
|
+ * @param value value to store in the array.
|
|
|
+ */
|
|
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
|
- public static void arrayStore(final Object array, Object index, Object value, final Definition definition,
|
|
|
- final boolean indexsafe, final boolean valuesafe) {
|
|
|
+ public static void arrayStore(final Object array, Object index, Object value) {
|
|
|
if (array instanceof Map) {
|
|
|
((Map)array).put(index, value);
|
|
|
- } else {
|
|
|
+ } else if (array.getClass().isArray()) {
|
|
|
try {
|
|
|
- if (!indexsafe) {
|
|
|
- final Transform transform = getTransform(index.getClass(), Integer.class, definition);
|
|
|
-
|
|
|
- if (transform != null) {
|
|
|
- index = transform.method.handle.invoke(index);
|
|
|
- }
|
|
|
- }
|
|
|
+ Array.set(array, (int)index, value);
|
|
|
} catch (final Throwable throwable) {
|
|
|
- throw new IllegalArgumentException(
|
|
|
- "Error storing value [" + value + "] in list using index [" + index + "].", throwable);
|
|
|
- }
|
|
|
-
|
|
|
- if (array.getClass().isArray()) {
|
|
|
- try {
|
|
|
- if (!valuesafe) {
|
|
|
- final Transform transform = getTransform(value.getClass(), array.getClass().getComponentType(), definition);
|
|
|
-
|
|
|
- if (transform != null) {
|
|
|
- value = transform.method.handle.invoke(value);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- Array.set(array, (int)index, value);
|
|
|
- } catch (final Throwable throwable) {
|
|
|
- throw new IllegalArgumentException("Error storing value [" + value + "] " +
|
|
|
- "in array class [" + array.getClass().getCanonicalName() + "].", throwable);
|
|
|
- }
|
|
|
- } else if (array instanceof List) {
|
|
|
- ((List)array).set((int)index, value);
|
|
|
- } else {
|
|
|
- throw new IllegalArgumentException("Attempting to address a non-array type " +
|
|
|
- "[" + array.getClass().getCanonicalName() + "] as an array.");
|
|
|
+ throw new IllegalArgumentException("Error storing value [" + value + "] " +
|
|
|
+ "in array class [" + array.getClass().getCanonicalName() + "].", throwable);
|
|
|
}
|
|
|
+ } else if (array instanceof List) {
|
|
|
+ ((List)array).set((int)index, value);
|
|
|
+ } else {
|
|
|
+ throw new IllegalArgumentException("Attempting to address a non-array type " +
|
|
|
+ "[" + array.getClass().getCanonicalName() + "] as an array.");
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Performs an actual array load.
|
|
|
+ * @param array array object
|
|
|
+ * @param index map key, array index (integer), or list index (integer)
|
|
|
+ */
|
|
|
@SuppressWarnings("rawtypes")
|
|
|
- public static Object arrayLoad(final Object array, Object index,
|
|
|
- final Definition definition, final boolean indexsafe) {
|
|
|
+ public static Object arrayLoad(final Object array, Object index) {
|
|
|
if (array instanceof Map) {
|
|
|
return ((Map)array).get(index);
|
|
|
- } else {
|
|
|
+ } else if (array.getClass().isArray()) {
|
|
|
try {
|
|
|
- if (!indexsafe) {
|
|
|
- final Transform transform = getTransform(index.getClass(), Integer.class, definition);
|
|
|
-
|
|
|
- if (transform != null) {
|
|
|
- index = transform.method.handle.invoke(index);
|
|
|
- }
|
|
|
- }
|
|
|
+ return Array.get(array, (int)index);
|
|
|
} catch (final Throwable throwable) {
|
|
|
- throw new IllegalArgumentException(
|
|
|
- "Error loading value using index [" + index + "].", throwable);
|
|
|
- }
|
|
|
-
|
|
|
- if (array.getClass().isArray()) {
|
|
|
- try {
|
|
|
- return Array.get(array, (int)index);
|
|
|
- } catch (final Throwable throwable) {
|
|
|
- throw new IllegalArgumentException("Error loading value from " +
|
|
|
- "array class [" + array.getClass().getCanonicalName() + "].", throwable);
|
|
|
- }
|
|
|
- } else if (array instanceof List) {
|
|
|
- return ((List)array).get((int)index);
|
|
|
- } else {
|
|
|
- throw new IllegalArgumentException("Attempting to address a non-array type " +
|
|
|
- "[" + array.getClass().getCanonicalName() + "] as an array.");
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /** Method lookup for owner.name(), returns null if no matching method was found */
|
|
|
- private static Method getMethod(final Object owner, final String name, final Definition definition) {
|
|
|
- Class<?> clazz = owner.getClass();
|
|
|
-
|
|
|
- while (clazz != null) {
|
|
|
- Struct struct = definition.classes.get(clazz);
|
|
|
-
|
|
|
- if (struct != null) {
|
|
|
- Method method = struct.methods.get(name);
|
|
|
-
|
|
|
- if (method != null) {
|
|
|
- return method;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- for (final Class<?> iface : clazz.getInterfaces()) {
|
|
|
- struct = definition.classes.get(iface);
|
|
|
-
|
|
|
- if (struct != null) {
|
|
|
- Method method = struct.methods.get(name);
|
|
|
-
|
|
|
- if (method != null) {
|
|
|
- return method;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- clazz = clazz.getSuperclass();
|
|
|
- }
|
|
|
-
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- /** Field lookup for owner.name, returns null if no matching field was found */
|
|
|
- private static Field getField(final Object owner, final String name, final Definition definition) {
|
|
|
- Class<?> clazz = owner.getClass();
|
|
|
-
|
|
|
- while (clazz != null) {
|
|
|
- Struct struct = definition.classes.get(clazz);
|
|
|
-
|
|
|
- if (struct != null) {
|
|
|
- Field field = struct.members.get(name);
|
|
|
-
|
|
|
- if (field != null) {
|
|
|
- return field;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- for (final Class<?> iface : clazz.getInterfaces()) {
|
|
|
- struct = definition.classes.get(iface);
|
|
|
-
|
|
|
- if (struct != null) {
|
|
|
- Field field = struct.members.get(name);
|
|
|
-
|
|
|
- if (field != null) {
|
|
|
- return field;
|
|
|
- }
|
|
|
- }
|
|
|
+ throw new IllegalArgumentException("Error loading value from " +
|
|
|
+ "array class [" + array.getClass().getCanonicalName() + "].", throwable);
|
|
|
}
|
|
|
-
|
|
|
- clazz = clazz.getSuperclass();
|
|
|
- }
|
|
|
-
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- public static Transform getTransform(Class<?> fromClass, Class<?> toClass, final Definition definition) {
|
|
|
- Struct fromStruct = null;
|
|
|
- Struct toStruct = null;
|
|
|
-
|
|
|
- if (fromClass.equals(toClass)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- while (fromClass != null) {
|
|
|
- fromStruct = definition.classes.get(fromClass);
|
|
|
-
|
|
|
- if (fromStruct != null) {
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- for (final Class<?> iface : fromClass.getInterfaces()) {
|
|
|
- fromStruct = definition.classes.get(iface);
|
|
|
-
|
|
|
- if (fromStruct != null) {
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (fromStruct != null) {
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- fromClass = fromClass.getSuperclass();
|
|
|
- }
|
|
|
-
|
|
|
- if (fromStruct != null) {
|
|
|
- while (toClass != null) {
|
|
|
- toStruct = definition.classes.get(toClass);
|
|
|
-
|
|
|
- if (toStruct != null) {
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- for (final Class<?> iface : toClass.getInterfaces()) {
|
|
|
- toStruct = definition.classes.get(iface);
|
|
|
-
|
|
|
- if (toStruct != null) {
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (toStruct != null) {
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- toClass = toClass.getSuperclass();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (toStruct != null) {
|
|
|
- final Type fromType = definition.getType(fromStruct.name);
|
|
|
- final Type toType = definition.getType(toStruct.name);
|
|
|
- final Cast cast = new Cast(fromType, toType);
|
|
|
-
|
|
|
- return definition.transforms.get(cast);
|
|
|
+ } else if (array instanceof List) {
|
|
|
+ return ((List)array).get((int)index);
|
|
|
+ } else {
|
|
|
+ throw new IllegalArgumentException("Attempting to address a non-array type " +
|
|
|
+ "[" + array.getClass().getCanonicalName() + "] as an array.");
|
|
|
}
|
|
|
-
|
|
|
- return null;
|
|
|
}
|
|
|
|
|
|
public static Object not(final Object unary) {
|