Skip to content

Commit c69e62d

Browse files
authored
Painless: Add PainlessConstructor (#32447)
PainlessMethod was being used as both a method and a constructor, and while there are similarities, there are also some major differences. This allows the reflection objects to be stored reducing the number of other pieces of data stored in a PainlessMethod as they are now redundant. This temporarily increases some of the code in FunctionRef and PainlessDocGenerator as they now differentiate between constructors and methods, BUT is also makes the code more maintainable because there aren't checks in several places anymore to differentiate.
1 parent 1e0fceb commit c69e62d

13 files changed

+307
-76
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLooku
368368
ref = new FunctionRef(clazz, interfaceMethod, call, handle.type(), captures.length);
369369
} else {
370370
// whitelist lookup
371-
ref = new FunctionRef(painlessLookup, clazz, type, call, captures.length);
371+
ref = FunctionRef.resolveFromLookup(painlessLookup, clazz, type, call, captures.length);
372372
}
373373
final CallSite callSite = LambdaBootstrap.lambdaBootstrap(
374374
methodHandlesLookup,

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

+90-29
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@
2020
package org.elasticsearch.painless;
2121

2222
import org.elasticsearch.painless.lookup.PainlessClass;
23+
import org.elasticsearch.painless.lookup.PainlessConstructor;
2324
import org.elasticsearch.painless.lookup.PainlessLookup;
2425
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
2526
import org.elasticsearch.painless.lookup.PainlessMethod;
2627
import org.objectweb.asm.Type;
2728

2829
import java.lang.invoke.MethodType;
30+
import java.lang.reflect.Constructor;
2931
import java.lang.reflect.Modifier;
32+
import java.util.List;
3033

3134
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
3235
import static org.objectweb.asm.Opcodes.H_INVOKEINTERFACE;
@@ -59,8 +62,10 @@ public class FunctionRef {
5962

6063
/** interface method */
6164
public final PainlessMethod interfaceMethod;
62-
/** delegate method */
63-
public final PainlessMethod delegateMethod;
65+
/** delegate method type parameters */
66+
public final List<Class<?>> delegateTypeParameters;
67+
/** delegate method return type */
68+
public final Class<?> delegateReturnType;
6469

6570
/** factory method type descriptor */
6671
public final String factoryDescriptor;
@@ -80,9 +85,47 @@ public class FunctionRef {
8085
* @param call the right hand side of a method reference expression
8186
* @param numCaptures number of captured arguments
8287
*/
83-
public FunctionRef(PainlessLookup painlessLookup, Class<?> expected, String type, String call, int numCaptures) {
84-
this(expected, painlessLookup.getPainlessStructFromJavaClass(expected).functionalMethod,
85-
lookup(painlessLookup, expected, type, call, numCaptures > 0), numCaptures);
88+
public static FunctionRef resolveFromLookup(
89+
PainlessLookup painlessLookup, Class<?> expected, String type, String call, int numCaptures) {
90+
91+
if ("new".equals(call)) {
92+
return new FunctionRef(expected, painlessLookup.getPainlessStructFromJavaClass(expected).functionalMethod,
93+
lookup(painlessLookup, expected, type), numCaptures);
94+
} else {
95+
return new FunctionRef(expected, painlessLookup.getPainlessStructFromJavaClass(expected).functionalMethod,
96+
lookup(painlessLookup, expected, type, call, numCaptures > 0), numCaptures);
97+
}
98+
}
99+
100+
/**
101+
* Creates a new FunctionRef (already resolved)
102+
* @param expected functional interface type to implement
103+
* @param interfaceMethod functional interface method
104+
* @param delegateConstructor implementation constructor
105+
* @param numCaptures number of captured arguments
106+
*/
107+
public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, PainlessConstructor delegateConstructor, int numCaptures) {
108+
Constructor<?> javaConstructor = delegateConstructor.javaConstructor;
109+
MethodType delegateMethodType = delegateConstructor.methodType;
110+
111+
interfaceMethodName = interfaceMethod.name;
112+
factoryMethodType = MethodType.methodType(expected,
113+
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
114+
interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
115+
116+
delegateClassName = javaConstructor.getDeclaringClass().getName();
117+
isDelegateInterface = false;
118+
delegateInvokeType = H_NEWINVOKESPECIAL;
119+
delegateMethodName = PainlessLookupUtility.CONSTRUCTOR_NAME;
120+
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
121+
122+
this.interfaceMethod = interfaceMethod;
123+
delegateTypeParameters = delegateConstructor.typeParameters;
124+
delegateReturnType = void.class;
125+
126+
factoryDescriptor = factoryMethodType.toMethodDescriptorString();
127+
interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString());
128+
delegateType = Type.getMethodType(this.delegateMethodType.toMethodDescriptorString());
86129
}
87130

88131
/**
@@ -112,9 +155,7 @@ public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, PainlessMe
112155
isDelegateInterface = delegateMethod.target.isInterface();
113156
}
114157

115-
if ("<init>".equals(delegateMethod.name)) {
116-
delegateInvokeType = H_NEWINVOKESPECIAL;
117-
} else if (Modifier.isStatic(delegateMethod.modifiers)) {
158+
if (Modifier.isStatic(delegateMethod.modifiers)) {
118159
delegateInvokeType = H_INVOKESTATIC;
119160
} else if (delegateMethod.target.isInterface()) {
120161
delegateInvokeType = H_INVOKEINTERFACE;
@@ -126,7 +167,8 @@ public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, PainlessMe
126167
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
127168

128169
this.interfaceMethod = interfaceMethod;
129-
this.delegateMethod = delegateMethod;
170+
delegateTypeParameters = delegateMethod.arguments;
171+
delegateReturnType = delegateMethod.rtn;
130172

131173
factoryDescriptor = factoryMethodType.toMethodDescriptorString();
132174
interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString());
@@ -151,13 +193,37 @@ public FunctionRef(Class<?> expected,
151193
isDelegateInterface = false;
152194

153195
this.interfaceMethod = null;
154-
delegateMethod = null;
196+
delegateTypeParameters = null;
197+
delegateReturnType = null;
155198

156199
factoryDescriptor = null;
157200
interfaceType = null;
158201
delegateType = null;
159202
}
160203

204+
/**
205+
* Looks up {@code type} from the whitelist, and returns a matching constructor.
206+
*/
207+
private static PainlessConstructor lookup(PainlessLookup painlessLookup, Class<?> expected, String type) {
208+
// check its really a functional interface
209+
// for e.g. Comparable
210+
PainlessMethod method = painlessLookup.getPainlessStructFromJavaClass(expected).functionalMethod;
211+
if (method == null) {
212+
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::new] " +
213+
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface");
214+
}
215+
216+
// lookup requested constructor
217+
PainlessClass struct = painlessLookup.getPainlessStructFromJavaClass(painlessLookup.getJavaClassFromPainlessType(type));
218+
PainlessConstructor impl = struct.constructors.get(PainlessLookupUtility.buildPainlessConstructorKey(method.arguments.size()));
219+
220+
if (impl == null) {
221+
throw new IllegalArgumentException("Unknown reference [" + type + "::new] matching [" + expected + "]");
222+
}
223+
224+
return impl;
225+
}
226+
161227
/**
162228
* Looks up {@code type::call} from the whitelist, and returns a matching method.
163229
*/
@@ -174,27 +240,22 @@ private static PainlessMethod lookup(PainlessLookup painlessLookup, Class<?> exp
174240
// lookup requested method
175241
PainlessClass struct = painlessLookup.getPainlessStructFromJavaClass(painlessLookup.getJavaClassFromPainlessType(type));
176242
final PainlessMethod impl;
177-
// ctor ref
178-
if ("new".equals(call)) {
179-
impl = struct.constructors.get(PainlessLookupUtility.buildPainlessMethodKey("<init>", method.arguments.size()));
180-
} else {
181-
// look for a static impl first
182-
PainlessMethod staticImpl =
183-
struct.staticMethods.get(PainlessLookupUtility.buildPainlessMethodKey(call, method.arguments.size()));
184-
if (staticImpl == null) {
185-
// otherwise a virtual impl
186-
final int arity;
187-
if (receiverCaptured) {
188-
// receiver captured
189-
arity = method.arguments.size();
190-
} else {
191-
// receiver passed
192-
arity = method.arguments.size() - 1;
193-
}
194-
impl = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey(call, arity));
243+
// look for a static impl first
244+
PainlessMethod staticImpl =
245+
struct.staticMethods.get(PainlessLookupUtility.buildPainlessMethodKey(call, method.arguments.size()));
246+
if (staticImpl == null) {
247+
// otherwise a virtual impl
248+
final int arity;
249+
if (receiverCaptured) {
250+
// receiver captured
251+
arity = method.arguments.size();
195252
} else {
196-
impl = staticImpl;
253+
// receiver passed
254+
arity = method.arguments.size() - 1;
197255
}
256+
impl = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey(call, arity));
257+
} else {
258+
impl = staticImpl;
198259
}
199260
if (impl == null) {
200261
throw new IllegalArgumentException("Unknown reference [" + type + "::" + call + "] matching " +

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
import java.util.Map;
2525

2626
public final class PainlessClass {
27-
public final Map<String, PainlessMethod> constructors;
27+
public final Map<String, PainlessConstructor> constructors;
28+
2829
public final Map<String, PainlessMethod> staticMethods;
2930
public final Map<String, PainlessMethod> methods;
3031

@@ -36,13 +37,14 @@ public final class PainlessClass {
3637

3738
public final PainlessMethod functionalMethod;
3839

39-
PainlessClass(Map<String, PainlessMethod> constructors,
40+
PainlessClass(Map<String, PainlessConstructor> constructors,
4041
Map<String, PainlessMethod> staticMethods, Map<String, PainlessMethod> methods,
4142
Map<String, PainlessField> staticFields, Map<String, PainlessField> fields,
4243
Map<String, MethodHandle> getterMethodHandles, Map<String, MethodHandle> setterMethodHandles,
4344
PainlessMethod functionalMethod) {
4445

4546
this.constructors = Collections.unmodifiableMap(constructors);
47+
4648
this.staticMethods = Collections.unmodifiableMap(staticMethods);
4749
this.methods = Collections.unmodifiableMap(methods);
4850

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
import java.util.Map;
2525

2626
final class PainlessClassBuilder {
27-
final Map<String, PainlessMethod> constructors;
27+
final Map<String, PainlessConstructor> constructors;
28+
2829
final Map<String, PainlessMethod> staticMethods;
2930
final Map<String, PainlessMethod> methods;
3031

@@ -38,6 +39,7 @@ final class PainlessClassBuilder {
3839

3940
PainlessClassBuilder() {
4041
constructors = new HashMap<>();
42+
4143
staticMethods = new HashMap<>();
4244
methods = new HashMap<>();
4345

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.painless.lookup;
21+
22+
import java.lang.invoke.MethodHandle;
23+
import java.lang.invoke.MethodType;
24+
import java.lang.reflect.Constructor;
25+
import java.util.List;
26+
27+
public class PainlessConstructor {
28+
public final Constructor<?> javaConstructor;
29+
public final List<Class<?>> typeParameters;
30+
public final MethodHandle methodHandle;
31+
public final MethodType methodType;
32+
33+
PainlessConstructor(Constructor<?> javaConstructor, List<Class<?>> typeParameters, MethodHandle methodHandle, MethodType methodType) {
34+
this.javaConstructor = javaConstructor;
35+
this.typeParameters = typeParameters;
36+
this.methodHandle = methodHandle;
37+
this.methodType = methodType;
38+
}
39+
}

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

+45-14
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,47 @@
4040
import java.util.Objects;
4141
import java.util.regex.Pattern;
4242

43-
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.CONSTRUCTOR_NAME;
4443
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.DEF_CLASS_NAME;
44+
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessConstructorKey;
4545
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessFieldKey;
4646
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.buildPainlessMethodKey;
4747
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName;
4848
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToJavaType;
4949
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typesToCanonicalTypeNames;
5050

51-
public class PainlessLookupBuilder {
51+
public final class PainlessLookupBuilder {
52+
53+
private static class PainlessConstructorCacheKey {
54+
55+
private final Class<?> targetType;
56+
private final List<Class<?>> typeParameters;
57+
58+
private PainlessConstructorCacheKey(Class<?> targetType, List<Class<?>> typeParameters) {
59+
this.targetType = targetType;
60+
this.typeParameters = Collections.unmodifiableList(typeParameters);
61+
}
62+
63+
@Override
64+
public boolean equals(Object object) {
65+
if (this == object) {
66+
return true;
67+
}
68+
69+
if (object == null || getClass() != object.getClass()) {
70+
return false;
71+
}
72+
73+
PainlessConstructorCacheKey that = (PainlessConstructorCacheKey)object;
74+
75+
return Objects.equals(targetType, that.targetType) &&
76+
Objects.equals(typeParameters, that.typeParameters);
77+
}
78+
79+
@Override
80+
public int hashCode() {
81+
return Objects.hash(targetType, typeParameters);
82+
}
83+
}
5284

5385
private static class PainlessMethodCacheKey {
5486

@@ -120,8 +152,9 @@ public int hashCode() {
120152
}
121153
}
122154

123-
private static final Map<PainlessMethodCacheKey, PainlessMethod> painlessMethodCache = new HashMap<>();
124-
private static final Map<PainlessFieldCacheKey, PainlessField> painlessFieldCache = new HashMap<>();
155+
private static final Map<PainlessConstructorCacheKey, PainlessConstructor> painlessConstuctorCache = new HashMap<>();
156+
private static final Map<PainlessMethodCacheKey, PainlessMethod> painlessMethodCache = new HashMap<>();
157+
private static final Map<PainlessFieldCacheKey, PainlessField> painlessFieldCache = new HashMap<>();
125158

126159
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][._a-zA-Z0-9]*$");
127160
private static final Pattern METHOD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
@@ -343,11 +376,10 @@ public void addPainlessConstructor(Class<?> targetClass, List<Class<?>> typePara
343376
"[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "] not found", nsme);
344377
}
345378

346-
String painlessMethodKey = buildPainlessMethodKey(CONSTRUCTOR_NAME, typeParametersSize);
347-
PainlessMethod painlessConstructor = painlessClassBuilder.constructors.get(painlessMethodKey);
379+
String painlessConstructorKey = buildPainlessConstructorKey(typeParametersSize);
380+
PainlessConstructor painlessConstructor = painlessClassBuilder.constructors.get(painlessConstructorKey);
348381

349382
if (painlessConstructor == null) {
350-
org.objectweb.asm.commons.Method asmConstructor = org.objectweb.asm.commons.Method.getMethod(javaConstructor);
351383
MethodHandle methodHandle;
352384

353385
try {
@@ -359,17 +391,16 @@ public void addPainlessConstructor(Class<?> targetClass, List<Class<?>> typePara
359391

360392
MethodType methodType = methodHandle.type();
361393

362-
painlessConstructor = painlessMethodCache.computeIfAbsent(
363-
new PainlessMethodCacheKey(targetClass, CONSTRUCTOR_NAME, typeParameters),
364-
key -> new PainlessMethod(CONSTRUCTOR_NAME, targetClass, null, void.class, typeParameters,
365-
asmConstructor, javaConstructor.getModifiers(), methodHandle, methodType)
394+
painlessConstructor = painlessConstuctorCache.computeIfAbsent(
395+
new PainlessConstructorCacheKey(targetClass, typeParameters),
396+
key -> new PainlessConstructor(javaConstructor, typeParameters, methodHandle, methodType)
366397
);
367398

368-
painlessClassBuilder.constructors.put(painlessMethodKey, painlessConstructor);
369-
} else if (painlessConstructor.arguments.equals(typeParameters) == false){
399+
painlessClassBuilder.constructors.put(painlessConstructorKey, painlessConstructor);
400+
} else if (painlessConstructor.typeParameters.equals(typeParameters) == false){
370401
throw new IllegalArgumentException("cannot have constructors " +
371402
"[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(typeParameters) + "] and " +
372-
"[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(painlessConstructor.arguments) + "] " +
403+
"[[" + targetCanonicalClassName + "], " + typesToCanonicalTypeNames(painlessConstructor.typeParameters) + "] " +
373404
"with the same arity and different type parameters");
374405
}
375406
}

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

+7
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,13 @@ public static boolean isConstantType(Class<?> type) {
336336
type == String.class;
337337
}
338338

339+
/**
340+
* Constructs a painless constructor key used to lookup painless constructors from a painless class.
341+
*/
342+
public static String buildPainlessConstructorKey(int constructorArity) {
343+
return CONSTRUCTOR_NAME + "/" + constructorArity;
344+
}
345+
339346
/**
340347
* Constructs a painless method key used to lookup painless methods from a painless class.
341348
*/

0 commit comments

Comments
 (0)