Skip to content

Commit facec18

Browse files
authored
Painless: Add Imported Static Method (#33440)
Allow static methods to be imported in Painless and called using just the method name.
1 parent 9a404f3 commit facec18

File tree

9 files changed

+282
-58
lines changed

9 files changed

+282
-58
lines changed

modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,19 @@ public final class Whitelist {
6161
/** The {@link List} of all the whitelisted Painless classes. */
6262
public final List<WhitelistClass> whitelistClasses;
6363

64+
/** The {@link List} of all the whitelisted static Painless methods. */
65+
public final List<WhitelistMethod> whitelistImportedMethods;
66+
67+
/** The {@link List} of all the whitelisted Painless bindings. */
6468
public final List<WhitelistBinding> whitelistBindings;
6569

66-
/** Standard constructor. All values must be not {@code null}. */
67-
public Whitelist(ClassLoader classLoader, List<WhitelistClass> whitelistClasses, List<WhitelistBinding> whitelistBindings) {
70+
/** Standard constructor. All values must be not {@code null}. */
71+
public Whitelist(ClassLoader classLoader, List<WhitelistClass> whitelistClasses,
72+
List<WhitelistMethod> whitelistImportedMethods, List<WhitelistBinding> whitelistBindings) {
73+
6874
this.classLoader = Objects.requireNonNull(classLoader);
6975
this.whitelistClasses = Collections.unmodifiableList(Objects.requireNonNull(whitelistClasses));
76+
this.whitelistImportedMethods = Collections.unmodifiableList(Objects.requireNonNull(whitelistImportedMethods));
7077
this.whitelistBindings = Collections.unmodifiableList(Objects.requireNonNull(whitelistBindings));
7178
}
7279
}

modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ public final class WhitelistLoader {
133133
*/
134134
public static Whitelist loadFromResourceFiles(Class<?> resource, String... filepaths) {
135135
List<WhitelistClass> whitelistClasses = new ArrayList<>();
136+
List<WhitelistMethod> whitelistStatics = new ArrayList<>();
136137
List<WhitelistBinding> whitelistBindings = new ArrayList<>();
137138

138139
// Execute a single pass through the whitelist text files. This will gather all the
@@ -192,18 +193,18 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep
192193
whitelistConstructors = new ArrayList<>();
193194
whitelistMethods = new ArrayList<>();
194195
whitelistFields = new ArrayList<>();
195-
} else if (line.startsWith("static ")) {
196+
} else if (line.startsWith("static_import ")) {
196197
// Ensure the final token of the line is '{'.
197198
if (line.endsWith("{") == false) {
198199
throw new IllegalArgumentException(
199-
"invalid static definition: failed to parse static opening bracket [" + line + "]");
200+
"invalid static import definition: failed to parse static import opening bracket [" + line + "]");
200201
}
201202

202203
if (parseType != null) {
203-
throw new IllegalArgumentException("invalid definition: cannot embed static definition [" + line + "]");
204+
throw new IllegalArgumentException("invalid definition: cannot embed static import definition [" + line + "]");
204205
}
205206

206-
parseType = "static";
207+
parseType = "static_import";
207208

208209
// Handle the end of a definition and reset all previously gathered values.
209210
// Expects the following format: '}' '\n'
@@ -229,9 +230,9 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep
229230
// Reset the parseType.
230231
parseType = null;
231232

232-
// Handle static definition types.
233-
// Expects the following format: ID ID '(' ( ID ( ',' ID )* )? ')' 'bound_to' ID '\n'
234-
} else if ("static".equals(parseType)) {
233+
// Handle static import definition types.
234+
// Expects the following format: ID ID '(' ( ID ( ',' ID )* )? ')' ( 'from_class' | 'bound_to' ) ID '\n'
235+
} else if ("static_import".equals(parseType)) {
235236
// Mark the origin of this parsable object.
236237
String origin = "[" + filepath + "]:[" + number + "]";
237238

@@ -240,7 +241,7 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep
240241

241242
if (parameterStartIndex == -1) {
242243
throw new IllegalArgumentException(
243-
"illegal static definition: start of method parameters not found [" + line + "]");
244+
"illegal static import definition: start of method parameters not found [" + line + "]");
244245
}
245246

246247
String[] tokens = line.substring(0, parameterStartIndex).trim().split("\\s+");
@@ -261,7 +262,7 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep
261262

262263
if (parameterEndIndex == -1) {
263264
throw new IllegalArgumentException(
264-
"illegal static definition: end of method parameters not found [" + line + "]");
265+
"illegal static import definition: end of method parameters not found [" + line + "]");
265266
}
266267

267268
String[] canonicalTypeNameParameters =
@@ -272,39 +273,37 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep
272273
canonicalTypeNameParameters = new String[0];
273274
}
274275

275-
// Parse the static type and class.
276+
// Parse the static import type and class.
276277
tokens = line.substring(parameterEndIndex + 1).trim().split("\\s+");
277278

278-
String staticType;
279+
String staticImportType;
279280
String targetJavaClassName;
280281

281282
// Based on the number of tokens, look up the type and class.
282283
if (tokens.length == 2) {
283-
staticType = tokens[0];
284+
staticImportType = tokens[0];
284285
targetJavaClassName = tokens[1];
285286
} else {
286-
throw new IllegalArgumentException("invalid static definition: unexpected format [" + line + "]");
287+
throw new IllegalArgumentException("invalid static import definition: unexpected format [" + line + "]");
287288
}
288289

