mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-09 06:25:07 +00:00
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. */
|
/** The {@link List} of all the whitelisted Painless classes. */
|
||||||
public final List<WhitelistClass> whitelistClasses;
|
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;
|
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, List<WhitelistBinding> whitelistBindings) {
|
public Whitelist(ClassLoader classLoader, List<WhitelistClass> whitelistClasses,
|
||||||
|
List<WhitelistMethod> whitelistImportedMethods, 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.whitelistImportedMethods = Collections.unmodifiableList(Objects.requireNonNull(whitelistImportedMethods));
|
||||||
this.whitelistBindings = Collections.unmodifiableList(Objects.requireNonNull(whitelistBindings));
|
this.whitelistBindings = Collections.unmodifiableList(Objects.requireNonNull(whitelistBindings));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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<WhitelistMethod> whitelistStatics = new ArrayList<>();
|
||||||
List<WhitelistBinding> whitelistBindings = 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
|
||||||
@ -192,18 +193,18 @@ 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 ")) {
|
} else if (line.startsWith("static_import ")) {
|
||||||
// Ensure the final token of the line is '{'.
|
// Ensure the final token of the line is '{'.
|
||||||
if (line.endsWith("{") == false) {
|
if (line.endsWith("{") == false) {
|
||||||
throw new IllegalArgumentException(
|
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) {
|
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.
|
// Handle the end of a definition and reset all previously gathered values.
|
||||||
// Expects the following format: '}' '\n'
|
// Expects the following format: '}' '\n'
|
||||||
@ -229,9 +230,9 @@ public final class WhitelistLoader {
|
|||||||
// Reset the parseType.
|
// Reset the parseType.
|
||||||
parseType = null;
|
parseType = null;
|
||||||
|
|
||||||
// Handle static definition types.
|
// Handle static import definition types.
|
||||||
// Expects the following format: ID ID '(' ( ID ( ',' ID )* )? ')' 'bound_to' ID '\n'
|
// Expects the following format: ID ID '(' ( ID ( ',' ID )* )? ')' ( 'from_class' | 'bound_to' ) ID '\n'
|
||||||
} else if ("static".equals(parseType)) {
|
} else if ("static_import".equals(parseType)) {
|
||||||
// Mark the origin of this parsable object.
|
// Mark the origin of this parsable object.
|
||||||
String origin = "[" + filepath + "]:[" + number + "]";
|
String origin = "[" + filepath + "]:[" + number + "]";
|
||||||
|
|
||||||
@ -240,7 +241,7 @@ public final class WhitelistLoader {
|
|||||||
|
|
||||||
if (parameterStartIndex == -1) {
|
if (parameterStartIndex == -1) {
|
||||||
throw new IllegalArgumentException(
|
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+");
|
String[] tokens = line.substring(0, parameterStartIndex).trim().split("\\s+");
|
||||||
@ -261,7 +262,7 @@ public final class WhitelistLoader {
|
|||||||
|
|
||||||
if (parameterEndIndex == -1) {
|
if (parameterEndIndex == -1) {
|
||||||
throw new IllegalArgumentException(
|
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 =
|
String[] canonicalTypeNameParameters =
|
||||||
@ -272,39 +273,37 @@ public final class WhitelistLoader {
|
|||||||
canonicalTypeNameParameters = new String[0];
|
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+");
|
tokens = line.substring(parameterEndIndex + 1).trim().split("\\s+");
|
||||||
|
|
||||||
String staticType;
|
String staticImportType;
|
||||||
String targetJavaClassName;
|
String targetJavaClassName;
|
||||||
|
|
||||||
// Based on the number of tokens, look up the type and class.
|
// Based on the number of tokens, look up the type and class.
|
||||||
if (tokens.length == 2) {
|
if (tokens.length == 2) {
|
||||||
staticType = tokens[0];
|
staticImportType = tokens[0];
|
||||||
targetJavaClassName = tokens[1];
|
targetJavaClassName = tokens[1];
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("invalid static definition: unexpected format [" + line + "]");
|
throw new IllegalArgumentException("invalid static import 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 + "]");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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,
|
whitelistBindings.add(new WhitelistBinding(origin, targetJavaClassName,
|
||||||
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
|
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("invalid static import definition: " +
|
||||||
|
"unexpected static import type [" + staticImportType + "] [" + line + "]");
|
||||||
|
}
|
||||||
|
|
||||||
// Handle class definition types.
|
// Handle class definition types.
|
||||||
} else if ("class".equals(parseType)) {
|
} 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.
|
|
||||||
if (parseType == null) {
|
|
||||||
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.
|
||||||
// Expects the following format: '(' ( ID ( ',' ID )* )? ')' '\n'
|
// Expects the following format: '(' ( ID ( ',' ID )* )? ')' '\n'
|
||||||
if (line.startsWith("(")) {
|
if (line.startsWith("(")) {
|
||||||
@ -393,7 +392,7 @@ public final class WhitelistLoader {
|
|||||||
|
|
||||||
ClassLoader loader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>)resource::getClassLoader);
|
ClassLoader loader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>)resource::getClassLoader);
|
||||||
|
|
||||||
return new Whitelist(loader, whitelistClasses, whitelistBindings);
|
return new Whitelist(loader, whitelistClasses, whitelistStatics, whitelistBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private WhitelistLoader() {}
|
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! */
|
/** Currently just a dummy class for testing a few features not yet exposed by whitelist! */
|
||||||
public class FeatureTest {
|
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 x;
|
||||||
private int y;
|
private int y;
|
||||||
public int z;
|
public int z;
|
||||||
@ -58,21 +73,12 @@ public class FeatureTest {
|
|||||||
this.y = y;
|
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! */
|
/** method taking two functions! */
|
||||||
public Object twoFunctionsOfX(Function<Object,Object> f, Function<Object,Object> g) {
|
public Object twoFunctionsOfX(Function<Object,Object> f, Function<Object,Object> g) {
|
||||||
return f.apply(g.apply(x));
|
return f.apply(g.apply(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** method to take in a list */
|
||||||
public void listInput(List<Object> list) {
|
public void listInput(List<Object> list) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -37,16 +37,23 @@ 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;
|
||||||
|
|
||||||
|
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
|
||||||
private final Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings;
|
private final Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings;
|
||||||
|
|
||||||
PainlessLookup(Map<String, Class<?>> canonicalClassNamesToClasses, Map<Class<?>, PainlessClass> classesToPainlessClasses,
|
PainlessLookup(Map<String, Class<?>> canonicalClassNamesToClasses, Map<Class<?>, PainlessClass> classesToPainlessClasses,
|
||||||
|
Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods,
|
||||||
Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings) {
|
Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings) {
|
||||||
|
|
||||||
Objects.requireNonNull(canonicalClassNamesToClasses);
|
Objects.requireNonNull(canonicalClassNamesToClasses);
|
||||||
Objects.requireNonNull(classesToPainlessClasses);
|
Objects.requireNonNull(classesToPainlessClasses);
|
||||||
|
|
||||||
|
Objects.requireNonNull(painlessMethodKeysToImportedPainlessMethods);
|
||||||
|
Objects.requireNonNull(painlessMethodKeysToPainlessBindings);
|
||||||
|
|
||||||
this.canonicalClassNamesToClasses = Collections.unmodifiableMap(canonicalClassNamesToClasses);
|
this.canonicalClassNamesToClasses = Collections.unmodifiableMap(canonicalClassNamesToClasses);
|
||||||
this.classesToPainlessClasses = Collections.unmodifiableMap(classesToPainlessClasses);
|
this.classesToPainlessClasses = Collections.unmodifiableMap(classesToPainlessClasses);
|
||||||
|
|
||||||
|
this.painlessMethodKeysToImportedPainlessMethods = Collections.unmodifiableMap(painlessMethodKeysToImportedPainlessMethods);
|
||||||
this.painlessMethodKeysToPainlessBindings = Collections.unmodifiableMap(painlessMethodKeysToPainlessBindings);
|
this.painlessMethodKeysToPainlessBindings = Collections.unmodifiableMap(painlessMethodKeysToPainlessBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,6 +174,14 @@ public final class PainlessLookup {
|
|||||||
return painlessField;
|
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) {
|
public PainlessBinding lookupPainlessBinding(String methodName, int arity) {
|
||||||
Objects.requireNonNull(methodName);
|
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) {
|
for (WhitelistBinding whitelistBinding : whitelist.whitelistBindings) {
|
||||||
origin = whitelistBinding.origin;
|
origin = whitelistBinding.origin;
|
||||||
painlessLookupBuilder.addPainlessBinding(
|
painlessLookupBuilder.addPainlessBinding(
|
||||||
@ -261,12 +269,14 @@ 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, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
|
||||||
private final Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings;
|
private final Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings;
|
||||||
|
|
||||||
public PainlessLookupBuilder() {
|
public PainlessLookupBuilder() {
|
||||||
canonicalClassNamesToClasses = new HashMap<>();
|
canonicalClassNamesToClasses = new HashMap<>();
|
||||||
classesToPainlessClassBuilders = new HashMap<>();
|
classesToPainlessClassBuilders = new HashMap<>();
|
||||||
|
|
||||||
|
painlessMethodKeysToImportedPainlessMethods = new HashMap<>();
|
||||||
painlessMethodKeysToPainlessBindings = new HashMap<>();
|
painlessMethodKeysToPainlessBindings = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -513,8 +523,9 @@ public final class PainlessLookupBuilder {
|
|||||||
addPainlessMethod(targetClass, augmentedClass, methodName, returnType, typeParameters);
|
addPainlessMethod(targetClass, augmentedClass, methodName, returnType, typeParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPainlessMethod(Class<?> targetClass, Class<?> augmentedClass, String methodName,
|
public void addPainlessMethod(Class<?> targetClass, Class<?> augmentedClass,
|
||||||
Class<?> returnType, List<Class<?>> typeParameters) {
|
String methodName, Class<?> returnType, List<Class<?>> typeParameters) {
|
||||||
|
|
||||||
Objects.requireNonNull(targetClass);
|
Objects.requireNonNull(targetClass);
|
||||||
Objects.requireNonNull(methodName);
|
Objects.requireNonNull(methodName);
|
||||||
Objects.requireNonNull(returnType);
|
Objects.requireNonNull(returnType);
|
||||||
@ -573,6 +584,12 @@ public final class PainlessLookupBuilder {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
javaMethod = augmentedClass.getMethod(methodName, javaTypeParameters.toArray(new Class<?>[typeParametersSize]));
|
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) {
|
} catch (NoSuchMethodException nsme) {
|
||||||
throw new IllegalArgumentException("method reflection object [[" + targetCanonicalClassName + "], " +
|
throw new IllegalArgumentException("method reflection object [[" + targetCanonicalClassName + "], " +
|
||||||
"[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found " +
|
"[" + methodName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found " +
|
||||||
@ -620,7 +637,7 @@ public final class PainlessLookupBuilder {
|
|||||||
"with the same arity and different return type or type parameters");
|
"with the same arity and different return type or type parameters");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PainlessMethod painlessMethod = painlessClassBuilder.staticMethods.get(painlessMethodKey);
|
PainlessMethod painlessMethod = painlessClassBuilder.methods.get(painlessMethodKey);
|
||||||
|
|
||||||
if (painlessMethod == null) {
|
if (painlessMethod == null) {
|
||||||
MethodHandle methodHandle;
|
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,
|
public void addPainlessBinding(ClassLoader classLoader, String targetJavaClassName,
|
||||||
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
String methodName, String returnCanonicalTypeName, List<String> canonicalTypeNameParameters) {
|
||||||
|
|
||||||
@ -937,6 +1094,11 @@ public final class PainlessLookupBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String painlessMethodKey = buildPainlessMethodKey(methodName, constructorTypeParametersSize + methodTypeParametersSize);
|
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);
|
PainlessBinding painlessBinding = painlessMethodKeysToPainlessBindings.get(painlessMethodKey);
|
||||||
|
|
||||||
if (painlessBinding == null) {
|
if (painlessBinding == null) {
|
||||||
@ -976,7 +1138,8 @@ public final class PainlessLookupBuilder {
|
|||||||
classesToPainlessClasses.put(painlessClassBuilderEntry.getKey(), painlessClassBuilderEntry.getValue().build());
|
classesToPainlessClasses.put(painlessClassBuilderEntry.getKey(), painlessClassBuilderEntry.getValue().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PainlessLookup(canonicalClassNamesToClasses, classesToPainlessClasses, painlessMethodKeysToPainlessBindings);
|
return new PainlessLookup(canonicalClassNamesToClasses, classesToPainlessClasses,
|
||||||
|
painlessMethodKeysToImportedPainlessMethods, painlessMethodKeysToPainlessBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyPainlessClassMembers() {
|
private void copyPainlessClassMembers() {
|
||||||
|
@ -25,6 +25,7 @@ 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.elasticsearch.painless.lookup.PainlessBinding;
|
||||||
|
import org.elasticsearch.painless.lookup.PainlessMethod;
|
||||||
import org.objectweb.asm.Label;
|
import org.objectweb.asm.Label;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.commons.Method;
|
import org.objectweb.asm.commons.Method;
|
||||||
@ -45,6 +46,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 PainlessMethod imported = null;
|
||||||
private PainlessBinding binding = null;
|
private PainlessBinding binding = null;
|
||||||
|
|
||||||
public ECallLocal(Location location, String name, List<AExpression> arguments) {
|
public ECallLocal(Location location, String name, List<AExpression> arguments) {
|
||||||
@ -65,16 +67,33 @@ 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) {
|
||||||
|
imported = locals.getPainlessLookup().lookupImportedPainlessMethod(name, arguments.size());
|
||||||
|
|
||||||
|
if (imported == null) {
|
||||||
binding = locals.getPainlessLookup().lookupPainlessBinding(name, arguments.size());
|
binding = locals.getPainlessLookup().lookupPainlessBinding(name, arguments.size());
|
||||||
|
|
||||||
if (binding == null) {
|
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) {
|
for (int argument = 0; argument < arguments.size(); ++argument) {
|
||||||
AExpression expression = arguments.get(argument);
|
AExpression expression = arguments.get(argument);
|
||||||
@ -86,14 +105,26 @@ public final class ECallLocal extends AExpression {
|
|||||||
}
|
}
|
||||||
|
|
||||||
statement = true;
|
statement = true;
|
||||||
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) {
|
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());
|
String name = globals.addBinding(binding.javaConstructor.getDeclaringClass());
|
||||||
Type type = Type.getType(binding.javaConstructor.getDeclaringClass());
|
Type type = Type.getType(binding.javaConstructor.getDeclaringClass());
|
||||||
int javaConstructorParameterCount = binding.javaConstructor.getParameterCount();
|
int javaConstructorParameterCount = binding.javaConstructor.getParameterCount();
|
||||||
@ -124,11 +155,7 @@ public final class ECallLocal extends AExpression {
|
|||||||
|
|
||||||
writer.invokeVirtual(type, Method.getMethod(binding.javaMethod));
|
writer.invokeVirtual(type, Method.getMethod(binding.javaMethod));
|
||||||
} else {
|
} else {
|
||||||
for (AExpression argument : arguments) {
|
throw new IllegalStateException("Illegal tree structure.");
|
||||||
argument.write(writer, globals);
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.invokeStatic(CLASS_TYPE, new Method(method.name, method.methodType.toMethodDescriptorString()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,6 +176,7 @@ class org.elasticsearch.painless.FeatureTest no_import {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# for testing
|
# 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
|
int testAddWithState(int, int, int, double) bound_to org.elasticsearch.painless.BindingTest
|
||||||
}
|
}
|
@ -133,4 +133,8 @@ public class BasicAPITests extends ScriptTestCase {
|
|||||||
public void testNoSemicolon() {
|
public void testNoSemicolon() {
|
||||||
assertEquals(true, exec("def x = true; if (x) return x"));
|
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()}
|
* Typically just asserts the output of {@code exec()}
|
||||||
*/
|
*/
|
||||||
public abstract class ScriptTestCase extends ESTestCase {
|
public abstract class ScriptTestCase extends ESTestCase {
|
||||||
|
private static final PainlessLookup PAINLESS_LOOKUP = PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS);
|
||||||
|
|
||||||
protected PainlessScriptEngine scriptEngine;
|
protected PainlessScriptEngine scriptEngine;
|
||||||
|
|
||||||
@Before
|
@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) {
|
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
|
// test for ambiguity errors before running the actual script if picky is true
|
||||||
if (picky) {
|
if (picky) {
|
||||||
PainlessLookup painlessLookup = PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS);
|
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(PAINLESS_LOOKUP, GenericElasticsearchScript.class);
|
||||||
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, GenericElasticsearchScript.class);
|
|
||||||
CompilerSettings pickySettings = new CompilerSettings();
|
CompilerSettings pickySettings = new CompilerSettings();
|
||||||
pickySettings.setPicky(true);
|
pickySettings.setPicky(true);
|
||||||
pickySettings.setRegexesEnabled(CompilerSettings.REGEX_ENABLED.get(scriptEngineSettings()));
|
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
|
// test actual script execution
|
||||||
ExecutableScript.Factory factory = scriptEngine.compile(null, script, ExecutableScript.CONTEXT, compileParams);
|
ExecutableScript.Factory factory = scriptEngine.compile(null, script, ExecutableScript.CONTEXT, compileParams);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user