Painless: Add Bindings (#33042)
Add bindings that allow some specialized methods to store permanent state between script executions.
This commit is contained in:
parent
6daf8115d6
commit
b52818ec6f
|
@ -61,9 +61,12 @@ public final class Whitelist {
|
||||||
/** The {@link List} of all the whitelisted Painless classes. */
|
/** The {@link List} of all the whitelisted Painless classes. */
|
||||||
public final List<WhitelistClass> whitelistClasses;
|
public final List<WhitelistClass> whitelistClasses;
|
||||||
|
|
||||||
|
public final List<WhitelistBinding> whitelistBindings;
|
||||||
|
|
||||||
/** Standard constructor. All values must be not {@code null}. */
|
/** Standard constructor. All values must be not {@code null}. */
|
||||||
public Whitelist(ClassLoader classLoader, List<WhitelistClass> whitelistClasses) {
|
public Whitelist(ClassLoader classLoader, List<WhitelistClass> whitelistClasses, List<WhitelistBinding> whitelistBindings) {
|
||||||
this.classLoader = Objects.requireNonNull(classLoader);
|
this.classLoader = Objects.requireNonNull(classLoader);
|
||||||
this.whitelistClasses = Collections.unmodifiableList(Objects.requireNonNull(whitelistClasses));
|
this.whitelistClasses = Collections.unmodifiableList(Objects.requireNonNull(whitelistClasses));
|
||||||
|
this.whitelistBindings = Collections.unmodifiableList(Objects.requireNonNull(whitelistBindings));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* 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.spi;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A binding represents a method call that stores state. Each binding class must have exactly one
|
||||||
|
* public constructor and one public method excluding those inherited directly from {@link Object}.
|
||||||
|
* The canonical type name parameters provided must match those of the constructor and method combined.
|
||||||
|
* The constructor for a binding class will be called when the binding method is called for the first
|
||||||
|
* time at which point state may be stored for the arguments passed into the constructor. The method
|
||||||
|
* for a binding class will be called each time the binding method is called and may use the previously
|
||||||
|
* stored state.
|
||||||
|
*/
|
||||||
|
public class WhitelistBinding {
|
||||||
|
|
||||||
|
/** Information about where this constructor was whitelisted from. */
|
||||||
|
public final String origin;
|
||||||
|
|
||||||
|
/** The Java class name this binding represents. */
|
||||||
|
public final String targetJavaClassName;
|
||||||
|
|
||||||
|
/** The method name for this binding. */
|
||||||
|
public final String methodName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The canonical type name for the return type.
|
||||||
|
*/
|
||||||
|
public final String returnCanonicalTypeName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link List} of {@link String}s that are the Painless type names for the parameters of the
|
||||||
|
* constructor which can be used to look up the Java constructor through reflection.
|
||||||
|
*/
|
||||||
|
public final List<String> canonicalTypeNameParameters;
|
||||||
|
|
||||||
|
/** Standard constructor. All values must be not {@code null}. */
|
||||||
|
public WhitelistBinding(String origin, String targetJavaClassName,
|
||||||
|
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||||
|
|
||||||
|
this.origin = Objects.requireNonNull(origin);
|
||||||
|
this.targetJavaClassName = Objects.requireNonNull(targetJavaClassName);
|
||||||
|
|
||||||
|
this.methodName = Objects.requireNonNull(methodName);
|
||||||
|
this.returnCanonicalTypeName = Objects.requireNonNull(returnCanonicalTypeName);
|
||||||
|
this.canonicalTypeNameParameters = Objects.requireNonNull(canonicalTypeNameParameters);
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,9 +62,8 @@ public final class WhitelistClass {
|
||||||
|
|
||||||
/** Standard constructor. All values must be not {@code null}. */
|
/** Standard constructor. All values must be not {@code null}. */
|
||||||
public WhitelistClass(String origin, String javaClassName, boolean noImport,
|
public WhitelistClass(String origin, String javaClassName, boolean noImport,
|
||||||
List<WhitelistConstructor> whitelistConstructors,
|
List<WhitelistConstructor> whitelistConstructors, List<WhitelistMethod> whitelistMethods, List<WhitelistField> whitelistFields)
|
||||||
List<WhitelistMethod> whitelistMethods,
|
{
|
||||||
List<WhitelistField> whitelistFields) {
|
|
||||||
|
|
||||||
this.origin = Objects.requireNonNull(origin);
|
this.origin = Objects.requireNonNull(origin);
|
||||||
this.javaClassName = Objects.requireNonNull(javaClassName);
|
this.javaClassName = Objects.requireNonNull(javaClassName);
|
||||||
|
|
|
@ -133,6 +133,7 @@ public final class WhitelistLoader {
|
||||||
*/
|
*/
|
||||||
public static Whitelist loadFromResourceFiles(Class<?> resource, String... filepaths) {
|
public static Whitelist loadFromResourceFiles(Class<?> resource, String... filepaths) {
|
||||||
List<WhitelistClass> whitelistClasses = new ArrayList<>();
|
List<WhitelistClass> whitelistClasses = new ArrayList<>();
|
||||||
|
List<WhitelistBinding> whitelistBindings = new ArrayList<>();
|
||||||
|
|
||||||
// Execute a single pass through the whitelist text files. This will gather all the
|
// Execute a single pass through the whitelist text files. This will gather all the
|
||||||
// constructors, methods, augmented methods, and fields for each whitelisted class.
|
// constructors, methods, augmented methods, and fields for each whitelisted class.
|
||||||
|
@ -143,6 +144,7 @@ public final class WhitelistLoader {
|
||||||
try (LineNumberReader reader = new LineNumberReader(
|
try (LineNumberReader reader = new LineNumberReader(
|
||||||
new InputStreamReader(resource.getResourceAsStream(filepath), StandardCharsets.UTF_8))) {
|
new InputStreamReader(resource.getResourceAsStream(filepath), StandardCharsets.UTF_8))) {
|
||||||
|
|
||||||
|
String parseType = null;
|
||||||
String whitelistClassOrigin = null;
|
String whitelistClassOrigin = null;
|
||||||
String javaClassName = null;
|
String javaClassName = null;
|
||||||
boolean noImport = false;
|
boolean noImport = false;
|
||||||
|
@ -168,6 +170,10 @@ public final class WhitelistLoader {
|
||||||
"invalid class definition: failed to parse class opening bracket [" + line + "]");
|
"invalid class definition: failed to parse class opening bracket [" + line + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parseType != null) {
|
||||||
|
throw new IllegalArgumentException("invalid definition: cannot embed class definition [" + line + "]");
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the Java class name.
|
// Parse the Java class name.
|
||||||
String[] tokens = line.substring(5, line.length() - 1).trim().split("\\s+");
|
String[] tokens = line.substring(5, line.length() - 1).trim().split("\\s+");
|
||||||
|
|
||||||
|
@ -178,6 +184,7 @@ public final class WhitelistLoader {
|
||||||
throw new IllegalArgumentException("invalid class definition: failed to parse class name [" + line + "]");
|
throw new IllegalArgumentException("invalid class definition: failed to parse class name [" + line + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parseType = "class";
|
||||||
whitelistClassOrigin = "[" + filepath + "]:[" + number + "]";
|
whitelistClassOrigin = "[" + filepath + "]:[" + number + "]";
|
||||||
javaClassName = tokens[0];
|
javaClassName = tokens[0];
|
||||||
|
|
||||||
|
@ -185,34 +192,117 @@ public final class WhitelistLoader {
|
||||||
whitelistConstructors = new ArrayList<>();
|
whitelistConstructors = new ArrayList<>();
|
||||||
whitelistMethods = new ArrayList<>();
|
whitelistMethods = new ArrayList<>();
|
||||||
whitelistFields = new ArrayList<>();
|
whitelistFields = new ArrayList<>();
|
||||||
|
} else if (line.startsWith("static ")) {
|
||||||
// Handle the end of a class, by creating a new WhitelistClass with all the previously gathered
|
// Ensure the final token of the line is '{'.
|
||||||
// constructors, methods, augmented methods, and fields, and adding it to the list of whitelisted classes.
|
if (line.endsWith("{") == false) {
|
||||||
// Expects the following format: '}' '\n'
|
throw new IllegalArgumentException(
|
||||||
} else if (line.equals("}")) {
|
"invalid static definition: failed to parse static opening bracket [" + line + "]");
|
||||||
if (javaClassName == null) {
|
|
||||||
throw new IllegalArgumentException("invalid class definition: extraneous closing bracket");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parseType != null) {
|
||||||
|
throw new IllegalArgumentException("invalid definition: cannot embed static definition [" + line + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
parseType = "static";
|
||||||
|
|
||||||
|
// Handle the end of a definition and reset all previously gathered values.
|
||||||
|
// Expects the following format: '}' '\n'
|
||||||
|
} else if (line.equals("}")) {
|
||||||
|
if (parseType == null) {
|
||||||
|
throw new IllegalArgumentException("invalid definition: extraneous closing bracket");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new WhitelistClass with all the previously gathered constructors, methods,
|
||||||
|
// augmented methods, and fields, and add it to the list of whitelisted classes.
|
||||||
|
if ("class".equals(parseType)) {
|
||||||
whitelistClasses.add(new WhitelistClass(whitelistClassOrigin, javaClassName, noImport,
|
whitelistClasses.add(new WhitelistClass(whitelistClassOrigin, javaClassName, noImport,
|
||||||
whitelistConstructors, whitelistMethods, whitelistFields));
|
whitelistConstructors, whitelistMethods, whitelistFields));
|
||||||
|
|
||||||
// Set all the variables to null to ensure a new class definition is found before other parsable values.
|
|
||||||
whitelistClassOrigin = null;
|
whitelistClassOrigin = null;
|
||||||
javaClassName = null;
|
javaClassName = null;
|
||||||
noImport = false;
|
noImport = false;
|
||||||
whitelistConstructors = null;
|
whitelistConstructors = null;
|
||||||
whitelistMethods = null;
|
whitelistMethods = null;
|
||||||
whitelistFields = null;
|
whitelistFields = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle all other valid cases.
|
// 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)) {
|
||||||
|
// Mark the origin of this parsable object.
|
||||||
|
String origin = "[" + filepath + "]:[" + number + "]";
|
||||||
|
|
||||||
|
// Parse the tokens prior to the method parameters.
|
||||||
|
int parameterStartIndex = line.indexOf('(');
|
||||||
|
|
||||||
|
if (parameterStartIndex == -1) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"illegal static definition: start of method parameters not found [" + line + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] tokens = line.substring(0, parameterStartIndex).trim().split("\\s+");
|
||||||
|
|
||||||
|
String methodName;
|
||||||
|
|
||||||
|
// Based on the number of tokens, look up the Java method name.
|
||||||
|
if (tokens.length == 2) {
|
||||||
|
methodName = tokens[1];
|
||||||
} else {
|
} else {
|
||||||
|
throw new IllegalArgumentException("invalid method definition: unexpected format [" + line + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
String returnCanonicalTypeName = tokens[0];
|
||||||
|
|
||||||
|
// Parse the method parameters.
|
||||||
|
int parameterEndIndex = line.indexOf(')');
|
||||||
|
|
||||||
|
if (parameterEndIndex == -1) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"illegal static definition: end of method parameters not found [" + line + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] canonicalTypeNameParameters =
|
||||||
|
line.substring(parameterStartIndex + 1, parameterEndIndex).replaceAll("\\s+", "").split(",");
|
||||||
|
|
||||||
|
// Handle the case for a method with no parameters.
|
||||||
|
if ("".equals(canonicalTypeNameParameters[0])) {
|
||||||
|
canonicalTypeNameParameters = new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the static type and class.
|
||||||
|
tokens = line.substring(parameterEndIndex + 1).trim().split("\\s+");
|
||||||
|
|
||||||
|
String staticType;
|
||||||
|
String targetJavaClassName;
|
||||||
|
|
||||||
|
// Based on the number of tokens, look up the type and class.
|
||||||
|
if (tokens.length == 2) {
|
||||||
|
staticType = 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 + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistBindings.add(new WhitelistBinding(origin, targetJavaClassName,
|
||||||
|
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
|
||||||
|
|
||||||
|
// Handle class definition types.
|
||||||
|
} else if ("class".equals(parseType)) {
|
||||||
// Mark the origin of this parsable object.
|
// Mark the origin of this parsable object.
|
||||||
String origin = "[" + filepath + "]:[" + number + "]";
|
String origin = "[" + filepath + "]:[" + number + "]";
|
||||||
|
|
||||||
// Ensure we have a defined class before adding any constructors, methods, augmented methods, or fields.
|
// Ensure we have a defined class before adding any constructors, methods, augmented methods, or fields.
|
||||||
if (javaClassName == null) {
|
if (parseType == null) {
|
||||||
throw new IllegalArgumentException("invalid object definition: expected a class name [" + line + "]");
|
throw new IllegalArgumentException("invalid definition: expected one of ['class', 'static'] [" + line + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the case for a constructor definition.
|
// Handle the case for a constructor definition.
|
||||||
|
@ -245,23 +335,23 @@ public final class WhitelistLoader {
|
||||||
|
|
||||||
// Parse the tokens prior to the method parameters.
|
// Parse the tokens prior to the method parameters.
|
||||||
int parameterIndex = line.indexOf('(');
|
int parameterIndex = line.indexOf('(');
|
||||||
String[] tokens = line.trim().substring(0, parameterIndex).split("\\s+");
|
String[] tokens = line.substring(0, parameterIndex).trim().split("\\s+");
|
||||||
|
|
||||||
String javaMethodName;
|
String methodName;
|
||||||
String javaAugmentedClassName;
|
String javaAugmentedClassName;
|
||||||
|
|
||||||
// Based on the number of tokens, look up the Java method name and if provided the Java augmented class.
|
// Based on the number of tokens, look up the Java method name and if provided the Java augmented class.
|
||||||
if (tokens.length == 2) {
|
if (tokens.length == 2) {
|
||||||
javaMethodName = tokens[1];
|
methodName = tokens[1];
|
||||||
javaAugmentedClassName = null;
|
javaAugmentedClassName = null;
|
||||||
} else if (tokens.length == 3) {
|
} else if (tokens.length == 3) {
|
||||||
javaMethodName = tokens[2];
|
methodName = tokens[2];
|
||||||
javaAugmentedClassName = tokens[1];
|
javaAugmentedClassName = tokens[1];
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("invalid method definition: unexpected format [" + line + "]");
|
throw new IllegalArgumentException("invalid method definition: unexpected format [" + line + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
String painlessReturnTypeName = tokens[0];
|
String returnCanonicalTypeName = tokens[0];
|
||||||
|
|
||||||
// Parse the method parameters.
|
// Parse the method parameters.
|
||||||
tokens = line.substring(parameterIndex + 1, line.length() - 1).replaceAll("\\s+", "").split(",");
|
tokens = line.substring(parameterIndex + 1, line.length() - 1).replaceAll("\\s+", "").split(",");
|
||||||
|
@ -271,8 +361,8 @@ public final class WhitelistLoader {
|
||||||
tokens = new String[0];
|
tokens = new String[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
whitelistMethods.add(new WhitelistMethod(origin, javaAugmentedClassName, javaMethodName,
|
whitelistMethods.add(new WhitelistMethod(origin, javaAugmentedClassName, methodName,
|
||||||
painlessReturnTypeName, Arrays.asList(tokens)));
|
returnCanonicalTypeName, Arrays.asList(tokens)));
|
||||||
|
|
||||||
// Handle the case for a field definition.
|
// Handle the case for a field definition.
|
||||||
// Expects the following format: ID ID '\n'
|
// Expects the following format: ID ID '\n'
|
||||||
|
@ -287,20 +377,23 @@ public final class WhitelistLoader {
|
||||||
|
|
||||||
whitelistFields.add(new WhitelistField(origin, tokens[1], tokens[0]));
|
whitelistFields.add(new WhitelistField(origin, tokens[1], tokens[0]));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("invalid definition: unable to parse line [" + line + "]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure all classes end with a '}' token before the end of the file.
|
// Ensure all classes end with a '}' token before the end of the file.
|
||||||
if (javaClassName != null) {
|
if (javaClassName != null) {
|
||||||
throw new IllegalArgumentException("invalid class definition: expected closing bracket");
|
throw new IllegalArgumentException("invalid definition: expected closing bracket");
|
||||||
}
|
}
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
throw new RuntimeException("error in [" + filepath + "] at line [" + number + "]", exception);
|
throw new RuntimeException("error in [" + filepath + "] at line [" + number + "]", exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassLoader loader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>)resource::getClassLoader);
|
ClassLoader loader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>)resource::getClassLoader);
|
||||||
|
|
||||||
return new Whitelist(loader, whitelistClasses);
|
return new Whitelist(loader, whitelistClasses, whitelistBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private WhitelistLoader() {}
|
private WhitelistLoader() {}
|
||||||
|
|
|
@ -68,6 +68,7 @@ public class WhitelistMethod {
|
||||||
*/
|
*/
|
||||||
public WhitelistMethod(String origin, String augmentedCanonicalClassName, String methodName,
|
public WhitelistMethod(String origin, String augmentedCanonicalClassName, String methodName,
|
||||||
String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||||
|
|
||||||
this.origin = Objects.requireNonNull(origin);
|
this.origin = Objects.requireNonNull(origin);
|
||||||
this.augmentedCanonicalClassName = augmentedCanonicalClassName;
|
this.augmentedCanonicalClassName = augmentedCanonicalClassName;
|
||||||
this.methodName = methodName;
|
this.methodName = methodName;
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public class BindingTest {
|
||||||
|
public int state;
|
||||||
|
|
||||||
|
public BindingTest(int state0, int state1) {
|
||||||
|
this.state = state0 + state1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int testAddWithState(int stateless) {
|
||||||
|
return stateless + state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ import java.util.Map;
|
||||||
public class Globals {
|
public class Globals {
|
||||||
private final Map<String,SFunction> syntheticMethods = new HashMap<>();
|
private final Map<String,SFunction> syntheticMethods = new HashMap<>();
|
||||||
private final Map<String,Constant> constantInitializers = new HashMap<>();
|
private final Map<String,Constant> constantInitializers = new HashMap<>();
|
||||||
|
private final Map<String,Class<?>> bindings = new HashMap<>();
|
||||||
private final BitSet statements;
|
private final BitSet statements;
|
||||||
|
|
||||||
/** Create a new Globals from the set of statement boundaries */
|
/** Create a new Globals from the set of statement boundaries */
|
||||||
|
@ -55,6 +56,14 @@ public class Globals {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Adds a new binding to be written as a local variable */
|
||||||
|
public String addBinding(Class<?> type) {
|
||||||
|
String name = "$binding$" + bindings.size();
|
||||||
|
bindings.put(name, type);
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the current synthetic methods */
|
/** Returns the current synthetic methods */
|
||||||
public Map<String,SFunction> getSyntheticMethods() {
|
public Map<String,SFunction> getSyntheticMethods() {
|
||||||
return syntheticMethods;
|
return syntheticMethods;
|
||||||
|
@ -65,6 +74,11 @@ public class Globals {
|
||||||
return constantInitializers;
|
return constantInitializers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the current bindings */
|
||||||
|
public Map<String,Class<?>> getBindings() {
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the set of statement boundaries */
|
/** Returns the set of statement boundaries */
|
||||||
public BitSet getStatements() {
|
public BitSet getStatements() {
|
||||||
return statements;
|
return statements;
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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.lookup;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PainlessBinding {
|
||||||
|
|
||||||
|
public final Constructor<?> javaConstructor;
|
||||||
|
public final Method javaMethod;
|
||||||
|
|
||||||
|
public final Class<?> returnType;
|
||||||
|
public final List<Class<?>> typeParameters;
|
||||||
|
|
||||||
|
PainlessBinding(Constructor<?> javaConstructor, Method javaMethod, Class<?> returnType, List<Class<?>> typeParameters) {
|
||||||
|
this.javaConstructor = javaConstructor;
|
||||||
|
this.javaMethod = javaMethod;
|
||||||
|
|
||||||
|
this.returnType = returnType;
|
||||||
|
this.typeParameters = typeParameters;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public final class PainlessClass {
|
public final class PainlessClass {
|
||||||
|
|
||||||
public final Map<String, PainlessConstructor> constructors;
|
public final Map<String, PainlessConstructor> constructors;
|
||||||
|
|
||||||
public final Map<String, PainlessMethod> staticMethods;
|
public final Map<String, PainlessMethod> staticMethods;
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
final class PainlessClassBuilder {
|
final class PainlessClassBuilder {
|
||||||
|
|
||||||
final Map<String, PainlessConstructor> constructors;
|
final Map<String, PainlessConstructor> constructors;
|
||||||
|
|
||||||
final Map<String, PainlessMethod> staticMethods;
|
final Map<String, PainlessMethod> staticMethods;
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.lang.reflect.Constructor;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PainlessConstructor {
|
public class PainlessConstructor {
|
||||||
|
|
||||||
public final Constructor<?> javaConstructor;
|
public final Constructor<?> javaConstructor;
|
||||||
public final List<Class<?>> typeParameters;
|
public final List<Class<?>> typeParameters;
|
||||||
public final MethodHandle methodHandle;
|
public final MethodHandle methodHandle;
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
public final class PainlessField {
|
public final class PainlessField {
|
||||||
|
|
||||||
public final Field javaField;
|
public final Field javaField;
|
||||||
public final Class<?> typeParameter;
|
public final Class<?> typeParameter;
|
||||||
|
|
||||||
|
|
|
@ -37,12 +37,17 @@ public final class PainlessLookup {
|
||||||
private final Map<String, Class<?>> canonicalClassNamesToClasses;
|
private final Map<String, Class<?>> canonicalClassNamesToClasses;
|
||||||
private final Map<Class<?>, PainlessClass> classesToPainlessClasses;
|
private final Map<Class<?>, PainlessClass> classesToPainlessClasses;
|
||||||
|
|
||||||
PainlessLookup(Map<String, Class<?>> canonicalClassNamesToClasses, Map<Class<?>, PainlessClass> classesToPainlessClasses) {
|
private final Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings;
|
||||||
|
|
||||||
|
PainlessLookup(Map<String, Class<?>> canonicalClassNamesToClasses, Map<Class<?>, PainlessClass> classesToPainlessClasses,
|
||||||
|
Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings) {
|
||||||
Objects.requireNonNull(canonicalClassNamesToClasses);
|
Objects.requireNonNull(canonicalClassNamesToClasses);
|
||||||
Objects.requireNonNull(classesToPainlessClasses);
|
Objects.requireNonNull(classesToPainlessClasses);
|
||||||
|
|
||||||
this.canonicalClassNamesToClasses = Collections.unmodifiableMap(canonicalClassNamesToClasses);
|
this.canonicalClassNamesToClasses = Collections.unmodifiableMap(canonicalClassNamesToClasses);
|
||||||
this.classesToPainlessClasses = Collections.unmodifiableMap(classesToPainlessClasses);
|
this.classesToPainlessClasses = Collections.unmodifiableMap(classesToPainlessClasses);
|
||||||
|
|
||||||
|
this.painlessMethodKeysToPainlessBindings = Collections.unmodifiableMap(painlessMethodKeysToPainlessBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValidCanonicalClassName(String canonicalClassName) {
|
public boolean isValidCanonicalClassName(String canonicalClassName) {
|
||||||
|
@ -162,6 +167,14 @@ public final class PainlessLookup {
|
||||||
return painlessField;
|
return painlessField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PainlessBinding lookupPainlessBinding(String methodName, int arity) {
|
||||||
|
Objects.requireNonNull(methodName);
|
||||||
|
|
||||||
|
String painlessMethodKey = buildPainlessMethodKey(methodName, arity);
|
||||||
|
|
||||||
|
return painlessMethodKeysToPainlessBindings.get(painlessMethodKey);
|
||||||
|
}
|
||||||
|
|
||||||
public PainlessMethod lookupFunctionalInterfacePainlessMethod(Class<?> targetClass) {
|
public PainlessMethod lookupFunctionalInterfacePainlessMethod(Class<?> targetClass) {
|
||||||
PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass);
|
PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
package org.elasticsearch.painless.lookup;
|
package org.elasticsearch.painless.lookup;
|
||||||
|
|
||||||
import org.elasticsearch.painless.spi.Whitelist;
|
import org.elasticsearch.painless.spi.Whitelist;
|
||||||
|
import org.elasticsearch.painless.spi.WhitelistBinding;
|
||||||
import org.elasticsearch.painless.spi.WhitelistClass;
|
import org.elasticsearch.painless.spi.WhitelistClass;
|
||||||
import org.elasticsearch.painless.spi.WhitelistConstructor;
|
import org.elasticsearch.painless.spi.WhitelistConstructor;
|
||||||
import org.elasticsearch.painless.spi.WhitelistField;
|
import org.elasticsearch.painless.spi.WhitelistField;
|
||||||
|
@ -52,11 +53,11 @@ public final class PainlessLookupBuilder {
|
||||||
|
|
||||||
private static class PainlessConstructorCacheKey {
|
private static class PainlessConstructorCacheKey {
|
||||||
|
|
||||||
private final Class<?> targetType;
|
private final Class<?> targetClass;
|
||||||
private final List<Class<?>> typeParameters;
|
private final List<Class<?>> typeParameters;
|
||||||
|
|
||||||
private PainlessConstructorCacheKey(Class<?> targetType, List<Class<?>> typeParameters) {
|
private PainlessConstructorCacheKey(Class<?> targetClass, List<Class<?>> typeParameters) {
|
||||||
this.targetType = targetType;
|
this.targetClass = targetClass;
|
||||||
this.typeParameters = Collections.unmodifiableList(typeParameters);
|
this.typeParameters = Collections.unmodifiableList(typeParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,25 +73,27 @@ public final class PainlessLookupBuilder {
|
||||||
|
|
||||||
PainlessConstructorCacheKey that = (PainlessConstructorCacheKey)object;
|
PainlessConstructorCacheKey that = (PainlessConstructorCacheKey)object;
|
||||||
|
|
||||||
return Objects.equals(targetType, that.targetType) &&
|
return Objects.equals(targetClass, that.targetClass) &&
|
||||||
Objects.equals(typeParameters, that.typeParameters);
|
Objects.equals(typeParameters, that.typeParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(targetType, typeParameters);
|
return Objects.hash(targetClass, typeParameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class PainlessMethodCacheKey {
|
private static class PainlessMethodCacheKey {
|
||||||
|
|
||||||
private final Class<?> targetType;
|
private final Class<?> targetClass;
|
||||||
private final String methodName;
|
private final String methodName;
|
||||||
|
private final Class<?> returnType;
|
||||||
private final List<Class<?>> typeParameters;
|
private final List<Class<?>> typeParameters;
|
||||||
|
|
||||||
private PainlessMethodCacheKey(Class<?> targetType, String methodName, List<Class<?>> typeParameters) {
|
private PainlessMethodCacheKey(Class<?> targetClass, String methodName, Class<?> returnType, List<Class<?>> typeParameters) {
|
||||||
this.targetType = targetType;
|
this.targetClass = targetClass;
|
||||||
this.methodName = methodName;
|
this.methodName = methodName;
|
||||||
|
this.returnType = returnType;
|
||||||
this.typeParameters = Collections.unmodifiableList(typeParameters);
|
this.typeParameters = Collections.unmodifiableList(typeParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,25 +109,26 @@ public final class PainlessLookupBuilder {
|
||||||
|
|
||||||
PainlessMethodCacheKey that = (PainlessMethodCacheKey)object;
|
PainlessMethodCacheKey that = (PainlessMethodCacheKey)object;
|
||||||
|
|
||||||
return Objects.equals(targetType, that.targetType) &&
|
return Objects.equals(targetClass, that.targetClass) &&
|
||||||
Objects.equals(methodName, that.methodName) &&
|
Objects.equals(methodName, that.methodName) &&
|
||||||
|
Objects.equals(returnType, that.returnType) &&
|
||||||
Objects.equals(typeParameters, that.typeParameters);
|
Objects.equals(typeParameters, that.typeParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(targetType, methodName, typeParameters);
|
return Objects.hash(targetClass, methodName, returnType, typeParameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class PainlessFieldCacheKey {
|
private static class PainlessFieldCacheKey {
|
||||||
|
|
||||||
private final Class<?> targetType;
|
private final Class<?> targetClass;
|
||||||
private final String fieldName;
|
private final String fieldName;
|
||||||
private final Class<?> typeParameter;
|
private final Class<?> typeParameter;
|
||||||
|
|
||||||
private PainlessFieldCacheKey(Class<?> targetType, String fieldName, Class<?> typeParameter) {
|
private PainlessFieldCacheKey(Class<?> targetClass, String fieldName, Class<?> typeParameter) {
|
||||||
this.targetType = targetType;
|
this.targetClass = targetClass;
|
||||||
this.fieldName = fieldName;
|
this.fieldName = fieldName;
|
||||||
this.typeParameter = typeParameter;
|
this.typeParameter = typeParameter;
|
||||||
}
|
}
|
||||||
|
@ -141,20 +145,61 @@ public final class PainlessLookupBuilder {
|
||||||
|
|
||||||
PainlessFieldCacheKey that = (PainlessFieldCacheKey) object;
|
PainlessFieldCacheKey that = (PainlessFieldCacheKey) object;
|
||||||
|
|
||||||
return Objects.equals(targetType, that.targetType) &&
|
return Objects.equals(targetClass, that.targetClass) &&
|
||||||
Objects.equals(fieldName, that.fieldName) &&
|
Objects.equals(fieldName, that.fieldName) &&
|
||||||
Objects.equals(typeParameter, that.typeParameter);
|
Objects.equals(typeParameter, that.typeParameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(targetType, fieldName, typeParameter);
|
return Objects.hash(targetClass, fieldName, typeParameter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<PainlessConstructorCacheKey, PainlessConstructor> painlessConstuctorCache = new HashMap<>();
|
private static class PainlessBindingCacheKey {
|
||||||
|
|
||||||
|
private final Class<?> targetClass;
|
||||||
|
private final String methodName;
|
||||||
|
private final Class<?> methodReturnType;
|
||||||
|
private final List<Class<?>> methodTypeParameters;
|
||||||
|
|
||||||
|
private PainlessBindingCacheKey(Class<?> targetClass,
|
||||||
|
String methodName, Class<?> returnType, List<Class<?>> typeParameters) {
|
||||||
|
|
||||||
|
this.targetClass = targetClass;
|
||||||
|
this.methodName = methodName;
|
||||||
|
this.methodReturnType = returnType;
|
||||||
|
this.methodTypeParameters = Collections.unmodifiableList(typeParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object object) {
|
||||||
|
if (this == object) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object == null || getClass() != object.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PainlessBindingCacheKey that = (PainlessBindingCacheKey)object;
|
||||||
|
|
||||||
|
return Objects.equals(targetClass, that.targetClass) &&
|
||||||
|
Objects.equals(methodName, that.methodName) &&
|
||||||
|
Objects.equals(methodReturnType, that.methodReturnType) &&
|
||||||
|
Objects.equals(methodTypeParameters, that.methodTypeParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(targetClass, methodName, methodReturnType, methodTypeParameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<PainlessConstructorCacheKey, PainlessConstructor> painlessConstructorCache = new HashMap<>();
|
||||||
private static final Map<PainlessMethodCacheKey, PainlessMethod> painlessMethodCache = new HashMap<>();
|
private static final Map<PainlessMethodCacheKey, PainlessMethod> painlessMethodCache = new HashMap<>();
|
||||||
private static final Map<PainlessFieldCacheKey, PainlessField> painlessFieldCache = new HashMap<>();
|
private static final Map<PainlessFieldCacheKey, PainlessField> painlessFieldCache = new HashMap<>();
|
||||||
|
private static final Map<PainlessBindingCacheKey, PainlessBinding> painlessBindingCache = new HashMap<>();
|
||||||
|
|
||||||
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][._a-zA-Z0-9]*$");
|
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][._a-zA-Z0-9]*$");
|
||||||
private static final Pattern METHOD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
|
private static final Pattern METHOD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
|
||||||
|
@ -197,6 +242,14 @@ public final class PainlessLookupBuilder {
|
||||||
targetCanonicalClassName, whitelistField.fieldName, whitelistField.canonicalTypeNameParameter);
|
targetCanonicalClassName, whitelistField.fieldName, whitelistField.canonicalTypeNameParameter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (WhitelistBinding whitelistBinding : whitelist.whitelistBindings) {
|
||||||
|
origin = whitelistBinding.origin;
|
||||||
|
painlessLookupBuilder.addPainlessBinding(
|
||||||
|
whitelist.classLoader, whitelistBinding.targetJavaClassName,
|
||||||
|
whitelistBinding.methodName, whitelistBinding.returnCanonicalTypeName,
|
||||||
|
whitelistBinding.canonicalTypeNameParameters);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
throw new IllegalArgumentException("error loading whitelist(s) " + origin, exception);
|
throw new IllegalArgumentException("error loading whitelist(s) " + origin, exception);
|
||||||
|
@ -208,9 +261,13 @@ public final class PainlessLookupBuilder {
|
||||||
private final Map<String, Class<?>> canonicalClassNamesToClasses;
|
private final Map<String, Class<?>> canonicalClassNamesToClasses;
|
||||||
private final Map<Class<?>, PainlessClassBuilder> classesToPainlessClassBuilders;
|
private final Map<Class<?>, PainlessClassBuilder> classesToPainlessClassBuilders;
|
||||||
|
|
||||||
|
private final Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings;
|
||||||
|
|
||||||
public PainlessLookupBuilder() {
|
public PainlessLookupBuilder() {
|
||||||
canonicalClassNamesToClasses = new HashMap<>();
|
canonicalClassNamesToClasses = new HashMap<>();
|
||||||
classesToPainlessClassBuilders = new HashMap<>();
|
classesToPainlessClassBuilders = new HashMap<>();
|
||||||
|
|
||||||
|
painlessMethodKeysToPainlessBindings = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Class<?> canonicalTypeNameToType(String canonicalTypeName) {
|
private Class<?> canonicalTypeNameToType(String canonicalTypeName) {
|
||||||
|
@ -392,7 +449,7 @@ public final class PainlessLookupBuilder {
|
||||||
|
|
||||||
MethodType methodType = methodHandle.type();
|
MethodType methodType = methodHandle.type();
|
||||||
|
|
||||||
painlessConstructor = painlessConstuctorCache.computeIfAbsent(
|
painlessConstructor = painlessConstructorCache.computeIfAbsent(
|
||||||
new PainlessConstructorCacheKey(targetClass, typeParameters),
|
new PainlessConstructorCacheKey(targetClass, typeParameters),
|
||||||
key -> new PainlessConstructor(javaConstructor, typeParameters, methodHandle, methodType)
|
key -> new PainlessConstructor(javaConstructor, typeParameters, methodHandle, methodType)
|
||||||
);
|
);
|
||||||
|
@ -439,7 +496,7 @@ public final class PainlessLookupBuilder {
|
||||||
Class<?> typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter);
|
Class<?> typeParameter = canonicalTypeNameToType(canonicalTypeNameParameter);
|
||||||
|
|
||||||
if (typeParameter == null) {
|
if (typeParameter == null) {
|
||||||
throw new IllegalArgumentException("parameter type [" + canonicalTypeNameParameter + "] not found for method " +
|
throw new IllegalArgumentException("type parameter [" + canonicalTypeNameParameter + "] not found for method " +
|
||||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,7 +506,7 @@ public final class PainlessLookupBuilder {
|
||||||
Class<?> returnType = canonicalTypeNameToType(returnCanonicalTypeName);
|
Class<?> returnType = canonicalTypeNameToType(returnCanonicalTypeName);
|
||||||
|
|
||||||
if (returnType == null) {
|
if (returnType == null) {
|
||||||
throw new IllegalArgumentException("parameter type [" + returnCanonicalTypeName + "] not found for method " +
|
throw new IllegalArgumentException("return type [" + returnCanonicalTypeName + "] not found for method " +
|
||||||
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -548,7 +605,7 @@ public final class PainlessLookupBuilder {
|
||||||
MethodType methodType = methodHandle.type();
|
MethodType methodType = methodHandle.type();
|
||||||
|
|
||||||
painlessMethod = painlessMethodCache.computeIfAbsent(
|
painlessMethod = painlessMethodCache.computeIfAbsent(
|
||||||
new PainlessMethodCacheKey(targetClass, methodName, typeParameters),
|
new PainlessMethodCacheKey(targetClass, methodName, returnType, typeParameters),
|
||||||
key -> new PainlessMethod(javaMethod, targetClass, returnType, typeParameters, methodHandle, methodType));
|
key -> new PainlessMethod(javaMethod, targetClass, returnType, typeParameters, methodHandle, methodType));
|
||||||
|
|
||||||
painlessClassBuilder.staticMethods.put(painlessMethodKey, painlessMethod);
|
painlessClassBuilder.staticMethods.put(painlessMethodKey, painlessMethod);
|
||||||
|
@ -588,7 +645,7 @@ public final class PainlessLookupBuilder {
|
||||||
MethodType methodType = methodHandle.type();
|
MethodType methodType = methodHandle.type();
|
||||||
|
|
||||||
painlessMethod = painlessMethodCache.computeIfAbsent(
|
painlessMethod = painlessMethodCache.computeIfAbsent(
|
||||||
new PainlessMethodCacheKey(targetClass, methodName, typeParameters),
|
new PainlessMethodCacheKey(targetClass, methodName, returnType, typeParameters),
|
||||||
key -> new PainlessMethod(javaMethod, targetClass, returnType, typeParameters, methodHandle, methodType));
|
key -> new PainlessMethod(javaMethod, targetClass, returnType, typeParameters, methodHandle, methodType));
|
||||||
|
|
||||||
painlessClassBuilder.methods.put(painlessMethodKey, painlessMethod);
|
painlessClassBuilder.methods.put(painlessMethodKey, painlessMethod);
|
||||||
|
@ -731,6 +788,183 @@ public final class PainlessLookupBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addPainlessBinding(ClassLoader classLoader, String targetJavaClassName,
|
||||||
|
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(classLoader);
|
||||||
|
Objects.requireNonNull(targetJavaClassName);
|
||||||
|
Objects.requireNonNull(methodName);
|
||||||
|
Objects.requireNonNull(returnCanonicalTypeName);
|
||||||
|
Objects.requireNonNull(canonicalTypeNameParameters);
|
||||||
|
|
||||||
|
Class<?> targetClass;
|
||||||
|
|
||||||
|
try {
|
||||||
|
targetClass = Class.forName(targetJavaClassName, true, classLoader);
|
||||||
|
} catch (ClassNotFoundException cnfe) {
|
||||||
|
throw new IllegalArgumentException("class [" + targetJavaClassName + "] not found", cnfe);
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
|
||||||
|
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 binding " +
|
||||||
|
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
typeParameters.add(typeParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> returnType = canonicalTypeNameToType(returnCanonicalTypeName);
|
||||||
|
|
||||||
|
if (returnType == null) {
|
||||||
|
throw new IllegalArgumentException("return type [" + returnCanonicalTypeName + "] not found for binding " +
|
||||||
|
"[[" + targetCanonicalClassName + "], [" + methodName + "], " + canonicalTypeNameParameters + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
addPainlessBinding(targetClass, methodName, returnType, typeParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPainlessBinding(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 binding as reserved class [" + DEF_CLASS_NAME + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
|
||||||
|
|
||||||
|
Constructor<?>[] javaConstructors = targetClass.getConstructors();
|
||||||
|
Constructor<?> javaConstructor = null;
|
||||||
|
|
||||||
|
for (Constructor<?> eachJavaConstructor : javaConstructors) {
|
||||||
|
if (eachJavaConstructor.getDeclaringClass() == targetClass) {
|
||||||
|
if (javaConstructor != null) {
|
||||||
|
throw new IllegalArgumentException("binding [" + targetCanonicalClassName + "] cannot have multiple constructors");
|
||||||
|
}
|
||||||
|
|
||||||
|
javaConstructor = eachJavaConstructor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (javaConstructor == null) {
|
||||||
|
throw new IllegalArgumentException("binding [" + targetCanonicalClassName + "] must have exactly one constructor");
|
||||||
|
}
|
||||||
|
|
||||||
|
int constructorTypeParametersSize = javaConstructor.getParameterCount();
|
||||||
|
|
||||||
|
for (int typeParameterIndex = 0; typeParameterIndex < constructorTypeParametersSize; ++typeParameterIndex) {
|
||||||
|
Class<?> typeParameter = typeParameters.get(typeParameterIndex);
|
||||||
|
|
||||||
|
if (isValidType(typeParameter) == false) {
|
||||||
|
throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " +
|
||||||
|
"for binding [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> javaTypeParameter = javaConstructor.getParameterTypes()[typeParameterIndex];
|
||||||
|
|
||||||
|
if (isValidType(javaTypeParameter) == false) {
|
||||||
|
throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " +
|
||||||
|
"for binding [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (javaTypeParameter != typeToJavaType(typeParameter)) {
|
||||||
|
throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(javaTypeParameter) + "] " +
|
||||||
|
"does not match the specified type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " +
|
||||||
|
"for binding [[" + targetClass.getCanonicalName() + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"invalid method name [" + methodName + "] for binding [" + targetCanonicalClassName + "].");
|
||||||
|
}
|
||||||
|
|
||||||
|
Method[] javaMethods = targetClass.getMethods();
|
||||||
|
Method javaMethod = null;
|
||||||
|
|
||||||
|
for (Method eachJavaMethod : javaMethods) {
|
||||||
|
if (eachJavaMethod.getDeclaringClass() == targetClass) {
|
||||||
|
if (javaMethod != null) {
|
||||||
|
throw new IllegalArgumentException("binding [" + targetCanonicalClassName + "] cannot have multiple methods");
|
||||||
|
}
|
||||||
|
|
||||||
|
javaMethod = eachJavaMethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (javaMethod == null) {
|
||||||
|
throw new IllegalArgumentException("binding [" + targetCanonicalClassName + "] must have exactly one method");
|
||||||
|
}
|
||||||
|
|
||||||
|
int methodTypeParametersSize = javaMethod.getParameterCount();
|
||||||
|
|
||||||
|
for (int typeParameterIndex = 0; typeParameterIndex < methodTypeParametersSize; ++typeParameterIndex) {
|
||||||
|
Class<?> typeParameter = typeParameters.get(typeParameterIndex);
|
||||||
|
|
||||||
|
if (isValidType(typeParameter) == false) {
|
||||||
|
throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " +
|
||||||
|
"for binding [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> javaTypeParameter = javaMethod.getParameterTypes()[typeParameterIndex];
|
||||||
|
|
||||||
|
if (isValidType(javaTypeParameter) == false) {
|
||||||
|
throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(typeParameter) + "] not found " +
|
||||||
|
"for binding [[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (javaTypeParameter != typeToJavaType(typeParameter)) {
|
||||||
|
throw new IllegalArgumentException("type parameter [" + typeToCanonicalTypeName(javaTypeParameter) + "] " +
|
||||||
|
"does not match the specified type parameter [" + typeToCanonicalTypeName(typeParameter) + "] " +
|
||||||
|
"for binding [[" + targetClass.getCanonicalName() + "], " + typesToCanonicalTypeNames(typeParameters) + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (javaMethod.getReturnType() != typeToJavaType(returnType)) {
|
||||||
|
throw new IllegalArgumentException("return type [" + typeToCanonicalTypeName(javaMethod.getReturnType()) + "] " +
|
||||||
|
"does not match the specified returned type [" + typeToCanonicalTypeName(returnType) + "] " +
|
||||||
|
"for binding [[" + targetClass.getCanonicalName() + "], [" + methodName + "], " +
|
||||||
|
typesToCanonicalTypeNames(typeParameters) + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
String painlessMethodKey = buildPainlessMethodKey(methodName, constructorTypeParametersSize + methodTypeParametersSize);
|
||||||
|
PainlessBinding painlessBinding = painlessMethodKeysToPainlessBindings.get(painlessMethodKey);
|
||||||
|
|
||||||
|
if (painlessBinding == null) {
|
||||||
|
Constructor<?> finalJavaConstructor = javaConstructor;
|
||||||
|
Method finalJavaMethod = javaMethod;
|
||||||
|
|
||||||
|
painlessBinding = painlessBindingCache.computeIfAbsent(
|
||||||
|
new PainlessBindingCacheKey(targetClass, methodName, returnType, typeParameters),
|
||||||
|
key -> new PainlessBinding(finalJavaConstructor, finalJavaMethod, returnType, typeParameters));
|
||||||
|
|
||||||
|
painlessMethodKeysToPainlessBindings.put(painlessMethodKey, painlessBinding);
|
||||||
|
} else if (painlessBinding.javaConstructor.equals(javaConstructor) == false ||
|
||||||
|
painlessBinding.javaMethod.equals(javaMethod) == false ||
|
||||||
|
painlessBinding.returnType != returnType ||
|
||||||
|
painlessBinding.typeParameters.equals(typeParameters) == false) {
|
||||||
|
throw new IllegalArgumentException("cannot have bindings " +
|
||||||
|
"[[" + targetCanonicalClassName + "], " +
|
||||||
|
"[" + methodName + "], " +
|
||||||
|
"[" + typeToCanonicalTypeName(returnType) + "], " +
|
||||||
|
typesToCanonicalTypeNames(typeParameters) + "] and " +
|
||||||
|
"[[" + targetCanonicalClassName + "], " +
|
||||||
|
"[" + methodName + "], " +
|
||||||
|
"[" + typeToCanonicalTypeName(painlessBinding.returnType) + "], " +
|
||||||
|
typesToCanonicalTypeNames(painlessBinding.typeParameters) + "] and " +
|
||||||
|
"with the same name and arity but different constructors or methods");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public PainlessLookup build() {
|
public PainlessLookup build() {
|
||||||
copyPainlessClassMembers();
|
copyPainlessClassMembers();
|
||||||
cacheRuntimeHandles();
|
cacheRuntimeHandles();
|
||||||
|
@ -742,7 +976,7 @@ public final class PainlessLookupBuilder {
|
||||||
classesToPainlessClasses.put(painlessClassBuilderEntry.getKey(), painlessClassBuilderEntry.getValue().build());
|
classesToPainlessClasses.put(painlessClassBuilderEntry.getKey(), painlessClassBuilderEntry.getValue().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PainlessLookup(canonicalClassNamesToClasses, classesToPainlessClasses);
|
return new PainlessLookup(canonicalClassNamesToClasses, classesToPainlessClasses, painlessMethodKeysToPainlessBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyPainlessClassMembers() {
|
private void copyPainlessClassMembers() {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PainlessMethod {
|
public class PainlessMethod {
|
||||||
|
|
||||||
public final Method javaMethod;
|
public final Method javaMethod;
|
||||||
public final Class<?> targetClass;
|
public final Class<?> targetClass;
|
||||||
public final Class<?> returnType;
|
public final Class<?> returnType;
|
||||||
|
|
|
@ -24,8 +24,12 @@ import org.elasticsearch.painless.Locals;
|
||||||
import org.elasticsearch.painless.Locals.LocalMethod;
|
import org.elasticsearch.painless.Locals.LocalMethod;
|
||||||
import org.elasticsearch.painless.Location;
|
import org.elasticsearch.painless.Location;
|
||||||
import org.elasticsearch.painless.MethodWriter;
|
import org.elasticsearch.painless.MethodWriter;
|
||||||
|
import org.elasticsearch.painless.lookup.PainlessBinding;
|
||||||
|
import org.objectweb.asm.Label;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.commons.Method;
|
import org.objectweb.asm.commons.Method;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -41,6 +45,7 @@ public final class ECallLocal extends AExpression {
|
||||||
private final List<AExpression> arguments;
|
private final List<AExpression> arguments;
|
||||||
|
|
||||||
private LocalMethod method = null;
|
private LocalMethod method = null;
|
||||||
|
private PainlessBinding binding = null;
|
||||||
|
|
||||||
public ECallLocal(Location location, String name, List<AExpression> arguments) {
|
public ECallLocal(Location location, String name, List<AExpression> arguments) {
|
||||||
super(location);
|
super(location);
|
||||||
|
@ -60,33 +65,72 @@ public final class ECallLocal extends AExpression {
|
||||||
void analyze(Locals locals) {
|
void analyze(Locals locals) {
|
||||||
method = locals.getMethod(name, arguments.size());
|
method = locals.getMethod(name, arguments.size());
|
||||||
|
|
||||||
|
|
||||||
if (method == null) {
|
if (method == 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);
|
||||||
|
|
||||||
for (int argument = 0; argument < arguments.size(); ++argument) {
|
for (int argument = 0; argument < arguments.size(); ++argument) {
|
||||||
AExpression expression = arguments.get(argument);
|
AExpression expression = arguments.get(argument);
|
||||||
|
|
||||||
expression.expected = method.typeParameters.get(argument);
|
expression.expected = typeParameters.get(argument);
|
||||||
expression.internal = true;
|
expression.internal = true;
|
||||||
expression.analyze(locals);
|
expression.analyze(locals);
|
||||||
arguments.set(argument, expression.cast(locals));
|
arguments.set(argument, expression.cast(locals));
|
||||||
}
|
}
|
||||||
|
|
||||||
statement = true;
|
statement = true;
|
||||||
actual = method.returnType;
|
actual = method == null ? binding.returnType : method.returnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void write(MethodWriter writer, Globals globals) {
|
void write(MethodWriter writer, Globals globals) {
|
||||||
writer.writeDebugInfo(location);
|
writer.writeDebugInfo(location);
|
||||||
|
|
||||||
|
if (method == null) {
|
||||||
|
String name = globals.addBinding(binding.javaConstructor.getDeclaringClass());
|
||||||
|
Type type = Type.getType(binding.javaConstructor.getDeclaringClass());
|
||||||
|
int javaConstructorParameterCount = binding.javaConstructor.getParameterCount();
|
||||||
|
|
||||||
|
Label nonNull = new Label();
|
||||||
|
|
||||||
|
writer.loadThis();
|
||||||
|
writer.getField(CLASS_TYPE, name, type);
|
||||||
|
writer.ifNonNull(nonNull);
|
||||||
|
writer.loadThis();
|
||||||
|
writer.newInstance(type);
|
||||||
|
writer.dup();
|
||||||
|
|
||||||
|
for (int argument = 0; argument < javaConstructorParameterCount; ++argument) {
|
||||||
|
arguments.get(argument).write(writer, globals);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.invokeConstructor(type, Method.getMethod(binding.javaConstructor));
|
||||||
|
writer.putField(CLASS_TYPE, name, type);
|
||||||
|
|
||||||
|
writer.mark(nonNull);
|
||||||
|
writer.loadThis();
|
||||||
|
writer.getField(CLASS_TYPE, name, type);
|
||||||
|
|
||||||
|
for (int argument = 0; argument < binding.javaMethod.getParameterCount(); ++argument) {
|
||||||
|
arguments.get(argument + javaConstructorParameterCount).write(writer, globals);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.invokeVirtual(type, Method.getMethod(binding.javaMethod));
|
||||||
|
} else {
|
||||||
for (AExpression argument : arguments) {
|
for (AExpression argument : arguments) {
|
||||||
argument.write(writer, globals);
|
argument.write(writer, globals);
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.invokeStatic(CLASS_TYPE, new Method(method.name, method.methodType.toMethodDescriptorString()));
|
writer.invokeStatic(CLASS_TYPE, new Method(method.name, method.methodType.toMethodDescriptorString()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
|
|
@ -359,6 +359,13 @@ public final class SSource extends AStatement {
|
||||||
clinit.endMethod();
|
clinit.endMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write binding variables
|
||||||
|
for (Map.Entry<String, Class<?>> binding : globals.getBindings().entrySet()) {
|
||||||
|
String name = binding.getKey();
|
||||||
|
String descriptor = Type.getType(binding.getValue()).getDescriptor();
|
||||||
|
visitor.visitField(Opcodes.ACC_PRIVATE, name, descriptor, null, null).visitEnd();
|
||||||
|
}
|
||||||
|
|
||||||
// Write any needsVarName methods for used variables
|
// Write any needsVarName methods for used variables
|
||||||
for (org.objectweb.asm.commons.Method needsMethod : scriptClassInfo.getNeedsMethods()) {
|
for (org.objectweb.asm.commons.Method needsMethod : scriptClassInfo.getNeedsMethods()) {
|
||||||
String name = needsMethod.getName();
|
String name = needsMethod.getName();
|
||||||
|
|
|
@ -132,24 +132,6 @@ class org.elasticsearch.index.mapper.IpFieldMapper$IpFieldType$IpScriptDocValues
|
||||||
List getValues()
|
List getValues()
|
||||||
}
|
}
|
||||||
|
|
||||||
# for testing.
|
|
||||||
# currently FeatureTest exposes overloaded constructor, field load store, and overloaded static methods
|
|
||||||
class org.elasticsearch.painless.FeatureTest no_import {
|
|
||||||
int z
|
|
||||||
()
|
|
||||||
(int,int)
|
|
||||||
int getX()
|
|
||||||
int getY()
|
|
||||||
void setX(int)
|
|
||||||
void setY(int)
|
|
||||||
boolean overloadedStatic()
|
|
||||||
boolean overloadedStatic(boolean)
|
|
||||||
Object twoFunctionsOfX(Function,Function)
|
|
||||||
void listInput(List)
|
|
||||||
int org.elasticsearch.painless.FeatureTestAugmentation getTotal()
|
|
||||||
int org.elasticsearch.painless.FeatureTestAugmentation addToTotal(int)
|
|
||||||
}
|
|
||||||
|
|
||||||
class org.elasticsearch.search.lookup.FieldLookup {
|
class org.elasticsearch.search.lookup.FieldLookup {
|
||||||
def getValue()
|
def getValue()
|
||||||
List getValues()
|
List getValues()
|
||||||
|
@ -175,3 +157,25 @@ class org.elasticsearch.index.similarity.ScriptedSimilarity$Doc {
|
||||||
int getLength()
|
int getLength()
|
||||||
float getFreq()
|
float getFreq()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# for testing
|
||||||
|
class org.elasticsearch.painless.FeatureTest no_import {
|
||||||
|
int z
|
||||||
|
()
|
||||||
|
(int,int)
|
||||||
|
int getX()
|
||||||
|
int getY()
|
||||||
|
void setX(int)
|
||||||
|
void setY(int)
|
||||||
|
boolean overloadedStatic()
|
||||||
|
boolean overloadedStatic(boolean)
|
||||||
|
Object twoFunctionsOfX(Function,Function)
|
||||||
|
void listInput(List)
|
||||||
|
int org.elasticsearch.painless.FeatureTestAugmentation getTotal()
|
||||||
|
int org.elasticsearch.painless.FeatureTestAugmentation addToTotal(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
# for testing
|
||||||
|
static {
|
||||||
|
int testAddWithState(int, int, int) bound_to org.elasticsearch.painless.BindingTest
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* 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.script.ExecutableScript;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BindingsTests extends ScriptTestCase {
|
||||||
|
|
||||||
|
public void testBasicBinding() {
|
||||||
|
assertEquals(15, exec("testAddWithState(4, 5, 6)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRepeatedBinding() {
|
||||||
|
String script = "testAddWithState(4, 5, params.test)";
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
ExecutableScript.Factory factory = scriptEngine.compile(null, script, ExecutableScript.CONTEXT, Collections.emptyMap());
|
||||||
|
ExecutableScript executableScript = factory.newInstance(params);
|
||||||
|
|
||||||
|
executableScript.setNextVar("test", 5);
|
||||||
|
assertEquals(14, executableScript.run());
|
||||||
|
|
||||||
|
executableScript.setNextVar("test", 4);
|
||||||
|
assertEquals(13, executableScript.run());
|
||||||
|
|
||||||
|
executableScript.setNextVar("test", 7);
|
||||||
|
assertEquals(16, executableScript.run());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBoundBinding() {
|
||||||
|
String script = "testAddWithState(4, params.bound, params.test)";
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
ExecutableScript.Factory factory = scriptEngine.compile(null, script, ExecutableScript.CONTEXT, Collections.emptyMap());
|
||||||
|
ExecutableScript executableScript = factory.newInstance(params);
|
||||||
|
|
||||||
|
executableScript.setNextVar("test", 5);
|
||||||
|
executableScript.setNextVar("bound", 1);
|
||||||
|
assertEquals(10, executableScript.run());
|
||||||
|
|
||||||
|
executableScript.setNextVar("test", 4);
|
||||||
|
executableScript.setNextVar("bound", 2);
|
||||||
|
assertEquals(9, executableScript.run());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue