Skip to content

Commit a4471f5

Browse files
authored
Support script context stateful factory in Painless. (#25233)
1 parent 68deda6 commit a4471f5

File tree

2 files changed

+171
-9
lines changed

2 files changed

+171
-9
lines changed

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

Lines changed: 128 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.security.PrivilegedAction;
4444
import java.security.ProtectionDomain;
4545
import java.util.ArrayList;
46+
import java.util.Arrays;
4647
import java.util.Collection;
4748
import java.util.Collections;
4849
import java.util.HashMap;
@@ -158,20 +159,126 @@ public Loader run() {
158159

159160
compile(contextsToCompilers.get(context), loader, scriptName, scriptSource, params);
160161

161-
return generateFactory(loader, context);
162+
if (context.statefulFactoryClazz != null) {
163+
return generateFactory(loader, context, generateStatefulFactory(loader, context));
164+
} else {
165+
return generateFactory(loader, context, WriterConstants.CLASS_TYPE);
166+
}
167+
}
168+
}
169+
170+
/**
171+
* Generates a stateful factory class that will return script instances. Acts as a middle man between
172+
* the {@link ScriptContext#factoryClazz} and the {@link ScriptContext#instanceClazz} when used so that
173+
* the stateless factory can be used for caching and the stateful factory can act as a cache for new
174+
* script instances. Uses the newInstance method from a {@link ScriptContext#statefulFactoryClazz} to
175+
* define the factory method to create new instances of the {@link ScriptContext#instanceClazz}.
176+
* @param loader The {@link ClassLoader} that is used to define the factory class and script class.
177+
* @param context The {@link ScriptContext}'s semantics are used to define the factory class.
178+
* @param <T> The factory class.
179+
* @return A factory class that will return script instances.
180+
*/
181+
private <T> Type generateStatefulFactory(Loader loader, ScriptContext<T> context) {
182+
int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
183+
int classAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL;
184+
String interfaceBase = Type.getType(context.statefulFactoryClazz).getInternalName();
185+
String className = interfaceBase + "$StatefulFactory";
186+
String classInterfaces[] = new String[] { interfaceBase };
187+
188+
ClassWriter writer = new ClassWriter(classFrames);
189+
writer.visit(WriterConstants.CLASS_VERSION, classAccess, className, null, OBJECT_TYPE.getInternalName(), classInterfaces);
190+
191+
Method newFactory = null;
192+
193+
for (Method method : context.factoryClazz.getMethods()) {
194+
if ("newFactory".equals(method.getName())) {
195+
newFactory = method;
196+
197+
break;
198+
}
199+
}
200+
201+
for (int count = 0; count < newFactory.getParameterTypes().length; ++count) {
202+
writer.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, "$arg" + count,
203+
Type.getType(newFactory.getParameterTypes()[count]).getDescriptor(), null, null).visitEnd();
204+
}
205+
206+
org.objectweb.asm.commons.Method base =
207+
new org.objectweb.asm.commons.Method("<init>", MethodType.methodType(void.class).toMethodDescriptorString());
208+
org.objectweb.asm.commons.Method init = new org.objectweb.asm.commons.Method("<init>",
209+
MethodType.methodType(void.class, newFactory.getParameterTypes()).toMethodDescriptorString());
210+
211+
GeneratorAdapter constructor = new GeneratorAdapter(Opcodes.ASM5, init,
212+
writer.visitMethod(Opcodes.ACC_PUBLIC, init.getName(), init.getDescriptor(), null, null));
213+
constructor.visitCode();
214+
constructor.loadThis();
215+
constructor.invokeConstructor(OBJECT_TYPE, base);
216+
217+
for (int count = 0; count < newFactory.getParameterTypes().length; ++count) {
218+
constructor.loadThis();
219+
constructor.loadArg(count);
220+
constructor.putField(Type.getType(className), "$arg" + count, Type.getType(newFactory.getParameterTypes()[count]));
221+
}
222+
223+
constructor.returnValue();
224+
constructor.endMethod();
225+
226+
Method newInstance = null;
227+
228+
for (Method method : context.statefulFactoryClazz.getMethods()) {
229+
if ("newInstance".equals(method.getName())) {
230+
newInstance = method;
231+
232+
break;
233+
}
234+
}
235+
236+
org.objectweb.asm.commons.Method instance = new org.objectweb.asm.commons.Method(newInstance.getName(),
237+
MethodType.methodType(newInstance.getReturnType(), newInstance.getParameterTypes()).toMethodDescriptorString());
238+
239+
List<Class<?>> parameters = new ArrayList<>(Arrays.asList(newFactory.getParameterTypes()));
240+
parameters.addAll(Arrays.asList(newInstance.getParameterTypes()));
241+
242+
org.objectweb.asm.commons.Method constru = new org.objectweb.asm.commons.Method("<init>",
243+
MethodType.methodType(void.class, parameters.toArray(new Class<?>[] {})).toMethodDescriptorString());
244+
245+
GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ASM5, instance,
246+
writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
247+
instance.getName(), instance.getDescriptor(), null, null));
248+
adapter.visitCode();
249+
adapter.newInstance(WriterConstants.CLASS_TYPE);
250+
adapter.dup();
251+
252+
for (int count = 0; count < newFactory.getParameterTypes().length; ++count) {
253+
adapter.loadThis();
254+
adapter.getField(Type.getType(className), "$arg" + count, Type.getType(newFactory.getParameterTypes()[count]));
162255
}
256+
257+
adapter.loadArgs();
258+
adapter.invokeConstructor(WriterConstants.CLASS_TYPE, constru);
259+
adapter.returnValue();
260+
adapter.endMethod();
261+
262+
writer.visitEnd();
263+
264+
loader.defineFactory(className.replace('/', '.'), writer.toByteArray());
265+
266+
return Type.getType(className);
163267
}
164268

