1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl3;
18
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20 import static org.junit.jupiter.api.Assertions.assertNotNull;
21 import static org.junit.jupiter.api.Assertions.assertNotSame;
22 import static org.junit.jupiter.api.Assertions.assertNull;
23 import static org.junit.jupiter.api.Assertions.assertSame;
24
25 import java.io.File;
26 import java.lang.ref.Reference;
27 import java.lang.ref.ReferenceQueue;
28 import java.lang.ref.SoftReference;
29 import java.lang.ref.WeakReference;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34
35 import org.apache.commons.lang3.SystemProperties;
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.junit.jupiter.api.AfterEach;
39 import org.junit.jupiter.api.Assumptions;
40 import org.junit.jupiter.api.BeforeEach;
41 import org.junit.jupiter.api.Test;
42
43
44
45
46 @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
47 class ClassCreatorTest extends JexlTestCase {
48 public static class BigObject {
49 @SuppressWarnings("unused")
50 private final byte[] space = new byte[MEGA];
51 private final int id;
52
53 public BigObject(final int id) {
54 this.id = id;
55 }
56
57 public int getId() {
58 return id;
59 }
60 }
61
62 static final class ClassReference extends WeakReference<Class<?>> {
63 ClassReference(final Class<?> clazz, final ReferenceQueue<Object> queue) {
64 super(clazz, queue);
65 }
66 }
67 public static class ContextualCtor {
68 int value = -1;
69
70 public ContextualCtor(final JexlContext ctxt) {
71 value = (Integer) ctxt.get("value");
72 }
73
74 public ContextualCtor(final JexlContext ctxt, final int v) {
75 value = (Integer) ctxt.get("value") + v;
76 }
77
78 public int getValue() {
79 return value;
80 }
81 }
82
83 static final class InstanceReference extends SoftReference<Object> {
84 InstanceReference(final Object obj, final ReferenceQueue<Object> queue) {
85 super(obj, queue);
86 }
87 }
88
89 public static class NsTest implements JexlContext.NamespaceFunctor {
90 private final String className;
91
92 public NsTest(final String cls) {
93 className = cls;
94 }
95 @Override
96 public Object createFunctor(final JexlContext context) {
97 final JexlEngine jexl = JexlEngine.getThreadEngine();
98 return jexl.newInstance(className, context);
99 }
100
101 }
102
103 public static class TwoCtors {
104 int value;
105
106 public TwoCtors(final int v) {
107 this.value = v;
108 }
109
110 public TwoCtors(final Number x) {
111 this.value = -x.intValue();
112 }
113
114 public int getValue() {
115 return value;
116 }
117 }
118
119 static final Log logger = LogFactory.getLog(JexlTestCase.class);
120
121 static final int LOOPS = 8;
122
123
124 static final int MEGA = 1024 * 1024;
125
126 private File base;
127
128 private JexlEngine jexl;
129
130 public ClassCreatorTest() {
131 super("ClassCreatorTest");
132 }
133
134 private void deleteDirectory(final File dir) {
135 if (dir.isDirectory()) {
136 for (final File file : dir.listFiles()) {
137 if (file.isFile()) {
138 file.delete();
139 }
140 }
141 }
142 dir.delete();
143 }
144
145 void functorTwo(final Object nstest) throws Exception {
146
147 final Map<String, Object> ns = new HashMap<>();
148 ns.put("test", nstest);
149 final JexlEngine jexl2 = new JexlBuilder().namespaces(ns).create();
150 final JexlContext ctxt = new MapContext();
151 ctxt.set("value", 1000);
152
153
154 final ClassCreator cctor = new ClassCreator(jexl, base);
155 cctor.setSeed(2);
156 cctor.setCtorBody("value = (Integer) ctxt.get(\"value\") + 10;");
157 Class<?> foo1 = cctor.createClass(true);
158 assertSame(foo1.getClassLoader(), cctor.getClassLoader());
159 assertEquals("foo2", foo1.getSimpleName());
160 Object result = cctor.newInstance(foo1, ctxt);
161 assertEquals(foo1, result.getClass());
162 jexl2.setClassLoader(cctor.getClassLoader());
163 cctor.clear();
164
165
166 final JexlScript script = jexl2.createScript("test:getValue()");
167 result = script.execute(ctxt, foo1.getName());
168 assertEquals(1010, result);
169
170
171 cctor.setSeed(2);
172 cctor.setCtorBody("value = (Integer) ctxt.get(\"value\") + 99;");
173 final Class<?> foo11 = cctor.createClass(true);
174 assertEquals("foo2", foo1.getSimpleName());
175 assertNotSame(foo11, foo1);
176 foo1 = foo11;
177 result = cctor .newInstance(foo1, ctxt);
178 assertEquals(foo1, result.getClass());
179
180 jexl2.setClassLoader(foo1.getClassLoader());
181 result = script.execute(ctxt, foo1.getName());
182
183 assertEquals(1099, result);
184 }
185
186 @BeforeEach
187 @Override
188 public void setUp() throws Exception {
189 base = new File(SystemProperties.getJavaIoTmpdir(), "jexl" + System.currentTimeMillis());
190 jexl = JEXL;
191
192 }
193
194 @AfterEach
195 @Override
196 public void tearDown() {
197 deleteDirectory(base);
198 }
199
200 @Test
201 void testBasicCtor() {
202 final JexlScript s = jexl.createScript("(c, v)->{ var ct2 = new(c, v); ct2.value; }");
203 Object r = s.execute(null, TwoCtors.class, 10);
204 assertEquals(10, r);
205 r = s.execute(null, TwoCtors.class, 5 + 5);
206 assertEquals(10, r);
207 r = s.execute(null, TwoCtors.class, 10d);
208 assertEquals(-10, r);
209 r = s.execute(null, TwoCtors.class, 100f);
210 assertEquals(-100, r);
211 }
212
213 @Test
214 void testContextualCtor() {
215 final MapContext ctxt = new MapContext();
216 ctxt.set("value", 42);
217 JexlScript s = jexl.createScript("(c)->{ new(c).value }");
218 Object r = s.execute(ctxt, ContextualCtor.class);
219 assertEquals(42, r);
220 s = jexl.createScript("(c, v)->{ new(c, v).value }");
221 r = s.execute(ctxt, ContextualCtor.class, 100);
222 assertEquals(142, r);
223 }
224
225 @Test
226 void testFunctor2Class() throws Exception {
227 functorTwo(new NsTest(ClassCreator.GEN_CLASS + "foo2"));
228 }
229
230 @Test
231 void testFunctor2Name() throws Exception {
232 functorTwo(ClassCreator.GEN_CLASS + "foo2");
233 }
234
235 @Test
236 void testFunctorOne() throws Exception {
237 final JexlContext ctxt = new MapContext();
238 ctxt.set("value", 1000);
239
240
241
242 final ClassCreator cctor = new ClassCreator(jexl, base);
243 cctor.setSeed(1);
244 cctor.setCtorBody("value = (Integer) ctxt.get(\"value\") + 10;");
245 Class<?> foo1 = cctor.createClass(true);
246 assertSame(foo1.getClassLoader(), cctor.getClassLoader());
247 assertEquals("foo1", foo1.getSimpleName());
248 Object result = cctor.newInstance(foo1, ctxt);
249 assertEquals(foo1, result.getClass());
250 jexl.setClassLoader(cctor.getClassLoader());
251 cctor.clear();
252
253
254 final JexlScript script = jexl.createScript("(c)->{ new(c).value; }");
255 result = script.execute(ctxt, foo1);
256 assertEquals(1010, result);
257 result = script.execute(ctxt, foo1.getName());
258 assertEquals(1010, result);
259
260
261 cctor.setSeed(1);
262 cctor.setCtorBody("value = (Integer) ctxt.get(\"value\") + 99;");
263 final Class<?> foo11 = cctor.createClass(true);
264 assertEquals("foo1", foo1.getSimpleName());
265 assertNotSame(foo11, foo1);
266 foo1 = foo11;
267 result = cctor.newInstance(foo1, ctxt);
268 assertEquals(foo1, result.getClass());
269
270 jexl.setClassLoader(foo1.getClassLoader());
271 result = script.execute(ctxt, foo1.getName());
272
273 assertEquals(1099, result);
274 result = script.execute(ctxt, foo1);
275 assertEquals(1099, result);
276 }
277
278 @Test
279 void testFunctorThree() throws Exception {
280 final JexlContext ctxt = new MapContext();
281 ctxt.set("value", 1000);
282
283 final ClassCreator cctor = new ClassCreator(jexl, base);
284 cctor.setSeed(2);
285 cctor.setCtorBody("value = (Integer) ctxt.get(\"value\") + 10;");
286 Class<?> foo1 = cctor.createClass(true);
287 assertSame(foo1.getClassLoader(), cctor.getClassLoader());
288 assertEquals("foo2", foo1.getSimpleName());
289 Object result = cctor.newInstance(foo1, ctxt);
290 assertEquals(foo1, result.getClass());
291 jexl.setClassLoader(cctor.getClassLoader());
292 cctor.clear();
293
294 final Map<String, Object> ns = new HashMap<>();
295 ns.put("test", foo1);
296 final JexlEngine jexl2 = new JexlBuilder().namespaces(ns).create();
297
298 final JexlScript script = jexl2.createScript("test:getValue()");
299 result = script.execute(ctxt, foo1.getName());
300 assertEquals(1010, result);
301
302 cctor.setSeed(2);
303 cctor.setCtorBody("value = (Integer) ctxt.get(\"value\") + 99;");
304 final Class<?> foo11 = cctor.createClass(true);
305 assertEquals("foo2", foo1.getSimpleName());
306 assertNotSame(foo11, foo1);
307 foo1 = foo11;
308 result = cctor.newInstance(foo1, ctxt);
309 assertEquals(foo1, result.getClass());
310
311 jexl2.setClassLoader(foo1.getClassLoader());
312 result = script.execute(ctxt, foo1.getName());
313
314 assertEquals(1099, result);
315 }
316
317 @Test
318 void testMany() throws Exception {
319
320 Assumptions.assumeTrue(featureVersion() < 28, "testMany is known to fail on Java 28+");
321
322 if (!ClassCreator.canRun) {
323 return;
324 }
325 int pass = 0;
326 int gced = -1;
327 final ReferenceQueue<Object> queue = new ReferenceQueue<>();
328 final List<Reference<?>> stuff = new ArrayList<>();
329
330
331 final JexlExpression expr = jexl.createExpression("foo.value");
332 final JexlExpression newx = jexl.createExpression("foo = new(clazz)");
333 final JexlEvalContext context = new JexlEvalContext();
334 final JexlOptions options = context.getEngineOptions();
335 options.setStrict(false);
336 options.setSilent(true);
337
338 final ClassCreator cctor = new ClassCreator(jexl, base);
339 for (int i = 0; i < LOOPS && gced < 0; ++i) {
340 cctor.setSeed(i);
341 Class<?> clazz;
342 if (pass == 0) {
343 clazz = cctor.createClass();
344 } else {
345 clazz = cctor.getClassInstance();
346 if (clazz == null) {
347 assertEquals(i, gced);
348 break;
349 }
350 }
351
352
353
354
355
356 context.set("clazz", cctor.getClassName());
357 context.set("foo", null);
358 Object z = newx.evaluate(context);
359 assertNull(z);
360
361 context.set("clazz", clazz);
362 z = newx.evaluate(context);
363 assertNotNull(z, clazz + ": class " + i + " could not be instantiated on pass " + pass);
364 assertEquals(Integer.valueOf(i), expr.evaluate(context));
365
366 jexl.setClassLoader(cctor.getClassLoader());
367 z = newx.evaluate(context);
368 assertEquals(z.getClass(), clazz);
369 assertEquals(Integer.valueOf(i), expr.evaluate(context));
370 cctor.clear();
371 jexl.setClassLoader(null);
372
373
374 if (pass == 0) {
375
376 stuff.add(new ClassReference(clazz, queue));
377
378 stuff.add(new InstanceReference(clazz.getConstructor().newInstance(), queue));
379
380
381
382 for (int b = 0; b < 1024 && Runtime.getRuntime().freeMemory() > MEGA; ++b) {
383 final BigObject big = new BigObject(b);
384 stuff.add(new InstanceReference(big, queue));
385 }
386
387 System.gc();
388
389 boolean qr = false;
390 while (queue.poll() != null) {
391 final Reference<?> ref = queue.remove(1);
392 if (ref instanceof ClassReference) {
393 gced = i;
394 qr = true;
395 }
396 }
397 if (qr) {
398
399 pass = 1;
400 i = 0;
401 }
402 }
403 }
404
405 if (gced < 0) {
406 logger.warn("unable to force GC");
407
408 }
409 }
410
411 @Test
412 void testOne() throws Exception {
413
414 if (!ClassCreator.canRun) {
415 logger.warn("unable to create classes");
416 return;
417 }
418 final ClassCreator cctor = new ClassCreator(jexl, base);
419 cctor.setSeed(1);
420 final Class<?> foo1 = cctor.createClass();
421 assertEquals("foo1", foo1.getSimpleName());
422 cctor.clear();
423 }
424 @Test
425 void test432() throws Exception {
426 final ClassCreator cctor = new ClassCreator(jexl, base);
427 cctor.setSeed(2);
428 cctor.setCtorBody("value = (Integer) ctxt.get(\"value\") + 10;");
429 Class<?> foo1 = cctor.createClass(true);
430 assertSame(foo1.getClassLoader(), cctor.getClassLoader());
431 assertEquals("foo2", foo1.getSimpleName());
432 final Map<String, Object> ns = new HashMap<>();
433 ns.put("test", foo1.getName());
434
435 final JexlEngine jexl2 = new JexlBuilder().namespaces(ns).cache(16).create();
436 jexl2.setClassLoader(cctor.getClassLoader());
437 cctor.clear();
438 final JexlContext ctxt = new MapContext();
439 ctxt.set("value", 1000);
440 final JexlScript script = jexl2.createScript("test:getValue()");
441 Object result = script.execute(ctxt);
442 assertEquals(1010, result); cctor.setSeed(2);
443 cctor.setCtorBody("value = (Integer) ctxt.get(\"value\") + 99;");
444 final Class<?> foo11 = cctor.createClass(true);
445 assertEquals("foo2", foo1.getSimpleName());
446 assertNotSame(foo11, foo1);
447 foo1 = foo11;
448
449 jexl2.setClassLoader(foo1.getClassLoader());
450 result = script.execute(ctxt);
451
452 assertEquals(1099, result);
453 }
454 }