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