289-
// Check the static type is valid.
290-
if ("bound_to".equals(staticType) == false) {
291-
throw new IllegalArgumentException(
292-
"invalid static definition: unexpected static type [" + staticType + "] [" + line + "]");
290+
// Add a static import method or binding depending on the static import type.
291+
if ("from_class".equals(staticImportType)) {
292+
whitelistStatics.add(new WhitelistMethod(origin, targetJavaClassName,
293+
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
294+
} else if ("bound_to".equals(staticImportType)) {
295+
whitelistBindings.add(new WhitelistBinding(origin, targetJavaClassName,
296+
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
297+
} else {
298+
throw new IllegalArgumentException("invalid static import definition: " +
299+
"unexpected static import type [" + staticImportType + "] [" + line + "]");
293300
}
294301

295-
whitelistBindings.add(new WhitelistBinding(origin, targetJavaClassName,
296-
methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
297-
298302
// Handle class definition types.
299303
} else if ("class".equals(parseType)) {
300304
// Mark the origin of this parsable object.
301305
String origin = "[" + filepath + "]:[" + number + "]";
302306

303-
// Ensure we have a defined class before adding any constructors, methods, augmented methods, or fields.
304-
if (parseType == null) {
305-
throw new IllegalArgumentException("invalid definition: expected one of ['class', 'static'] [" + line + "]");
306-
}
307-
308307
// Handle the case for a constructor definition.
309308
// Expects the following format: '(' ( ID ( ',' ID )* )? ')' '\n'
310309
if (line.startsWith("(")) {
@@ -393,7 +392,7 @@ public static Whitelist loadFromResourceFiles(Class<?> resource, String... filep
393392

394393
ClassLoader loader = AccessController.doPrivileged((PrivilegedAction<ClassLoader>)resource::getClassLoader);
395394

396-
return new Whitelist(loader, whitelistClasses, whitelistBindings);
395+
return new Whitelist(loader, whitelistClasses, whitelistStatics, whitelistBindings);
397396
}
398397

399398
private WhitelistLoader() {}

modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTest.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@
2424

2525
/** Currently just a dummy class for testing a few features not yet exposed by whitelist! */
2626
public class FeatureTest {
27+
/** static method that returns true */
28+
public static boolean overloadedStatic() {
29+
return true;
30+
}
31+
32+
/** static method that returns what you ask it */
33+
public static boolean overloadedStatic(boolean whatToReturn) {
34+
return whatToReturn;
35+
}
36+
37+
/** static method only whitelisted as a static */
38+
public static float staticAddFloatsTest(float x, float y) {
39+
return x + y;
40+
}
41+
2742
private int x;
2843
private int y;
2944
public int z;
@@ -58,21 +73,12 @@ public void setY(int y) {
5873
this.y = y;
5974
}
6075

61-
/** static method that returns true */
62-
public static boolean overloadedStatic() {
63-
return true;
64-
}
65-
66-
/** static method that returns what you ask it */
67-
public static boolean overloadedStatic(boolean whatToReturn) {
68-
return whatToReturn;
69-
}
70-
7176
/** method taking two functions! */
7277
public Object twoFunctionsOfX(Function<Object,Object> f, Function<Object,Object> g) {
7378
return f.apply(g.apply(x));
7479
}
7580

81+
/** method to take in a list */
7682
public void listInput(List<Object> list) {
7783

7884
}

modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookup.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,23 @@ public final class PainlessLookup {
3737
private final Map<String, Class<?>> canonicalClassNamesToClasses;
3838
private final Map<Class<?>, PainlessClass> classesToPainlessClasses;
3939

40+
private final Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods;
4041
private final Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings;
4142

4243
PainlessLookup(Map<String, Class<?>> canonicalClassNamesToClasses, Map<Class<?>, PainlessClass> classesToPainlessClasses,
44+
Map<String, PainlessMethod> painlessMethodKeysToImportedPainlessMethods,
4345
Map<String, PainlessBinding> painlessMethodKeysToPainlessBindings) {
46+
4447
Objects.requireNonNull(canonicalClassNamesToClasses);
4548
Objects.requireNonNull(classesToPainlessClasses);
4649

50+
Objects.requireNonNull(painlessMethodKeysToImportedPainlessMethods);
51+
Objects.requireNonNull(painlessMethodKeysToPainlessBindings);
52+
4753
this.canonicalClassNamesToClasses = Collections.unmodifiableMap(canonicalClassNamesToClasses);
4854
this.classesToPainlessClasses = Collections.unmodifiableMap(classesToPainlessClasses);
4955

56+
this.painlessMethodKeysToImportedPainlessMethods = Collections.unmodifiableMap(painlessMethodKeysToImportedPainlessMethods);
5057
this.painlessMethodKeysToPainlessBindings = Collections.unmodifiableMap(painlessMethodKeysToPainlessBindings);
5158
}
5259

@@ -167,6 +174,14 @@ public PainlessField lookupPainlessField(Class<?> targetClass, boolean isStatic,
167174
return painlessField;
168175
}
169176

177+
public PainlessMethod lookupImportedPainlessMethod(String methodName, int arity) {
178+
Objects.requireNonNull(methodName);
179+
180+
String painlessMethodKey = buildPainlessMethodKey(methodName, arity);
181+
182+
return painlessMethodKeysToImportedPainlessMethods.get(painlessMethodKey);
183+
}
184+
170185
public PainlessBinding lookupPainlessBinding(String methodName, int arity) {
171186
Objects.requireNonNull(methodName);
172187

0 commit comments

Comments
 (0)