/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.RuntimeClass;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.util.BitSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 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, stores, and array/list/map load/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 DefBootstrap}) 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.
*/
public final class Def {
// TODO: Once Java has a factory for those in java.lang.invoke.MethodHandles, use it:
/** Helper class for isolating MethodHandles and methods to get the length of arrays
* (to emulate a "arraystore" bytecode using MethodHandles).
* See: https://bugs.openjdk.java.net/browse/JDK-8156915
*/
@SuppressWarnings("unused") // getArrayLength() methods are are actually used, javac just does not know :)
private static final class ArrayLengthHelper {
private static final Lookup PRIV_LOOKUP = MethodHandles.lookup();
private static final Map<Class<?>,MethodHandle> ARRAY_TYPE_MH_MAPPING = Collections.unmodifiableMap(
Stream.of(boolean[].class, byte[].class, short[].class, int[].class, long[].class,
char[].class, float[].class, double[].class, Object[].class)
.collect(Collectors.toMap(Function.identity(), type -> {
try {
return PRIV_LOOKUP.findStatic(PRIV_LOOKUP.lookupClass(), "getArrayLength", MethodType.methodType(int.class, type));
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}))
);
private static final MethodHandle OBJECT_ARRAY_MH = ARRAY_TYPE_MH_MAPPING.get(Object[].class);
static int getArrayLength(final boolean[] array) { return array.length; }
static int getArrayLength(final byte[] array) { return array.length; }
static int getArrayLength(final short[] array) { return array.length; }
static int getArrayLength(final int[] array) { return array.length; }
static int getArrayLength(final long[] array) { return array.length; }
static int getArrayLength(final char[] array) { return array.length; }
static int getArrayLength(final float[] array) { return array.length; }
static int getArrayLength(final double[] array) { return array.length; }
static int getArrayLength(final Object[] array) { return array.length; }
static MethodHandle arrayLengthGetter(Class<?> arrayType) {
if (!arrayType.isArray()) {
throw new IllegalArgumentException("type must be an array");
}
return (ARRAY_TYPE_MH_MAPPING.containsKey(arrayType)) ?
ARRAY_TYPE_MH_MAPPING.get(arrayType) :
OBJECT_ARRAY_MH.asType(OBJECT_ARRAY_MH.type().changeParameterType(0, arrayType));
}
private ArrayLengthHelper() {}
}
/** 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;
/** pointer to Iterable.iterator() */
private static final MethodHandle ITERATOR;
/** pointer to {@link Def#mapIndexNormalize}. */
private static final MethodHandle MAP_INDEX_NORMALIZE;
/** pointer to {@link Def#listIndexNormalize}. */
private static final MethodHandle LIST_INDEX_NORMALIZE;
/** factory for arraylength MethodHandle (intrinsic) from Java 9 (pkg-private for tests) */
static final MethodHandle JAVA9_ARRAY_LENGTH_MH_FACTORY;
static {
final Lookup lookup = MethodHandles.publicLookup();
try {
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));
ITERATOR = lookup.findVirtual(Iterable.class, "iterator", MethodType.methodType(Iterator.class));
MAP_INDEX_NORMALIZE = lookup.findStatic(Def.class, "mapIndexNormalize",
MethodType.methodType(Object.class, Map.class, Object.class));
LIST_INDEX_NORMALIZE = lookup.findStatic(Def.class, "listIndexNormalize",
MethodType.methodType(int.class, List.class, int.class));
} catch (final ReflectiveOperationException roe) {
throw new AssertionError(roe);
}
// lookup up the factory for arraylength MethodHandle (intrinsic) from Java 9:
// https://bugs.openjdk.java.net/browse/JDK-8156915
MethodHandle arrayLengthMHFactory;
try {
arrayLengthMHFactory = lookup.findStatic(MethodHandles.class, "arrayLength",
MethodType.methodType(MethodHandle.class, Class.class));
} catch (final ReflectiveOperationException roe) {
arrayLengthMHFactory = null;
}
JAVA9_ARRAY_LENGTH_MH_FACTORY = arrayLengthMHFactory;
}
/** Hack to rethrow unknown Exceptions from {@link MethodHandle#invokeExact}: */
@SuppressWarnings("unchecked")
static <T extends Throwable> void rethrow(Throwable t) throws T {
throw (T) t;
}
/** Returns an array length getter MethodHandle for the given array type */
static MethodHandle arrayLengthGetter(Class<?> arrayType) {
if (JAVA9_ARRAY_LENGTH_MH_FACTORY != null) {
try {
return (MethodHandle) JAVA9_ARRAY_LENGTH_MH_FACTORY.invokeExact(arrayType);
} catch (Throwable t) {
rethrow(t);
throw new AssertionError(t);
}
} else {
return ArrayLengthHelper.arrayLengthGetter(arrayType);
}
}
/**
* Looks up method entry 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 the matching method.
* <p>
* @params definition the whitelist
* @param receiverClass Class of the object to invoke the method on.
* @param name Name of the method.
* @param arity arity of method
* @return matching method to invoke. never returns null.
* @throws IllegalArgumentException if no matching whitelisted method was found.
*/
static Method lookupMethodInternal(Definition definition, Class<?> receiverClass, String name, int arity) {
Definition.MethodKey key = new Definition.MethodKey(name, arity);
// check whitelist for matching method
for (Class<?> clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) {
RuntimeClass struct = definition.getRuntimeClass(clazz);
if (struct != null) {
Method method = struct.methods.get(key);
if (method != null) {
return method;
}
}
for (Class<?> iface : clazz.getInterfaces()) {
struct = definition.getRuntimeClass(iface);
if (struct != null) {
Method method = struct.methods.get(key);
if (method != null) {
return method;
}
}
}
}
throw new IllegalArgumentException("Unable to find dynamic method [" + name + "] with [" + arity + "] arguments " +
"for class [" + receiverClass.getCanonicalName() + "].");
}
/**
* Looks up handle for a dynamic method call, with lambda replacement
* <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 definition the whitelist
* @param lookup caller's lookup
* @param callSiteType callsite's type
* @param receiverClass Class of the object to invoke the method on.
* @param name Name of the method.
* @param args bootstrap args passed to callsite
* @return pointer to matching method to invoke. never returns null.
* @throws IllegalArgumentException if no matching whitelisted method was found.
* @throws Throwable if a method reference cannot be converted to an functional interface
*/
static MethodHandle lookupMethod(Definition definition, Lookup lookup, MethodType callSiteType,
Class<?> receiverClass, String name, Object args[]) throws Throwable {
String recipeString = (String) args[0];
int numArguments = callSiteType.parameterCount();
// simple case: no lambdas
if (recipeString.isEmpty()) {
return lookupMethodInternal(definition, receiverClass, name, numArguments - 1).handle;
}
// convert recipe string to a bitset for convenience (the code below should be refactored...)
BitSet lambdaArgs = new BitSet(recipeString.length());
for (int i = 0; i < recipeString.length(); i++) {
lambdaArgs.set(recipeString.charAt(i));
}
// otherwise: first we have to compute the "real" arity. This is because we have extra arguments:
// e.g. f(a, g(x), b, h(y), i()) looks like f(a, g, x, b, h, y, i).
int arity = callSiteType.parameterCount() - 1;
int upTo = 1;
for (int i = 1; i < numArguments; i++) {
if (lambdaArgs.get(i - 1)) {
String signature = (String) args[upTo++];
int numCaptures = Integer.parseInt(signature.substring(signature.indexOf(',')+1));
arity -= numCaptures;
}
}
// lookup the method with the proper arity, then we know everything (e.g. interface types of parameters).
// based on these we can finally link any remaining lambdas that were deferred.
Method method = lookupMethodInternal(definition, receiverClass, name, arity);
MethodHandle handle = method.handle;
int replaced = 0;
upTo = 1;
for (int i = 1; i < numArguments; i++) {
// its a functional reference, replace the argument with an impl
if (lambdaArgs.get(i - 1)) {
// decode signature of form 'type.call,2'
String signature = (String) args[upTo++];
int separator = signature.lastIndexOf('.');
int separator2 = signature.indexOf(',');
String type = signature.substring(1, separator);
String call = signature.substring(separator+1, separator2);
int numCaptures = Integer.parseInt(signature.substring(separator2+1));
Class<?> captures[] = new Class<?>[numCaptures];
for (int capture = 0; capture < captures.length; capture++) {
captures[capture] = callSiteType.parameterType(i + 1 + capture);
}
MethodHandle filter;
Definition.Type interfaceType = method.arguments.get(i - 1 - replaced);
if (signature.charAt(0) == 'S') {
// the implementation is strongly typed, now that we know the interface type,
// we have everything.
filter = lookupReferenceInternal(definition,
lookup,
interfaceType,
type,
call,
captures);
} else if (signature.charAt(0) == 'D') {
// the interface type is now known, but we need to get the implementation.
// this is dynamically based on the receiver type (and cached separately, underneath
// this cache). It won't blow up since we never nest here (just references)
MethodType nestedType = MethodType.methodType(interfaceType.clazz, captures);
CallSite nested = DefBootstrap.bootstrap(definition,
lookup,
call,
nestedType,
0,
DefBootstrap.REFERENCE,
interfaceType.name);
filter = nested.dynamicInvoker();
} else {
throw new AssertionError();
}
// the filter now ignores the signature (placeholder) on the stack
filter = MethodHandles.dropArguments(filter, 0, String.class);
handle = MethodHandles.collectArguments(handle, i, filter);
i += numCaptures;
replaced += numCaptures;
}
}
return handle;
}
/**
* Returns an implementation of interfaceClass that calls receiverClass.name
* <p>
* This is just like LambdaMetaFactory, only with a dynamic type. The interface type is known,
* so we simply need to lookup the matching implementation method based on receiver type.
*/
static MethodHandle lookupReference(Definition definition, Lookup lookup, String interfaceClass,
Class<?> receiverClass, String name) throws Throwable {
Definition.Type interfaceType = definition.getType(interfaceClass);
Method interfaceMethod = interfaceType.struct.getFunctionalMethod();
if (interfaceMethod == null) {
throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface");
}
int arity = interfaceMethod.arguments.size();
Method implMethod = lookupMethodInternal(definition, receiverClass, name, arity);
return lookupReferenceInternal(definition, lookup, interfaceType, implMethod.owner.name,
implMethod.name, receiverClass);
}
/** Returns a method handle to an implementation of clazz, given method reference signature. */
private static MethodHandle lookupReferenceInternal(Definition definition, Lookup lookup,
Definition.Type clazz, String type, String call, Class<?>... captures)
throws Throwable {
final FunctionRef ref;
if ("this".equals(type)) {
// user written method
Method interfaceMethod = clazz.struct.getFunctionalMethod();
if (interfaceMethod == null) {
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
"to [" + clazz.name + "], not a functional interface");
}
int arity = interfaceMethod.arguments.size() + captures.length;
final MethodHandle handle;
try {
MethodHandle accessor = lookup.findStaticGetter(lookup.lookupClass(),
getUserFunctionHandleFieldName(call, arity),
MethodHandle.class);
handle = (MethodHandle)accessor.invokeExact();
} catch (NoSuchFieldException | IllegalAccessException e) {
// is it a synthetic method? If we generated the method ourselves, be more helpful. It can only fail
// because the arity does not match the expected interface type.
if (call.contains("$")) {
throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.name +
"] in [" + clazz.clazz + "]");
}
throw new IllegalArgumentException("Unknown call [" + call + "] with [" + arity + "] arguments.");
}
ref = new FunctionRef(clazz, interfaceMethod, call, handle.type(), captures.length);
} else {
// whitelist lookup
ref = new FunctionRef(definition, clazz, type, call, captures.length);
}
final CallSite callSite = LambdaBootstrap.lambdaBootstrap(
lookup,
ref.interfaceMethodName,
ref.factoryMethodType,
ref.interfaceMethodType,
ref.delegateClassName,
ref.delegateInvokeType,
ref.delegateMethodName,
ref.delegateMethodType
);
return callSite.dynamicInvoker().asType(MethodType.methodType(clazz.clazz, captures));
}
/** gets the field name used to lookup up the MethodHandle for a function. */
public static String getUserFunctionHandleFieldName(String name, int arity) {
return "handle$" + name + "$" + arity;
}
/**
* 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 definition the whitelist
* @param receiverClass Class of the object to retrieve the field from.
* @param name Name of the field.
* @return pointer to matching field. never returns null.
* @throws IllegalArgumentException if no matching whitelisted field was found.
*/
static MethodHandle lookupGetter(Definition definition, Class<?> receiverClass, String name) {
// first try whitelist
for (Class<?> clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) {
RuntimeClass struct = definition.getRuntimeClass(clazz);
if (struct != null) {
MethodHandle handle = struct.getters.get(name);
if (handle != null) {
return handle;
}
}
for (final Class<?> iface : clazz.getInterfaces()) {
struct = definition.getRuntimeClass(iface);
if (struct != null) {
MethodHandle handle = struct.getters.get(name);
if (handle != null) {
return handle;
}
}
}
}
// special case: arrays, maps, and lists
if (receiverClass.isArray() && "length".equals(name)) {
// arrays expose .length as a read-only getter
return arrayLengthGetter(receiverClass);
} 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 {
int index = Integer.parseInt(name);
return MethodHandles.insertArguments(LIST_GET, 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() + "].");
}
/**
* 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 definition the whitelist
* @param receiverClass Class of the object to retrieve the field from.
* @param name Name of the field.
* @return pointer to matching field. never returns null.
* @throws IllegalArgumentException if no matching whitelisted field was found.
*/
static MethodHandle lookupSetter(Definition definition, Class<?> receiverClass, String name) {
// first try whitelist
for (Class<?> clazz = receiverClass; clazz != null; clazz = clazz.getSuperclass()) {
RuntimeClass struct = definition.getRuntimeClass(clazz);
if (struct != null) {
MethodHandle handle = struct.setters.get(name);
if (handle != null) {
return handle;
}
}
for (final Class<?> iface : clazz.getInterfaces()) {
struct = definition.getRuntimeClass(iface);
if (struct != null) {
MethodHandle handle = struct.setters.get(name);
if (handle != null) {
return handle;
}
}
}
}
// 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 (final NumberFormatException exception) {
throw new IllegalArgumentException( "Illegal list shortcut value [" + name + "].");
}
}
throw new IllegalArgumentException("Unable to find dynamic field [" + name + "] " +
"for class [" + receiverClass.getCanonicalName() + "].");
}
/**
* Returns a method handle to normalize the index into an array. This is what makes lists and arrays stored in {@code def} support
* negative offsets.
* @param receiverClass Class of the array to store the value in
* @return a MethodHandle that accepts the receiver as first argument, the index as second argument, and returns the normalized index
* to use with array loads and array stores
*/
static MethodHandle lookupIndexNormalize(Class<?> receiverClass) {
if (receiverClass.isArray()) {
return ArrayIndexNormalizeHelper.arrayIndexNormalizer(receiverClass);
} else if (Map.class.isAssignableFrom(receiverClass)) {
// noop so that mymap[key] doesn't do funny things with negative keys
return MAP_INDEX_NORMALIZE;
} else if (List.class.isAssignableFrom(receiverClass)) {
return LIST_INDEX_NORMALIZE;
}
throw new IllegalArgumentException("Attempting to address a non-array-like type " +
"[" + receiverClass.getCanonicalName() + "] as an array.");
}
/**
* Returns a method handle to do an array store.
* @param receiverClass Class of the array to store the value in
* @return a MethodHandle that accepts the receiver as first argument, the index as second argument,
* and the value to set as 3rd argument. Return value is undefined and should be ignored.
*/
static MethodHandle lookupArrayStore(Class<?> receiverClass) {
if (receiverClass.isArray()) {
return MethodHandles.arrayElementSetter(receiverClass);
} else if (Map.class.isAssignableFrom(receiverClass)) {
// maps allow access like mymap[key]
return MAP_PUT;
} else if (List.class.isAssignableFrom(receiverClass)) {
return LIST_SET;
}
throw new IllegalArgumentException("Attempting to address a non-array type " +
"[" + receiverClass.getCanonicalName() + "] as an array.");
}
/**
* Returns a method handle to do an array load.
* @param receiverClass Class of the array to load the value from
* @return a MethodHandle that accepts the receiver as first argument, the index as second argument.
* It returns the loaded value.
*/
static MethodHandle lookupArrayLoad(Class<?> receiverClass) {
if (receiverClass.isArray()) {
return MethodHandles.arrayElementGetter(receiverClass);
} else if (Map.class.isAssignableFrom(receiverClass)) {
// maps allow access like mymap[key]
return MAP_GET;
} else if (List.class.isAssignableFrom(receiverClass)) {
return LIST_GET;
}
throw new IllegalArgumentException("Attempting to address a non-array type " +
"[" + receiverClass.getCanonicalName() + "] as an array.");
}
/** Helper class for isolating MethodHandles and methods to get iterators over arrays
* (to emulate "enhanced for loop" using MethodHandles). These cause boxing, and are not as efficient
* as they could be, but works.
*/
@SuppressWarnings("unused") // iterator() methods are are actually used, javac just does not know :)
private static final class ArrayIteratorHelper {
private static final Lookup PRIV_LOOKUP = MethodHandles.lookup();
private static final Map<Class<?>,MethodHandle> ARRAY_TYPE_MH_MAPPING = Collections.unmodifiableMap(
Stream.of(boolean[].class, byte[].class, short[].class, int[].class, long[].class,
char[].class, float[].class, double[].class, Object[].class)
.collect(Collectors.toMap(Function.identity(), type -> {
try {
return PRIV_LOOKUP.findStatic(PRIV_LOOKUP.lookupClass(), "iterator", MethodType.methodType(Iterator.class, type));
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}))
);
private static final MethodHandle OBJECT_ARRAY_MH = ARRAY_TYPE_MH_MAPPING.get(Object[].class);
static Iterator<Boolean> iterator(final boolean[] array) {
return new Iterator<Boolean>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Boolean next() { return array[index++]; }
};
}
static Iterator<Byte> iterator(final byte[] array) {
return new Iterator<Byte>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Byte next() { return array[index++]; }
};
}
static Iterator<Short> iterator(final short[] array) {
return new Iterator<Short>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Short next() { return array[index++]; }
};
}
static Iterator<Integer> iterator(final int[] array) {
return new Iterator<Integer>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Integer next() { return array[index++]; }
};
}
static Iterator<Long> iterator(final long[] array) {
return new Iterator<Long>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Long next() { return array[index++]; }
};
}
static Iterator<Character> iterator(final char[] array) {
return new Iterator<Character>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Character next() { return array[index++]; }
};
}
static Iterator<Float> iterator(final float[] array) {
return new Iterator<Float>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Float next() { return array[index++]; }
};
}
static Iterator<Double> iterator(final double[] array) {
return new Iterator<Double>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Double next() { return array[index++]; }
};
}
static Iterator<Object> iterator(final Object[] array) {
return new Iterator<Object>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Object next() { return array[index++]; }
};
}
static MethodHandle newIterator(Class<?> arrayType) {
if (!arrayType.isArray()) {
throw new IllegalArgumentException("type must be an array");
}
return (ARRAY_TYPE_MH_MAPPING.containsKey(arrayType)) ?
ARRAY_TYPE_MH_MAPPING.get(arrayType) :
OBJECT_ARRAY_MH.asType(OBJECT_ARRAY_MH.type().changeParameterType(0, arrayType));
}
private ArrayIteratorHelper() {}
}
/**
* Returns a method handle to do iteration (for enhanced for loop)
* @param receiverClass Class of the array to load the value from
* @return a MethodHandle that accepts the receiver as first argument, returns iterator
*/
static MethodHandle lookupIterator(Class<?> receiverClass) {
if (Iterable.class.isAssignableFrom(receiverClass)) {
return ITERATOR;
} else if (receiverClass.isArray()) {
return ArrayIteratorHelper.newIterator(receiverClass);
} else {
throw new IllegalArgumentException("Cannot iterate over [" + receiverClass.getCanonicalName() + "]");
}
}
// Conversion methods for Def to primitive types.
public static boolean DefToboolean(final Object value) {
return (boolean)value;
}
public static byte DefTobyteImplicit(final Object value) {
return (byte)value;
}
public static short DefToshortImplicit(final Object value) {
if (value instanceof Byte) {
return (byte)value;
} else {
return (short)value;
}
}
public static char DefTocharImplicit(final Object value) {
if (value instanceof Byte) {
return (char)(byte)value;
} else {
return (char)value;
}
}
public static int DefTointImplicit(final Object value) {
if (value instanceof Byte) {
return (byte)value;
} else if (value instanceof Short) {
return (short)value;
} else if (value instanceof Character) {
return (char)value;
} else {
return (int)value;
}
}
public static long DefTolongImplicit(final Object value) {
if (value instanceof Byte) {
return (byte)value;
} else if (value instanceof Short) {
return (short)value;
} else if (value instanceof Character) {
return (char)value;
} else if (value instanceof Integer) {
return (int)value;
} else {
return (long)value;
}
}
public static float DefTofloatImplicit(final Object value) {
if (value instanceof Byte) {
return (byte)value;
} else if (value instanceof Short) {
return (short)value;
} else if (value instanceof Character) {
return (char)value;
} else if (value instanceof Integer) {
return (int)value;
} else if (value instanceof Long) {
return (long)value;
} else {
return (float)value;
}
}
public static double DefTodoubleImplicit(final Object value) {
if (value instanceof Byte) {
return (byte)value;
} else if (value instanceof Short) {
return (short)value;
} else if (value instanceof Character) {
return (char)value;
} else if (value instanceof Integer) {
return (int)value;
} else if (value instanceof Long) {
return (long)value;
} else if (value instanceof Float) {
return (float)value;
} else {
return (double)value;
}
}
public static byte DefTobyteExplicit(final Object value) {
if (value instanceof Character) {
return (byte)(char)value;
} else {
return ((Number)value).byteValue();
}
}
public static short DefToshortExplicit(final Object value) {
if (value instanceof Character) {
return (short)(char)value;
} else {
return ((Number)value).shortValue();
}
}
public static char DefTocharExplicit(final Object value) {
if (value instanceof Character) {
return ((Character)value);
} else {
return (char)((Number)value).intValue();
}
}
public static int DefTointExplicit(final Object value) {
if (value instanceof Character) {
return (char)value;
} else {
return ((Number)value).intValue();
}
}
public static long DefTolongExplicit(final Object value) {
if (value instanceof Character) {
return (char)value;
} else {
return ((Number)value).longValue();
}
}
public static float DefTofloatExplicit(final Object value) {
if (value instanceof Character) {
return (char)value;
} else {
return ((Number)value).floatValue();
}
}
public static double DefTodoubleExplicit(final Object value) {
if (value instanceof Character) {
return (char)value;
} else {
return ((Number)value).doubleValue();
}
}
/**
* "Normalizes" the index into a {@code Map} by making no change to the index.
*/
public static Object mapIndexNormalize(final Map<?, ?> value, Object index) {
return index;
}
/**
* "Normalizes" the idnex into a {@code List} by flipping negative indexes around so they are "from the end" of the list.
*/
public static int listIndexNormalize(final List<?> value, int index) {
return index >= 0 ? index : value.size() + index;
}
/**
* Methods to normalize array indices to support negative indices into arrays stored in {@code def}s.
*/
@SuppressWarnings("unused") // normalizeIndex() methods are are actually used, javac just does not know :)
private static final class ArrayIndexNormalizeHelper {
private static final Lookup PRIV_LOOKUP = MethodHandles.lookup();
private static final Map<Class<?>,MethodHandle> ARRAY_TYPE_MH_MAPPING = Collections.unmodifiableMap(
Stream.of(boolean[].class, byte[].class, short[].class, int[].class, long[].class,
char[].class, float[].class, double[].class, Object[].class)
.collect(Collectors.toMap(Function.identity(), type -> {
try {
return PRIV_LOOKUP.findStatic(PRIV_LOOKUP.lookupClass(), "normalizeIndex",
MethodType.methodType(int.class, type, int.class));
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}))
);
private static final MethodHandle OBJECT_ARRAY_MH = ARRAY_TYPE_MH_MAPPING.get(Object[].class);
static int normalizeIndex(final boolean[] array, final int index) { return index >= 0 ? index : index + array.length; }
static int normalizeIndex(final byte[] array, final int index) { return index >= 0 ? index : index + array.length; }
static int normalizeIndex(final short[] array, final int index) { return index >= 0 ? index : index + array.length; }
static int normalizeIndex(final int[] array, final int index) { return index >= 0 ? index : index + array.length; }
static int normalizeIndex(final long[] array, final int index) { return index >= 0 ? index : index + array.length; }
static int normalizeIndex(final char[] array, final int index) { return index >= 0 ? index : index + array.length; }
static int normalizeIndex(final float[] array, final int index) { return index >= 0 ? index : index + array.length; }
static int normalizeIndex(final double[] array, final int index) { return index >= 0 ? index : index + array.length; }
static int normalizeIndex(final Object[] array, final int index) { return index >= 0 ? index : index + array.length; }
static MethodHandle arrayIndexNormalizer(Class<?> arrayType) {
if (!arrayType.isArray()) {
throw new IllegalArgumentException("type must be an array");
}
return (ARRAY_TYPE_MH_MAPPING.containsKey(arrayType)) ?
ARRAY_TYPE_MH_MAPPING.get(arrayType) :
OBJECT_ARRAY_MH.asType(OBJECT_ARRAY_MH.type().changeParameterType(0, arrayType));
}
private ArrayIndexNormalizeHelper() {}
}
}