165269
/**
166-
* Generates a factory class that will return script instances.
270+
* Generates a factory class that will return script instances or stateful factories.
167271
* Uses the newInstance method from a {@link ScriptContext#factoryClazz} to define the factory method
168-
* to create new instances of the {@link ScriptContext#instanceClazz}.
272+
* to create new instances of the {@link ScriptContext#instanceClazz} or uses the newFactory method
273+
* to create new factories of the {@link ScriptContext#statefulFactoryClazz}.
169274
* @param loader The {@link ClassLoader} that is used to define the factory class and script class.
170275
* @param context The {@link ScriptContext}'s semantics are used to define the factory class.
276+
* @param classType The type to be instaniated in the newFactory or newInstance method. Depends
277+
* on whether a {@link ScriptContext#statefulFactoryClazz} is specified.
171278
* @param <T> The factory class.
172279
* @return A factory class that will return script instances.
173280
*/
174-
private <T> T generateFactory(Loader loader, ScriptContext<T> context) {
281+
private <T> T generateFactory(Loader loader, ScriptContext<T> context, Type classType) {
175282
int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
176283
int classAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER| Opcodes.ACC_FINAL;
177284
String interfaceBase = Type.getType(context.factoryClazz).getInternalName();
@@ -188,25 +295,37 @@ private <T> T generateFactory(Loader loader, ScriptContext<T> context) {
188295
writer.visitMethod(Opcodes.ACC_PUBLIC, init.getName(), init.getDescriptor(), null, null));
189296
constructor.visitCode();
190297
constructor.loadThis();
191-
constructor.loadArgs();
192298
constructor.invokeConstructor(OBJECT_TYPE, init);
193299
constructor.returnValue();
194300
constructor.endMethod();
195301

196-
Method reflect = context.factoryClazz.getMethods()[0];
302+
Method reflect = null;
303+
304+
for (Method method : context.factoryClazz.getMethods()) {
305+
if ("newInstance".equals(method.getName())) {
306+
reflect = method;
307+
308+
break;
309+
} else if ("newFactory".equals(method.getName())) {
310+
reflect = method;
311+
312+
break;
313+
}
314+
}
315+
197316
org.objectweb.asm.commons.Method instance = new org.objectweb.asm.commons.Method(reflect.getName(),
198317
MethodType.methodType(reflect.getReturnType(), reflect.getParameterTypes()).toMethodDescriptorString());
199318
org.objectweb.asm.commons.Method constru = new org.objectweb.asm.commons.Method("<init>",
200319
MethodType.methodType(void.class, reflect.getParameterTypes()).toMethodDescriptorString());
201320

202321
GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ASM5, instance,
203-
writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL,
322+
writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
204323
instance.getName(), instance.getDescriptor(), null, null));
205324
adapter.visitCode();
206-
adapter.newInstance(WriterConstants.CLASS_TYPE);
325+
adapter.newInstance(classType);
207326
adapter.dup();
208327
adapter.loadArgs();
209-
adapter.invokeConstructor(WriterConstants.CLASS_TYPE, constru);
328+
adapter.invokeConstructor(classType, constru);
210329
adapter.returnValue();
211330
adapter.endMethod();
212331

modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,56 @@ public class FactoryTests extends ScriptTestCase {
3030

3131
protected Collection<ScriptContext<?>> scriptContexts() {
3232
Collection<ScriptContext<?>> contexts = super.scriptContexts();
33+
contexts.add(StatefulFactoryTestScript.CONTEXT);
3334
contexts.add(FactoryTestScript.CONTEXT);
3435
contexts.add(EmptyTestScript.CONTEXT);
3536
contexts.add(TemplateScript.CONTEXT);
3637

3738
return contexts;
3839
}
3940

41+
public abstract static class StatefulFactoryTestScript {
42+
private final int x;
43+
private final int y;
44+
45+
public StatefulFactoryTestScript(int x, int y, int a, int b) {
46+
this.x = x*a;
47+
this.y = y*b;
48+
}
49+
50+
public int getX() {
51+
return x;
52+
}
53+
54+
public int getY() {
55+
return y*2;
56+
}
57+
58+
public static final String[] PARAMETERS = new String[] {"test"};
59+
public abstract Object execute(int test);
60+
61+
public interface StatefulFactory {
62+
StatefulFactoryTestScript newInstance(int a, int b);
63+
}
64+
65+
public interface Factory {
66+
StatefulFactory newFactory(int x, int y);
67+
}
68+
69+
public static final ScriptContext<StatefulFactoryTestScript.Factory> CONTEXT =
70+
new ScriptContext<>("test", StatefulFactoryTestScript.Factory.class);
71+
}
72+
73+
public void testStatefulFactory() {
74+
StatefulFactoryTestScript.Factory factory = scriptEngine.compile(
75+
"stateful_factory_test", "test + x + y", StatefulFactoryTestScript.CONTEXT, Collections.emptyMap());
76+
StatefulFactoryTestScript.StatefulFactory statefulFactory = factory.newFactory(1, 2);
77+
StatefulFactoryTestScript script = statefulFactory.newInstance(3, 4);
78+
assertEquals(22, script.execute(3));
79+
statefulFactory.newInstance(5, 6);
80+
assertEquals(26, script.execute(7));
81+
}
82+
4083
public abstract static class FactoryTestScript {
4184
private final Map<String, Object> params;
4285

0 commit comments

Comments
 (0)