View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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.assertNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.util.HashMap;
26  import java.util.Map;
27  
28  import org.apache.commons.jexl3.internal.Debugger;
29  import org.apache.commons.jexl3.internal.introspection.IndexedType;
30  import org.apache.commons.jexl3.internal.introspection.Uberspect;
31  import org.apache.commons.jexl3.introspection.JexlPermissions;
32  import org.apache.commons.jexl3.junit.Asserter;
33  import org.junit.jupiter.api.BeforeEach;
34  import org.junit.jupiter.api.Test;
35  
36  /**
37   * Tests for property access operator '.'
38   */
39  @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
40  class PropertyAccessTest extends JexlTestCase {
41  
42      public static class Container extends PropertyContainer {
43          public Container(final String name, final int number) {
44              super(name, number);
45          }
46  
47          public Object getProperty(final int ref) {
48              switch (ref) {
49                  case 0:
50                      return value0;
51                  case 1:
52                      return value1;
53                  default:
54                      return null;
55              }
56          }
57  
58          public void setProperty(final int ref, final int value) {
59              if (1 == ref) {
60                  this.value1 = value;
61              }
62          }
63  
64          public void setProperty(final int ref, final String value) {
65              if (0 == ref) {
66                  this.value0 = value;
67              }
68          }
69  
70          public void setProperty(final String name, final int value) {
71              if ("number".equals(name)) {
72                  this.value1 = value;
73              }
74          }
75  
76          @Override
77          public void setProperty(final String name, final String value) {
78              if ("name".equals(name)) {
79                  this.value0 = value;
80              }
81          }
82      }
83  
84      public static class Prompt {
85          private final Map<String, PromptValue> values = new HashMap<>();
86  
87          public Object get(final String name) {
88              final PromptValue v = values.get(name);
89              return v != null ? v.getValue() : null;
90          }
91  
92          public void set(final String name, final Object value) {
93              values.put(name, new PromptValue(value));
94          }
95      }
96  
97      /**
98       * A valued prompt.
99       */
100     public static class PromptValue {
101 
102         /** Prompt value. */
103         private Object value;
104 
105         public PromptValue(final Object v) {
106            value = v;
107         }
108 
109         public Object getValue() {
110             return value;
111         }
112 
113         public void setValue(final Object value) {
114             this.value = value;
115         }
116     }
117 
118     /**
119      * Overloads propertySet.
120      */
121     public static class PropertyArithmetic extends JexlArithmetic {
122         int ncalls;
123 
124         public PropertyArithmetic(final boolean astrict) {
125             super(astrict);
126         }
127 
128         public int getCalls() {
129             return ncalls;
130         }
131 
132         public Object propertySet(final IndexedType.IndexedContainer map, final String key, final Integer value) {
133             if (map.getContainerClass().equals(PropertyContainer.class)
134                 && map.getContainerName().equals("property")) {
135                 try {
136                     map.set(key, value.toString());
137                     ncalls += 1;
138                 } catch (final Exception xany) {
139                     throw new JexlException.Operator(null, key + "." + value.toString(), xany);
140                 }
141                 return null;
142             }
143             return JexlEngine.TRY_FAILED;
144         }
145     }
146 
147     /**
148      * A base property container; can only set from string.
149      */
150     public static class PropertyContainer {
151         String value0;
152         int value1;
153 
154         public PropertyContainer(final String name, final int number) {
155             value0 = name;
156             value1 = number;
157         }
158 
159         public Object getProperty(final String name) {
160             if ("name".equals(name)) {
161                 return value0;
162             }
163             if ("number".equals(name)) {
164                 return value1;
165             }
166             return null;
167         }
168 
169         public void setProperty(final String name, final String value) {
170             if ("name".equals(name)) {
171                 this.value0 = value.toUpperCase();
172             }
173             if ("number".equals(name)) {
174                 this.value1 = Integer.parseInt(value) + 1000;
175             }
176         }
177     }
178 
179     private Asserter asserter;
180 
181     public PropertyAccessTest() {
182         super("PropertyAccessTest");
183     }
184 
185     @BeforeEach
186     @Override
187     public void setUp() {
188         asserter = new Asserter(JEXL);
189     }
190 
191     @Test
192     void test250() {
193         final MapContext ctx = new MapContext();
194         final HashMap<Object, Object> x = new HashMap<>();
195         x.put(2, "123456789");
196         ctx.set("x", x);
197         // @formatter:off
198         final JexlEngine engine = new JexlBuilder()
199                 .uberspect(new Uberspect(null, null, JexlPermissions.UNRESTRICTED))
200                 .strict(true).silent(false).create();
201         // @formatter:on
202         String stmt = "x.2.class.name";
203         JexlScript script = engine.createScript(stmt);
204         Object result = script.execute(ctx);
205         assertEquals("java.lang.String", result);
206 
207         stmt = "x.3?.class.name";
208         script = engine.createScript(stmt);
209         result = script.execute(ctx);
210         assertNull(result);
211 
212         stmt = "x?.3.class.name";
213         final JexlScript script1 = engine.createScript(stmt);
214         assertThrows(JexlException.class, () -> script1.execute(ctx));
215 
216         stmt = "x?.3?.class.name";
217         script = engine.createScript(stmt);
218         result = script.execute(ctx);
219         assertNull(result);
220 
221         stmt = "y?.3.class.name";
222         script = engine.createScript(stmt);
223         result = script.execute(ctx);
224         assertNull(result);
225 
226         stmt = "x?.y?.z";
227         script = engine.createScript(stmt);
228         result = script.execute(ctx);
229         assertNull(result);
230 
231         stmt = "x? (x.y? (x.y.z ?: null) :null) : null";
232         script = engine.createScript(stmt);
233         result = script.execute(ctx);
234         assertNull(result);
235     }
236 
237     @Test
238     void test275a() {
239         final JexlEngine jexl = new JexlBuilder().strict(true).safe(false).create();
240         final JexlContext ctxt = new MapContext();
241         Object result = null;
242         final Prompt p0 = new Prompt();
243         p0.set("stuff", 42);
244         ctxt.set("$in", p0);
245 
246         // unprotected navigation
247         final JexlScript script0 = jexl.createScript("$in[p].intValue()", "p");
248         assertThrows(JexlException.Property.class, () -> script0.execute(ctxt, "fail"));
249 
250         assertNull(result);
251         result = script0.execute(ctxt, "stuff");
252         assertEquals(42, result);
253 
254         // protected navigation
255         JexlScript script = jexl.createScript("$in[p]?.intValue()", "p");
256         result = script.execute(ctxt, "fail");
257         assertNull(result);
258         result = script.execute(ctxt, "stuff");
259         assertEquals(42, result);
260 
261         // unprotected navigation
262         final JexlScript script1 = jexl.createScript("$in.`${p}`.intValue()", "p");
263         assertThrows(JexlException.Property.class, () -> script1.execute(ctxt, "fail"));
264 
265         result = script.execute(ctxt, "stuff");
266         assertEquals(42, result);
267 
268         // protected navigation
269         script = jexl.createScript("$in.`${p}`?.intValue()", "p");
270         result = script.execute(ctxt, "fail");
271         assertNull(result);
272         result = script.execute(ctxt, "stuff");
273         assertEquals(42, result);
274 
275     }
276 
277     @Test
278     void test275b() {
279         final JexlEngine jexl = new JexlBuilder().strict(true).safe(true).create();
280         final JexlContext ctxt = new MapContext();
281         JexlScript script;
282         final Prompt p0 = new Prompt();
283         p0.set("stuff", 42);
284         ctxt.set("$in", p0);
285 
286         // unprotected navigation
287         script = jexl.createScript("$in[p].intValue()", "p");
288         Object result = script.execute(ctxt, "fail");
289         assertNull(result);
290 
291         result = script.execute(ctxt, "stuff");
292         assertEquals(42, result);
293 
294         // unprotected navigation
295         script = jexl.createScript("$in.`${p}`.intValue()", "p");
296         result = script.execute(ctxt, "fail");
297         assertNull(result);
298         result = script.execute(ctxt, "stuff");
299         assertEquals(42, result);
300 
301         // protected navigation
302         script = jexl.createScript("$in.`${p}`?.intValue()", "p");
303         result = script.execute(ctxt, "fail");
304         assertNull(result);
305         result = script.execute(ctxt, "stuff");
306         assertEquals(42, result);
307     }
308 
309     @Test
310     void testErroneousIdentifier() {
311         final MapContext ctx = new MapContext();
312         final JexlEngine engine = new JexlBuilder().strict(true).silent(false).create();
313 
314         // base succeeds
315         String stmt = "(x)->{ x?.class ?? 'oops' }";
316         JexlScript script = engine.createScript(stmt);
317         Object result = script.execute(ctx, "querty");
318         assertEquals("querty".getClass(), result);
319 
320         // fail with unknown property
321         stmt = "(x)->{ x.class1 ?? 'oops' }";
322         script = engine.createScript(stmt);
323         result = script.execute(ctx, "querty");
324         assertEquals("oops", result);
325 
326         // succeeds with jxlt & strict navigation
327         ctx.set("al", "la");
328         stmt = "(x)->{ x.`c${al}ss` ?? 'oops' }";
329         script = engine.createScript(stmt);
330         result = script.execute(ctx, "querty");
331         assertEquals("querty".getClass(), result);
332 
333         // succeeds with jxlt & lenient navigation
334         stmt = "(x)->{ x?.`c${al}ss` ?? 'oops' }";
335         script = engine.createScript(stmt);
336         result = script.execute(ctx, "querty");
337         assertEquals("querty".getClass(), result);
338 
339         // fails with jxlt & lenient navigation
340         stmt = "(x)->{ x?.`c${la}ss` ?? 'oops' }";
341         script = engine.createScript(stmt);
342         result = script.execute(ctx, "querty");
343         assertEquals("oops", result);
344 
345         // fails with jxlt & strict navigation
346         stmt = "(x)->{ x.`c${la}ss` ?? 'oops' }";
347         script = engine.createScript(stmt);
348         result = script.execute(ctx, "querty");
349         assertEquals("oops", result);
350 
351         // parsing fails with jxlt & lenient navigation
352         stmt = "(x)->{ x?.`c${la--ss` ?? 'oops' }";
353         try {
354             script = engine.createScript(stmt);
355             result = script.execute(ctx, "querty");
356         } catch (final JexlException xany) {
357             assertNotNull(xany.getMessage());
358             assertTrue(xany.getMessage().contains("c${la--ss"));
359         }
360 
361         // parsing fails with jxlt & strict navigation
362         stmt = "(x)->{ x.`c${la--ss` ?? 'oops' }";
363         try {
364         script = engine.createScript(stmt);
365         result = script.execute(ctx, "querty");
366         } catch (final JexlException xany) {
367             assertNotNull(xany.getMessage());
368             assertTrue(xany.getMessage().contains("c${la--ss"));
369         }
370     }
371 
372     @Test
373     void testInnerProperty() {
374         final PropertyArithmetic pa = new PropertyArithmetic(true);
375         final JexlEngine jexl = new JexlBuilder().arithmetic(pa).strict(true).cache(32).create();
376         final Container quux = new Container("quux", 42);
377         final JexlScript get;
378         Object result;
379 
380         final int calls = pa.getCalls();
381         final JexlScript getName = JEXL.createScript("foo.property.name", "foo");
382         result = getName.execute(null, quux);
383         assertEquals("quux", result);
384 
385         final JexlScript get0 = JEXL.createScript("foo.property.0", "foo");
386         result = get0.execute(null, quux);
387         assertEquals("quux", result);
388 
389         final JexlScript getNumber = JEXL.createScript("foo.property.number", "foo");
390         result = getNumber.execute(null, quux);
391         assertEquals(42, result);
392 
393         final JexlScript get1 = JEXL.createScript("foo.property.1", "foo");
394         result = get1.execute(null, quux);
395         assertEquals(42, result);
396 
397         final JexlScript setName = JEXL.createScript("foo.property.name = $0", "foo", "$0");
398         setName.execute(null, quux, "QUUX");
399         result = getName.execute(null, quux);
400         assertEquals("QUUX", result);
401         result = get0.execute(null, quux);
402         assertEquals("QUUX", result);
403 
404         final JexlScript set0 = JEXL.createScript("foo.property.0 = $0", "foo", "$0");
405         set0.execute(null, quux, "BAR");
406         result = getName.execute(null, quux);
407         assertEquals("BAR", result);
408         result = get0.execute(null, quux);
409         assertEquals("BAR", result);
410 
411         final JexlScript setNumber = JEXL.createScript("foo.property.number = $0", "foo", "$0");
412         setNumber.execute(null, quux, -42);
413         result = getNumber.execute(null, quux);
414         assertEquals(-42, result);
415         result = get1.execute(null, quux);
416         assertEquals(-42, result);
417 
418         final JexlScript set1 = JEXL.createScript("foo.property.1 = $0", "foo", "$0");
419         set1.execute(null, quux, 24);
420         result = getNumber.execute(null, quux);
421         assertEquals(24, result);
422         result = get1.execute(null, quux);
423         assertEquals(24, result);
424 
425         assertEquals(calls, pa.getCalls());
426     }
427 
428     @Test
429     void testInnerViaArithmetic() {
430         final PropertyArithmetic pa = new PropertyArithmetic(true);
431         final JexlEngine jexl = new JexlBuilder().arithmetic(pa).strict(true).cache(32).create();
432         final PropertyContainer quux = new PropertyContainer("bar", 169);
433         Object result;
434 
435         final JexlScript getName = jexl.createScript("foo.property.name", "foo");
436         result = getName.execute(null, quux);
437         assertEquals("bar", result);
438         final int calls = pa.getCalls();
439         final JexlScript setName = jexl.createScript("foo.property.name = $0", "foo", "$0");
440         setName.execute(null, quux, 123);
441         result = getName.execute(null, quux);
442         assertEquals("123", result);
443         setName.execute(null, quux, 456);
444         result = getName.execute(null, quux);
445         assertEquals("456", result);
446         assertEquals(calls + 2, pa.getCalls());
447         setName.execute(null, quux, "quux");
448         result = getName.execute(null, quux);
449         assertEquals("QUUX", result);
450         assertEquals(calls + 2, pa.getCalls());
451 
452         final JexlScript getNumber = jexl.createScript("foo.property.number", "foo");
453         result = getNumber.execute(null, quux);
454         assertEquals(169, result);
455         final JexlScript setNumber = jexl.createScript("foo.property.number = $0", "foo", "$0");
456         setNumber.execute(null, quux, 42);
457         result = getNumber.execute(null, quux);
458         assertEquals(1042, result);
459         setNumber.execute(null, quux, 24);
460         result = getNumber.execute(null, quux);
461         assertEquals(1024, result);
462         assertEquals(calls + 4, pa.getCalls());
463         setNumber.execute(null, quux, "42");
464         result = getNumber.execute(null, quux);
465         assertEquals(1042, result);
466         assertEquals(calls + 4, pa.getCalls());
467     }
468 
469     @Test
470     void testPropertyProperty() throws Exception {
471         final Integer i42 = Integer.valueOf(42);
472         final Integer i43 = Integer.valueOf(43);
473         final String s42 = "fourty-two";
474         final Object[] foo = new Object[3];
475         foo[0] = foo;
476         foo[1] = i42;
477         foo[2] = s42;
478         asserter.setVariable("foo", foo);
479         asserter.setVariable("zero", Integer.valueOf(0));
480         asserter.setVariable("one", Integer.valueOf(1));
481         asserter.setVariable("two", Integer.valueOf(2));
482         for (int l = 0; l < 2; ++l) {
483             asserter.assertExpression("foo.0", foo);
484             asserter.assertExpression("foo.0.'0'", foo);
485             asserter.assertExpression("foo.'1'", foo[1]);
486             asserter.assertExpression("foo.0.'1'", foo[1]);
487             asserter.assertExpression("foo.0.'1' = 43", i43);
488             asserter.assertExpression("foo.0.'1'", i43);
489             asserter.assertExpression("foo.0.'1' = 42", i42);
490             //
491             asserter.assertExpression("foo?.0.'1'", i42);
492             asserter.assertExpression("foo?.0", foo);
493             asserter.assertExpression("foo?.0.'0'", foo);
494             asserter.assertExpression("foo?.'1'", foo[1]);
495             asserter.assertExpression("foo.0?.'1'", foo[1]);
496             asserter.assertExpression("foo?.0.'1' = 43", i43);
497             asserter.assertExpression("foo?.0?.'1'", i43);
498             asserter.assertExpression("foo?.0.'1' = 42", i42);
499             asserter.assertExpression("foo?.0.'1'", i42);
500             //
501             asserter.assertExpression("foo?.0.`1`", i42);
502             asserter.assertExpression("foo?.0", foo);
503             asserter.assertExpression("foo?.0.'0'", foo);
504             asserter.assertExpression("foo?.`1`", foo[1]);
505             asserter.assertExpression("foo?.0.`1`", foo[1]);
506             asserter.assertExpression("foo?.0.`${one}` = 43", i43);
507             asserter.assertExpression("foo.0?.`${one}`", i43);
508             asserter.assertExpression("foo.0.`${one}` = 42", i42);
509             asserter.assertExpression("foo?.0?.`${one}`", i42);
510             //
511             asserter.assertExpression("foo?[0].'1'", i42);
512             asserter.assertExpression("foo?[0]", foo);
513             asserter.assertExpression("foo?[0].'0'", foo);
514             asserter.assertExpression("foo?[1]", foo[1]);
515             asserter.assertExpression("foo[0]?.'1'", foo[1]);
516             asserter.assertExpression("foo?[0].'1' = 43", i43);
517             asserter.assertExpression("foo?[0]?.'1'", i43);
518             asserter.assertExpression("foo?[0].'1' = 42", i42);
519             asserter.assertExpression("foo?[0].'1'", i42);
520         }
521     }
522      @Test
523     void testStringIdentifier() {
524         final Map<String, String> foo = new HashMap<>();
525 
526         final JexlContext jc = new MapContext();
527         jc.set("foo", foo);
528         foo.put("q u u x", "456");
529         JexlExpression e = JEXL.createExpression("foo.\"q u u x\"");
530         Object result = e.evaluate(jc);
531         assertEquals("456", result);
532         e = JEXL.createExpression("foo.'q u u x'");
533         result = e.evaluate(jc);
534         assertEquals("456", result);
535         JexlScript s = JEXL.createScript("foo.\"q u u x\"");
536         result = s.execute(jc);
537         assertEquals("456", result);
538         s = JEXL.createScript("foo.'q u u x'");
539         result = s.execute(jc);
540         assertEquals("456", result);
541 
542         final Debugger dbg = new Debugger();
543         dbg.debug(e);
544         final String dbgdata = dbg.toString();
545         assertEquals("foo.'q u u x'", dbgdata);
546     }
547 
548 }