Painless: Add Imported Static Method (#33440)
Allow static methods to be imported in Painless and called using just the method name.
This commit is contained in:
parent
9a404f3def
commit
facec187bb
|
@ -61,12 +61,19 @@ public final class Whitelist {
|
|||
/** The {@link List} of all the whitelisted Painless classes. */
|
||||
public final List<WhitelistClass> whitelistClasses;
|
||||
|
||||
/** The {@link List} of all the whitelisted static Painless methods. */
|
||||
public final List<WhitelistMethod> whitelistImportedMethods;
|
||||
|
||||
/** The {@link List} of all the whitelisted Painless bindings. */
|
||||
public final List<WhitelistBinding> whitelistBindings;
|
||||
|
||||
/** Standard constructor. All values must be not {@code null}. */
|
||||
public Whitelist(ClassLoader classLoader, List<WhitelistClass> whitelistClasses, List<WhitelistBinding> whitelistBindings) {
|
||||
public Whitelist(ClassLoader classLoader, List<WhitelistClass> whitelistClasses,
|
||||
List<WhitelistMethod> whitelistImportedMethods, List<WhitelistBinding> whitelistBindings) {
|
||||
|
||||
this.classLoader = Objects.requireNonNull(classLoader);
|
||||
this.whitelistClasses = Collections.unmodifiableList(Objects.requireNonNull(whitelistClasses));
|
||||
this.whitelistImportedMethods = Collections.unmodifiableList(Objects.requireNonNull(whitelistImportedMethods));
|
||||
this.whitelistBindings = Collections.unmodifiableList(Objects.requireNonNull(whitelistBindings));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,6 +133,7 @@ public final class WhitelistLoader {
|
|||
*/
|
||||
public static Whitelist loadFromResourceFiles(Class<?> resource, String... filepaths) {
|
||||
List<WhitelistClass> whitelistClasses = new ArrayList<>();
|
||||
List<WhitelistMethod> whitelistStatics = new ArrayList<>();
|
||||
List<WhitelistBinding> whitelistBindings = new ArrayList<>();
|
||||
|
||||
// Execute a single pass through the whitelist text files. This will gather all the
|
||||
|
@ -192,18 +193,18 @@ public final class WhitelistLoader {
|
|||
whitelistConstructors = new ArrayList<>();
|
||||
whitelistMethods = new ArrayList<>();
|
||||
whitelistFields = new ArrayList<>();
|
||||
} else if (line.startsWith("static ")) {
|
||||
} else if (line.startsWith("static_import ")) {
|
||||
// Ensure the final token of the line is '{'.
|
||||
if (line.endsWith("{") == false) {
|
||||
throw new IllegalArgumentException(
|
||||
"invalid static definition: failed to parse static opening bracket [" + line + "]");
|
||||
"invalid static import definition: failed to parse static import opening bracket [" + line + "]");
|
||||
}
|
||||
|
||||
if (parseType != null) {
|
||||
throw new IllegalArgumentException("invalid definition: cannot embed static definition [" + line + "]");
|
||||
throw new IllegalArgumentException("invalid definition: cannot embed static import definition [" + line + "]");
|
||||
}
|
||||
|
||||
parseType = "static";
|
||||
parseType = "static_import";
|
||||
|
||||
// Handle the end of a definition and reset all previously gathered values.
|
||||
// Expects the following format: '}' '\n'
|
||||
|
@ -229,9 +230,9 @@ public final class WhitelistLoader {
|
|||
// Reset the parseType.
|
||||
parseType = null;
|
||||
|
||||
// Handle static definition types.
|
||||
// Expects the following format: ID ID '(' ( ID ( ',' ID )* )? ')' 'bound_to' ID '\n'
|
||||
} else if ("static".equals(parseType)) {
|
||||
// Handle static import definition types.
|
||||
// Expects the following format: ID ID '(' ( ID ( ',' ID )* )? ')' ( 'from_class' | 'bound_to' ) ID '\n'
|
||||
} else if ("static_import".equals(parseType)) {
|
||||
// Mark the origin of this parsable object.
|
||||
String origin = "[" + filepath + "]:[" + number + "]";
|
||||
|
||||
|
@ -240,7 +241,7 @@ public final class WhitelistLoader {
|
|||
|
||||
if (parameterStartIndex == -1) {
|
||||
throw new IllegalArgumentException(
|
||||
"illegal static definition: start of method parameters not found [" + line + "]");
|
||||
"illegal static import definition: start of method parameters not found [" + line + "]");
|
||||
}
|
||||
|
||||
String[] tokens = line.substring(0, parameterStartIndex).trim().split("\\s+");
|
||||
|
@ -261,7 +262,7 @@ public final class WhitelistLoader {
|
|||
|
||||
if (parameterEndIndex == -1) {
|
||||
throw new IllegalArgumentException(
|
||||
"illegal static definition: end of method parameters not found [" + line + "]");
|
||||
"illegal static import definition: end of method parameters not found [" + line + "]");
|
||||
}
|
||||
|
||||
String[] canonicalTypeNameParameters =
|
||||
|
@ -272,39 +273,37 @@ public final class WhitelistLoader {
|
|||
canonicalTypeNameParameters = new String[0];
|
||||
}
|
||||
|
||||
// Parse the static type and class.
|
||||
// Parse the static import type and class.
|
||||
tokens = line.substring(parameterEndIndex + 1).trim().split("\\s+");
|
||||
|
||||
String staticType;
|
||||
String staticImportType;
|
||||
String targetJavaClassName;
|
||||
|
||||
// Based on the number of tokens, look up the type and class.
|
||||
if (tokens.length == 2) {
|
||||
staticType = tokens[0];
|
||||
staticImportType = tokens[0];
|
||||
targetJavaClassName = tokens[1];
|
||||
} else {
|
||||
throw new IllegalArgumentException("invalid static definition: unexpected format [" + line + "]");
|
||||
}
|
||||
|
||||
// Check the static type is valid.
|
||||
if ("bound_to".equals(staticType) == false) {
|
||||
throw new IllegalArgumentException(
|
||||
"invalid static definition: unexpected static type [" + staticType + "] [" + line + "]");
|
||||
throw new IllegalArgumentException("invalid static import definition: unexpected format [" + line + "]");
|
||||
}
|
||||
|
||||
// Add a static import method or binding depending on the static import type.
|
||||
if ("from_class".equals(staticImportType)) {
|
||||
whitelistStatics.add(new WhitelistMethod(origin, targetJavaClassName,
|
||||
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
|
||||
} else if ("bound_to".equals(staticImportType)) {
|
||||
whitelistBindings.add(new WhitelistBinding(origin, targetJavaClassName,
|
||||
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
|
||||
} else {
|
||||
throw new IllegalArgumentException("invalid static import definition: " +
|
||||
"unexpected static import type [" + staticImportType + "] [" + line + "]");
|
||||
}
|
||||
|
||||
// Handle class definition types.
|
||||
} else if ("class".equals(parseType)) {
|
||||
// Mark the origin of this parsable object.
|
||||
String origin = "[" + filepath + "]:[" + number + "]";
|
||||
|
||||
// Ensure we have a defined class before adding any constructors, methods, augmented methods, or fields.
|
||||
if (parseType == null) {
|
||||
throw new IllegalArgumentException("invalid definition: expected one of ['class', 'static'] [" + line + "]");
|
||||
}
|
||||
|
||||
// Handle the case for a constructor definition.
|
||||
// Expects the following format: '(' ( ID ( ',' ID )* )? ')' '\n'
|
||||
if (line.startsWith("(")) {
|
||||
|
@ -393,7 +392,7 @@ public final class WhitelistLoader {
|
|||
|
||||
ClassLoader loader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>)resource::getClassLoader);
|
||||
|
||||
return new Whitelist(loader, whitelistClasses, whitelistBindings);
|
||||
return new Whitelist(loader, whitelistClasses, whitelistStatics, whitelistBindings);
|
||||
}
|
||||
|
||||
private WhitelistLoader() {}
|
||||
|
|
|
@ -24,6 +24,21 @@ import java.util.function.Function;
|
|||
|
||||
/** Currently just a dummy class for testing a few features not yet exposed by whitelist! */
|
||||
public class FeatureTest {
|
||||
/** static method that returns true */
|
||||
public static boolean overloadedStatic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** static method that returns what you ask it */
|
||||
public static boolean overloadedStatic(boolean whatToReturn) {
|
||||
return whatToReturn;
|
||||
}
|
||||
|
||||
/** static method only whitelisted as a static */
|
||||
public static float staticAddFloatsTest(float x, float y) {
|
||||
return x + y;
|
||||
}
|
||||
|
||||
private int x;
|
||||
private int y;
|
||||
public int z;
|
||||
|
@ -58,21 +73,12 @@ public class FeatureTest {
|
|||
this.y = y;
|
||||
}
|
||||
|
||||
/** static method that returns true */
|
||||
public static boolean overloadedStatic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** static method that returns what you ask it */
|
||||
public static boolean overloadedStatic(boolean whatToReturn) {
|
||||
return whatToReturn;
|
||||
}
|
||||
|
||||
/** method taking two functions! */
|
||||
public Object twoFunctionsOfX(Function<Object,Object> f, Function<Object,Object> g) {
|
||||
return f.apply(g.apply(x));
|
||||
}
|
||||
|
||||
/** method to take in a list */
|
||||
public void listInput(List<Object> list) {
|
||||
|
||||
}
|
||||
|
|
|
@ -37,16 +37,23 @@ public final class PainlessLookup {
|
|||
private final Map<String, Class<?>> canonicalClassNamesToClasses;
|
||||
private final Map<Class<?>, PainlessClass> classesToPainlessClasses;
|
||||
|
||||
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
|
||||
private final Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings;
|
||||
|
||||
PainlessLookup(Map<String, Class<?>> canonicalClassNamesToClasses, Map<Class<?>, PainlessClass> classesToPainlessClasses,
|
||||
Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods,
|
||||
Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings) {
|
||||
|
||||
Objects.requireNonNull(canonicalClassNamesToClasses);
|
||||
Objects.requireNonNull(classesToPainlessClasses);
|
||||
|
||||
Objects.requireNonNull(painlessMethodKeysToImportedPainlessMethods);
|
||||
Objects.requireNonNull(painlessMethodKeysToPainlessBindings);
|
||||
|
||||
this.canonicalClassNamesToClasses = Collections.unmodifiableMap(canonicalClassNamesToClasses);
|
||||
this.classesToPainlessClasses = Collections.unmodifiableMap(classesToPainlessClasses);
|
||||
|
||||
this.painlessMethodKeysToImportedPainlessMethods = Collections.unmodifiableMap(painlessMethodKeysToImportedPainlessMethods);
|
||||
this.painlessMethodKeysToPainlessBindings = Collections.unmodifiableMap(painlessMethodKeysToPainlessBindings);
|
||||
}
|
||||
|
||||
|
@ -167,6 +174,14 @@ public final class PainlessLookup {
|
|||
return painlessField;
|
||||
}
|
||||
|
||||
public PainlessMethod lookupImportedPainlessMethod(String methodName, int arity) {
|
||||
Objects.requireNonNull(methodName);
|
||||
|
||||
String painlessMethodKey = buildPainlessMethodKey(methodName, arity);
|
||||
|
||||
return painlessMethodKeysToImportedPainlessMethods.get(painlessMethodKey);
|
||||
}
|
||||
|
||||
public PainlessBinding lookupPainlessBinding(String methodName, int arity) {
|
||||
Objects.requireNonNull(methodName);
|
||||
|
||||
|
|
|
@ -243,6 +243,14 @@ public final class PainlessLookupBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
for (WhitelistMethod whitelistStatic : whitelist.whitelistImportedMethods) {
|
||||
origin = whitelistStatic.origin;
|
||||
painlessLookupBuilder.addImportedPainlessMethod(
|
||||
whitelist.classLoader, whitelistStatic.augmentedCanonicalClassName,
|
||||
whitelistStatic.methodName, whitelistStatic.returnCanonicalTypeName,
|
||||
whitelistStatic.canonicalTypeNameParameters);
|
||||
}
|
||||
|
||||
for (WhitelistBinding whitelistBinding : whitelist.whitelistBindings) {
|
||||
origin = whitelistBinding.origin;
|
||||
painlessLookupBuilder.addPainlessBinding(
|
||||
|
@ -261,12 +269,14 @@ public final class PainlessLookupBuilder {
|
|||
private final Map<String, Class<?>> canonicalClassNamesToClasses;
|
||||
private final Map<Class<?>, PainlessClassBuilder> classesToPainlessClassBuilders;
|
||||
|
||||
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
|
||||
private final Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings;
|
||||
|
||||
public PainlessLookupBuilder() {
|
||||
canonicalClassNamesToClasses = new HashMap<>();
|
||||
classesToPainlessClassBuilders = new HashMap<>();
|
||||
|
||||
painlessMethodKeysToImportedPainlessMethods = new HashMap<>();
|
||||
painlessMethodKeysToPainlessBindings = new HashMap<>();
|
||||
}
|
||||
|
||||
|
@ -513,8 +523,9 @@ public final class PainlessLookupBuilder {
|
|||
addPainlessMethod(targetClass, augmentedClass, methodName, returnType, typeParameters);
|
||||
}
|
||||
|
||||
public void addPainlessMethod(Class<?> targetClass, Class<?> augmentedClass, String methodName,
|
||||
Class<?> returnType, List<Class<?>> typeParameters) {
|
||||
public void addPainlessMethod(Class<?> targetClass, Class<?> augmentedClass,
|
||||
String methodName, Class<?> returnType, List<Class<?>> typeParameters) {
|
||||
|
||||
Objects.requireNonNull(targetClass);
|
||||
Objects.requireNonNull(methodName);
|
||||
Objects.requireNonNull(returnType);
|
||||
|
@ -573,6 +584,12 @@ public final class PainlessLookupBuilder {
|
|||
} else {
|
||||
try {
|
||||
javaMethod = augmentedClass.getMethod(methodName, javaTypeParameters.toArray(new Class<?>[typeParametersSize]));
|
||||
|
||||
if (Modifier.isStatic(javaMethod.getModifiers()) == false) {
|
||||
throw new IllegalArgumentException("method [[" + targetCanonicalClassName + "], [" + methodName + "], " +
|
||||
typesToCanonicalTypeNames(typeParameters) + "] with augmented class " +
|
||||
"[" + typeToCanonicalTypeName(augmentedClass) + "] must be static");
|
||||
}
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
throw new IllegalArgumentException("method reflection object [[" + targetCanonicalClassName + "], " +
|
||||
"[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found " +
|
||||
|
@ -620,7 +637,7 @@ public final class PainlessLookupBuilder {
|
|||
"with the same arity and different return type or type parameters");
|
||||
}
|
||||
} else {
|
||||
PainlessMethod painlessMethod = painlessClassBuilder.staticMethods.get(painlessMethodKey);
|
||||
PainlessMethod painlessMethod = painlessClassBuilder.methods.get(painlessMethodKey);
|
||||
|
||||
if (painlessMethod == null) {
|
||||
MethodHandle methodHandle;
|
||||
|
@ -788,6 +805,146 @@ public final class PainlessLookupBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
public void addImportedPainlessMethod(ClassLoader classLoader, String targetCanonicalClassName,
|
||||
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||
|
||||
Objects.requireNonNull(classLoader);
|
||||
Objects.requireNonNull(targetCanonicalClassName);
|
||||
Objects.requireNonNull(methodName);
|
||||
Objects.requireNonNull(returnCanonicalTypeName);
|
||||
Objects.requireNonNull(canonicalTypeNameParameters);
|
||||
|
||||
Class<?> targetClass = canonicalClassNamesToClasses.get(targetCanonicalClassName);
|
||||
|
||||
if (targetClass == null) {
|
||||
throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found for imported method " +
|
||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
||||
}
|
||||
|
||||
List<Class<?>> typeParameters = new ArrayList<>(canonicalTypeNameParameters.size());
|
||||
|
||||
for (String canonicalTypeNameParameter : canonicalTypeNameParameters) {
|
||||
Class<?> typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter);
|
||||
|
||||
if (typeParameter == null) {
|
||||
throw new IllegalArgumentException("type parameter [" + canonicalTypeNameParameter + "] not found for imported method " +
|
||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
||||
}
|
||||
|
||||
typeParameters.add(typeParameter);
|
||||
}
|
||||
|
||||
Class<?> returnType = canonicalTypeNameToType(returnCanonicalTypeName);
|
||||
|
||||
if (returnType == null) {
|
||||
throw new IllegalArgumentException("return type [" + returnCanonicalTypeName + "] not found for imported method " +
|
||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
||||
}
|
||||
|
||||
addImportedPainlessMethod(targetClass, methodName, returnType, typeParameters);
|
||||
}
|
||||
|
||||
public void addImportedPainlessMethod(Class<?> targetClass, String methodName, Class<?> returnType, List<Class<?>> typeParameters) {
|
||||
Objects.requireNonNull(targetClass);
|
||||
Objects.requireNonNull(methodName);
|
||||
Objects.requireNonNull(returnType);
|
||||
Objects.requireNonNull(typeParameters);
|
||||
|
||||
if (targetClass == def.class) {
|
||||
throw new IllegalArgumentException("cannot add imported method from reserved class [" + DEF_CLASS_NAME + "]");
|
||||
}
|
||||
|
||||
String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
|
||||
|
||||
if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
|
||||
throw new IllegalArgumentException(
|
||||
"invalid imported method name [" + methodName + "] for target class [" + targetCanonicalClassName + "].");
|
||||
}
|
||||
|
||||
PainlessClassBuilder painlessClassBuilder = classesToPainlessClassBuilders.get(targetClass);
|
||||
|
||||
if (painlessClassBuilder == null) {
|
||||
throw new IllegalArgumentException("target class [" + targetCanonicalClassName + "] not found for imported method " +
|
||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
|
||||
}
|
||||
|
||||
int typeParametersSize = typeParameters.size();
|
||||
List<Class<?>> javaTypeParameters = new ArrayList<>(typeParametersSize);
|
||||
|
||||
for (Class<?> typeParameter : typeParameters) {
|
||||
if (isValidType(typeParameter) == false) {
|
||||
throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " +
|
||||
"not found for imported method [[" + targetCanonicalClassName + "], [" + methodName + "], " +
|
||||
typesToCanonicalTypeNames(typeParameters) + "]");
|
||||
}
|
||||
|
||||
javaTypeParameters.add(typeToJavaType(typeParameter));
|
||||
}
|
||||
|
||||
if (isValidType(returnType) == false) {
|
||||
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(returnType) + "] not found for imported method " +
|
||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
|
||||
}
|
||||
|
||||
Method javaMethod;
|
||||
|
||||
try {
|
||||
javaMethod = targetClass.getMethod(methodName, javaTypeParameters.toArray(new Class<?>[typeParametersSize]));
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
throw new IllegalArgumentException("imported method reflection object [[" + targetCanonicalClassName + "], " +
|
||||
"[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", nsme);
|
||||
}
|
||||
|
||||
if (javaMethod.getReturnType() != typeToJavaType(returnType)) {
|
||||
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " +
|
||||
"does not match the specified returned type [" + typeToCanonicalTypeName(returnType) + "] " +
|
||||
"for imported method [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
|
||||
typesToCanonicalTypeNames(typeParameters) + "]");
|
||||
}
|
||||
|
||||
if (Modifier.isStatic(javaMethod.getModifiers()) == false) {
|
||||
throw new IllegalArgumentException("imported method [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
|
||||
typesToCanonicalTypeNames(typeParameters) + "] must be static");
|
||||
}
|
||||
|
||||
String painlessMethodKey = buildPainlessMethodKey(methodName, typeParametersSize);
|
||||
|
||||
if (painlessMethodKeysToPainlessBindings.containsKey(painlessMethodKey)) {
|
||||
throw new IllegalArgumentException("imported method and binding cannot have the same name [" + methodName + "]");
|
||||
}
|
||||
|
||||
PainlessMethod importedPainlessMethod = painlessMethodKeysToImportedPainlessMethods.get(painlessMethodKey);
|
||||
|
||||
if (importedPainlessMethod == null) {
|
||||
MethodHandle methodHandle;
|
||||
|
||||
try {
|
||||
methodHandle = MethodHandles.publicLookup().in(targetClass).unreflect(javaMethod);
|
||||
} catch (IllegalAccessException iae) {
|
||||
throw new IllegalArgumentException("imported method handle [[" + targetClass.getCanonicalName() + "], " +
|
||||
"[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", iae);
|
||||
}
|
||||
|
||||
MethodType methodType = methodHandle.type();
|
||||
|
||||
importedPainlessMethod = painlessMethodCache.computeIfAbsent(
|
||||
new PainlessMethodCacheKey(targetClass, methodName, returnType, typeParameters),
|
||||
key -> new PainlessMethod(javaMethod, targetClass, returnType, typeParameters, methodHandle, methodType));
|
||||
|
||||
painlessMethodKeysToImportedPainlessMethods.put(painlessMethodKey, importedPainlessMethod);
|
||||
} else if (importedPainlessMethod.returnType == returnType &&
|
||||
importedPainlessMethod.typeParameters.equals(typeParameters) == false) {
|
||||
throw new IllegalArgumentException("cannot have imported methods " +
|
||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " +
|
||||
"[" + typeToCanonicalTypeName(returnType) + "], " +
|
||||
typesToCanonicalTypeNames(typeParameters) + "] and " +
|
||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " +
|
||||
"[" + typeToCanonicalTypeName(importedPainlessMethod.returnType) + "], " +
|
||||
typesToCanonicalTypeNames(importedPainlessMethod.typeParameters) + "] " +
|
||||
"with the same arity and different return type or type parameters");
|
||||
}
|
||||
}
|
||||
|
||||
public void addPainlessBinding(ClassLoader classLoader, String targetJavaClassName,
|
||||
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||
|
||||
|
@ -937,6 +1094,11 @@ public final class PainlessLookupBuilder {
|
|||
}
|
||||
|
||||
String painlessMethodKey = buildPainlessMethodKey(methodName, constructorTypeParametersSize + methodTypeParametersSize);
|
||||
|
||||
if (painlessMethodKeysToImportedPainlessMethods.containsKey(painlessMethodKey)) {
|
||||
throw new IllegalArgumentException("binding and imported method cannot have the same name [" + methodName + "]");
|
||||
}
|
||||
|
||||
PainlessBinding painlessBinding = painlessMethodKeysToPainlessBindings.get(painlessMethodKey);
|
||||
|
||||
if (painlessBinding == null) {
|
||||
|
@ -976,7 +1138,8 @@ public final class PainlessLookupBuilder {
|
|||
classesToPainlessClasses.put(painlessClassBuilderEntry.getKey(), painlessClassBuilderEntry.getValue().build());
|
||||
}
|
||||
|
||||
return new PainlessLookup(canonicalClassNamesToClasses, classesToPainlessClasses, painlessMethodKeysToPainlessBindings);
|
||||
return new PainlessLookup(canonicalClassNamesToClasses, classesToPainlessClasses,
|
||||
painlessMethodKeysToImportedPainlessMethods, painlessMethodKeysToPainlessBindings);
|
||||
}
|
||||
|
||||
private void copyPainlessClassMembers() {
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.elasticsearch.painless.Locals.LocalMethod;
|
|||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.lookup.PainlessBinding;
|
||||
import org.elasticsearch.painless.lookup.PainlessMethod;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.commons.Method;
|
||||
|
@ -45,6 +46,7 @@ public final class ECallLocal extends AExpression {
|
|||
private final List<AExpression> arguments;
|
||||
|
||||
private LocalMethod method = null;
|
||||
private PainlessMethod imported = null;
|
||||
private PainlessBinding binding = null;
|
||||
|
||||
public ECallLocal(Location location, String name, List<AExpression> arguments) {
|
||||
|
@ -65,16 +67,33 @@ public final class ECallLocal extends AExpression {
|
|||
void analyze(Locals locals) {
|
||||
method = locals.getMethod(name, arguments.size());
|
||||
|
||||
|
||||
if (method == null) {
|
||||
imported = locals.getPainlessLookup().lookupImportedPainlessMethod(name, arguments.size());
|
||||
|
||||
if (imported == null) {
|
||||
binding = locals.getPainlessLookup().lookupPainlessBinding(name, arguments.size());
|
||||
|
||||
if (binding == null) {
|
||||
throw createError(new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments."));
|
||||
throw createError(
|
||||
new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() + "] arguments."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Class<?>> typeParameters = new ArrayList<>(method == null ? binding.typeParameters : method.typeParameters);
|
||||
List<Class<?>> typeParameters;
|
||||
|
||||
if (method != null) {
|
||||
typeParameters = new ArrayList<>(method.typeParameters);
|
||||
actual = method.returnType;
|
||||
} else if (imported != null) {
|
||||
typeParameters = new ArrayList<>(imported.typeParameters);
|
||||
actual = imported.returnType;
|
||||
} else if (binding != null) {
|
||||
typeParameters = new ArrayList<>(binding.typeParameters);
|
||||
actual = binding.returnType;
|
||||
} else {
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
|
||||
for (int argument = 0; argument < arguments.size(); ++argument) {
|
||||
AExpression expression = arguments.get(argument);
|
||||
|
@ -86,14 +105,26 @@ public final class ECallLocal extends AExpression {
|
|||
}
|
||||
|
||||
statement = true;
|
||||
actual = method == null ? binding.returnType : method.returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(MethodWriter writer, Globals globals) {
|
||||
writer.writeDebugInfo(location);
|
||||
|
||||
if (method == null) {
|
||||
if (method != null) {
|
||||
for (AExpression argument : arguments) {
|
||||
argument.write(writer, globals);
|
||||
}
|
||||
|
||||
writer.invokeStatic(CLASS_TYPE, new Method(method.name, method.methodType.toMethodDescriptorString()));
|
||||
} else if (imported != null) {
|
||||
for (AExpression argument : arguments) {
|
||||
argument.write(writer, globals);
|
||||
}
|
||||
|
||||
writer.invokeStatic(Type.getType(imported.targetClass),
|
||||
new Method(imported.javaMethod.getName(), imported.methodType.toMethodDescriptorString()));
|
||||
} else if (binding != null) {
|
||||
String name = globals.addBinding(binding.javaConstructor.getDeclaringClass());
|
||||
Type type = Type.getType(binding.javaConstructor.getDeclaringClass());
|
||||
int javaConstructorParameterCount = binding.javaConstructor.getParameterCount();
|
||||
|
@ -124,11 +155,7 @@ public final class ECallLocal extends AExpression {
|
|||
|
||||
writer.invokeVirtual(type, Method.getMethod(binding.javaMethod));
|
||||
} else {
|
||||
for (AExpression argument : arguments) {
|
||||
argument.write(writer, globals);
|
||||
}
|
||||
|
||||
writer.invokeStatic(CLASS_TYPE, new Method(method.name, method.methodType.toMethodDescriptorString()));
|
||||
throw new IllegalStateException("Illegal tree structure.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -176,6 +176,7 @@ class org.elasticsearch.painless.FeatureTest no_import {
|
|||
}
|
||||
|
||||
# for testing
|
||||
static {
|
||||
static_import {
|
||||
float staticAddFloatsTest(float, float) from_class org.elasticsearch.painless.FeatureTest
|
||||
int testAddWithState(int, int, int, double) bound_to org.elasticsearch.painless.BindingTest
|
||||
}
|
|
@ -133,4 +133,8 @@ public class BasicAPITests extends ScriptTestCase {
|
|||
public void testNoSemicolon() {
|
||||
assertEquals(true, exec("def x = true; if (x) return x"));
|
||||
}
|
||||
|
||||
public void testStatic() {
|
||||
assertEquals(15.5f, exec("staticAddFloatsTest(6.5f, 9.0f)"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ import static org.hamcrest.Matchers.hasSize;
|
|||
* Typically just asserts the output of {@code exec()}
|
||||
*/
|
||||
public abstract class ScriptTestCase extends ESTestCase {
|
||||
private static final PainlessLookup PAINLESS_LOOKUP = PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS);
|
||||
|
||||
protected PainlessScriptEngine scriptEngine;
|
||||
|
||||
@Before
|
||||
|
@ -92,12 +94,12 @@ public abstract class ScriptTestCase extends ESTestCase {
|
|||
public Object exec(String script, Map<String, Object> vars, Map<String,String> compileParams, Scorer scorer, boolean picky) {
|
||||
// test for ambiguity errors before running the actual script if picky is true
|
||||
if (picky) {
|
||||
PainlessLookup painlessLookup = PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS);
|
||||
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, GenericElasticsearchScript.class);
|
||||
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(PAINLESS_LOOKUP, GenericElasticsearchScript.class);
|
||||
CompilerSettings pickySettings = new CompilerSettings();
|
||||
pickySettings.setPicky(true);
|
||||
pickySettings.setRegexesEnabled(CompilerSettings.REGEX_ENABLED.get(scriptEngineSettings()));
|
||||
Walker.buildPainlessTree(scriptClassInfo, new MainMethodReserved(), getTestName(), script, pickySettings, painlessLookup, null);
|
||||
Walker.buildPainlessTree(
|
||||
scriptClassInfo, new MainMethodReserved(), getTestName(), script, pickySettings, PAINLESS_LOOKUP, null);
|
||||
}
|
||||
// test actual script execution
|
||||
ExecutableScript.Factory factory = scriptEngine.compile(null, script, ExecutableScript.CONTEXT, compileParams);
|
||||
|
|
Loading…
Reference in New Issue