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 org.apache.commons.jexl3.internal.Engine;
20  import org.apache.commons.jexl3.internal.introspection.Uberspect;
21  
22  import java.util.HashMap;
23  import java.util.Map;
24  import org.junit.Assert;
25  import org.junit.Before;
26  import org.junit.Test;
27  //import org.apache.commons.beanutils.LazyDynaMap;
28  
29  /**
30   * Test cases for reported issue between JEXL-1 and JEXL-100.
31   */
32  @SuppressWarnings({"boxing", "UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
33  public class IssuesTest extends JexlTestCase {
34      public IssuesTest() {
35          super("IssuesTest", null);
36      }
37  
38      @Before
39      @Override
40      public void setUp() throws Exception {
41          // ensure jul logging is only error to avoid warning in silent mode
42          java.util.logging.Logger.getLogger(JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
43      }
44  
45      // JEXL-49: blocks not parsed (fixed)
46      @Test
47      public void test49() throws Exception {
48          final JexlEngine jexl = new Engine();
49          final Map<String, Object> vars = new HashMap<>();
50          final JexlContext ctxt = new MapContext(vars);
51          final String stmt = "a = 'b'; c = 'd';";
52          final JexlScript expr = jexl.createScript(stmt);
53          /* Object value = */ expr.execute(ctxt);
54          Assert.assertTrue("JEXL-49 is not fixed", vars.get("a").equals("b") && vars.get("c").equals("d"));
55      }
56  
57      // JEXL-48: bad assignment detection
58      public static class Another {
59          public String name = "whatever";
60          private final Boolean foo = Boolean.TRUE;
61  
62          public Boolean foo() {
63              return foo;
64          }
65  
66          public int goo() {
67              return 100;
68          }
69      }
70  
71      public static class Foo {
72          private final Another inner;
73  
74          Foo() {
75              inner = new Another();
76          }
77  
78          public Another getInner() {
79              return inner;
80          }
81      }
82  
83      @Test
84      public void test48() throws Exception {
85          final JexlEngine jexl = new Engine();
86          final JexlEvalContext jc = new JexlEvalContext();
87          final JexlOptions options = jc.getEngineOptions();
88          // ensure errors will throw
89          options.setStrict(true);
90          options.setSilent(false);
91          try {
92              final String jexlExp = "(foo.getInner().foo() eq true) and (foo.getInner().goo() = (foo.getInner().goo()+1-1))";
93              final JexlExpression e = jexl.createExpression(jexlExp);
94              jc.set("foo", new Foo());
95              /* Object o = */ e.evaluate(jc);
96              Assert.fail("Should have failed due to invalid assignment");
97          } catch (final JexlException.Assignment xparse) {
98              final String dbg = xparse.toString();
99          } catch (final JexlException xjexl) {
100             Assert.fail("Should have thrown a parse exception");
101         }
102     }
103 
104     // JEXL-47: C style comments (single & multi line) (fixed in Parser.jjt)
105     // JEXL-44: comments don't allow double quotes (fixed in Parser.jjt)
106     @Test
107     public void test47() throws Exception {
108         final JexlEngine jexl = new Engine();
109         final JexlEvalContext ctxt = new JexlEvalContext();
110         final JexlOptions options = ctxt.getEngineOptions();
111         // ensure errors will throw
112         options.setSilent(false);
113 
114         JexlExpression expr = jexl.createExpression("true//false\n");
115         Object value = expr.evaluate(ctxt);
116         Assert.assertTrue("should be true", (Boolean) value);
117 
118         expr = jexl.createExpression("/*true*/false");
119         value = expr.evaluate(ctxt);
120         Assert.assertFalse("should be false", (Boolean) value);
121 
122         expr = jexl.createExpression("/*\"true\"*/false");
123         value = expr.evaluate(ctxt);
124         Assert.assertFalse("should be false", (Boolean) value);
125     }
126 
127     // JEXL-42: NullPointerException evaluating an expression
128     // fixed in JexlArithmetic by allowing add operator to deal with string, null
129     @Test
130     public void test42() throws Exception {
131         final JexlEngine jexl = new JexlBuilder().create();
132         final JxltEngine uel = jexl.createJxltEngine();
133         // ensure errors will throw
134         //jexl.setSilent(false);
135         final JexlEvalContext ctxt = new JexlEvalContext();
136         final JexlOptions options = ctxt.getEngineOptions();
137         options.set(jexl);
138         options.setStrict(false);
139         options.setStrictArithmetic(false);
140         ctxt.set("ax", "ok");
141 
142         final JxltEngine.Expression expr = uel.createExpression("${ax+(bx)}");
143         final Object value = expr.evaluate(ctxt);
144         Assert.assertEquals("should be ok", "ok", value);
145     }
146 
147     // JEXL-40: failed to discover all methods (non public class implements public method)
148     // fixed in ClassMap by taking newer version of populateCache from Velocity
149     public static abstract class Base {
150         public abstract boolean foo();
151     }
152 
153     static class Derived extends Base {
154         @Override
155         public boolean foo() {
156             return true;
157         }
158     }
159 
160     @Test
161     public void test40() throws Exception {
162         final JexlEngine jexl = new Engine();
163         final JexlEvalContext ctxt = new JexlEvalContext();
164         final JexlOptions options = ctxt.getEngineOptions();
165         options.set(jexl);
166         // ensure errors will throw
167         options.setSilent(false);
168 
169         ctxt.set("derived", new Derived());
170 
171         final JexlExpression expr = jexl.createExpression("derived.foo()");
172         final Object value = expr.evaluate(ctxt);
173         Assert.assertTrue("should be true", (Boolean) value);
174     }
175 
176     // JEXL-52: can be implemented by deriving Interpreter.{g,s}etAttribute; later
177     @Test
178     public void test52base() throws Exception {
179         final Engine jexl = (Engine) createEngine(false);
180         final Uberspect uber = (Uberspect) jexl.getUberspect();
181         // most likely, call will be in an Interpreter, getUberspect
182         String[] names = uber.getMethodNames(Another.class);
183         Assert.assertTrue("should find methods", names.length > 0);
184         int found = 0;
185         for (final String name : names) {
186             if ("foo".equals(name) || "goo".equals(name)) {
187                 found += 1;
188             }
189         }
190         Assert.assertEquals("should have foo & goo", 2, found);
191 
192         names = uber.getFieldNames(Another.class);
193         Assert.assertTrue("should find fields", names.length > 0);
194         found = 0;
195         for (final String name : names) {
196             if ("name".equals(name)) {
197                 found += 1;
198             }
199         }
200         Assert.assertEquals("should have name", 1, found);
201     }
202 
203     // JEXL-10/JEXL-11: variable checking, null operand is error
204     @Test
205     public void test11() throws Exception {
206         final JexlEngine jexl = createEngine(false);
207         final JexlEvalContext ctxt = new JexlEvalContext();
208         final JexlOptions options = ctxt.getEngineOptions();
209         // ensure errors will throw
210         options.setSilent(false);
211         options.setStrict(true);
212 
213         ctxt.set("a", null);
214 
215         final String[] exprs = {
216             //"10 + null",
217             //"a - 10",
218             //"b * 10",
219             "a % b"//,
220         //"1000 / a"
221         };
222         for (final String s : exprs) {
223             try {
224                 final JexlExpression expr = jexl.createExpression(s);
225                 /* Object value = */
226                 expr.evaluate(ctxt);
227                 Assert.fail(s + " : should have failed due to null argument");
228             } catch (final JexlException xjexl) {
229                 // expected
230             }
231         }
232     }
233 
234     // JEXL-62
235     @Test
236     public void test62() throws Exception {
237         final JexlEngine jexl = createEngine(false);
238         final MapContext vars = new MapContext();
239         final JexlEvalContext ctxt = new JexlEvalContext(vars);
240         final JexlOptions options = ctxt.getEngineOptions();
241         options.setStrict(true);
242         options.setSilent(true);// to avoid throwing JexlException on null method call
243 
244         JexlScript jscript;
245 
246         jscript = jexl.createScript("dummy.hashCode()");
247         Assert.assertNull(jscript.getSourceText(), jscript.execute(ctxt)); // OK
248 
249         ctxt.set("dummy", "abcd");
250         Assert.assertEquals(jscript.getSourceText(), Integer.valueOf("abcd".hashCode()), jscript.execute(ctxt)); // OK
251 
252         jscript = jexl.createScript("dummy.hashCode");
253         Assert.assertNull(jscript.getSourceText(), jscript.execute(ctxt)); // OK
254 
255         JexlExpression jexpr;
256         vars.clear();
257         jexpr = jexl.createExpression("dummy.hashCode()");
258         Assert.assertNull(jexpr.toString(), jexpr.evaluate(ctxt)); // OK
259 
260         ctxt.set("dummy", "abcd");
261         Assert.assertEquals(jexpr.toString(), Integer.valueOf("abcd".hashCode()), jexpr.evaluate(ctxt)); // OK
262 
263         jexpr = jexl.createExpression("dummy.hashCode");
264         Assert.assertNull(jexpr.toString(), jexpr.evaluate(ctxt)); // OK
265     }
266 
267     // JEXL-87
268     @Test
269     public void test87() throws Exception {
270         final JexlEngine jexl = createEngine(false);
271         final JexlEvalContext ctxt = new JexlEvalContext();
272         final JexlOptions options = ctxt.getEngineOptions();
273         // ensure errors will throw
274         options.setSilent(false);
275         final JexlExpression divide = jexl.createExpression("l / r");
276         final JexlExpression modulo = jexl.createExpression("l % r");
277 
278         ctxt.set("l", java.math.BigInteger.valueOf(7));
279         ctxt.set("r", java.math.BigInteger.valueOf(2));
280         Assert.assertEquals(java.math.BigInteger.valueOf(3), divide.evaluate(ctxt));
281         Assert.assertTrue(jexl.getArithmetic().equals(1, modulo.evaluate(ctxt)));
282 
283         ctxt.set("l", java.math.BigDecimal.valueOf(7));
284         ctxt.set("r", java.math.BigDecimal.valueOf(2));
285         Assert.assertEquals(java.math.BigDecimal.valueOf(3.5), divide.evaluate(ctxt));
286         Assert.assertTrue(jexl.getArithmetic().equals(1, modulo.evaluate(ctxt)));
287     }
288 
289     // JEXL-90
290     @Test
291     public void test90() throws Exception {
292         final JexlEngine jexl = createEngine(false);
293         final JexlEvalContext ctxt = new JexlEvalContext();
294         final JexlOptions options = ctxt.getEngineOptions();
295         // ensure errors will throw
296         options.setSilent(false);
297         // ';' is necessary between expressions
298         final String[] fexprs = {
299             "a=3 b=4",
300             "while(a) while(a)",
301             "1 2",
302             "if (true) 2; 3 {}",
303             "while (x) 1 if (y) 2 3"
304         };
305         for (final String fexpr : fexprs) {
306             try {
307                 jexl.createScript(fexpr);
308                 Assert.fail(fexpr + ": Should have failed in parse");
309             } catch (final JexlException xany) {
310                 // expected to fail in createExpression
311             }
312         }
313         // ';' is necessary between expressions and only expressions
314         final String[] exprs = {
315             "if (x) {1} if (y) {2}",
316             "if (x) 1 if (y) 2",
317             "while (x) 1 if (y) 2 else 3",
318             "for(z : [3, 4, 5]) { z } y ? 2 : 1",
319             "for(z : [3, 4, 5]) { z } if (y) 2 else 1"
320         };
321         ctxt.set("x", Boolean.FALSE);
322         ctxt.set("y", Boolean.TRUE);
323         for (final String expr : exprs) {
324             final JexlScript s = jexl.createScript(expr);
325             Assert.assertEquals(Integer.valueOf(2), s.execute(ctxt));
326         }
327         debuggerCheck(jexl);
328     }
329 
330     // JEXL-44
331     @Test
332     public void test44() throws Exception {
333         final JexlEngine jexl = createEngine(false);
334         final JexlEvalContext ctxt = new JexlEvalContext();
335         final JexlOptions options = ctxt.getEngineOptions();
336         // ensure errors will throw
337         options.setSilent(false);
338         JexlScript script;
339         script = jexl.createScript("'hello world!'//commented");
340         Assert.assertEquals("hello world!", script.execute(ctxt));
341         script = jexl.createScript("'hello world!';//commented\n'bye...'");
342         Assert.assertEquals("bye...", script.execute(ctxt));
343         script = jexl.createScript("'hello world!'## commented");
344         Assert.assertEquals("hello world!", script.execute(ctxt));
345         script = jexl.createScript("'hello world!';## commented\n'bye...'");
346         Assert.assertEquals("bye...", script.execute(ctxt));
347     }
348 
349     @Test
350     public void test97() throws Exception {
351         final JexlEngine jexl = createEngine(false);
352         final JexlEvalContext ctxt = new JexlEvalContext();
353         final JexlOptions options = ctxt.getEngineOptions();
354         // ensure errors will throw
355         options.setSilent(false);
356         for (char v = 'a'; v <= 'z'; ++v) {
357             ctxt.set(Character.toString(v), 10);
358         }
359         final String input
360                 = "(((((((((((((((((((((((((z+y)/x)*w)-v)*u)/t)-s)*r)/q)+p)-o)*n)-m)+l)*k)+j)/i)+h)*g)+f)/e)+d)-c)/b)+a)";
361 
362         JexlExpression script;
363         // Make sure everything is loaded...
364         final long start = System.nanoTime();
365         script = jexl.createExpression(input);
366         final Object value = script.evaluate(ctxt);
367         Assert.assertEquals(Integer.valueOf(11), value);
368         final long end = System.nanoTime();
369         final double millisec = (end - start) / 1e6;
370         final double limit = 200.0; // Allow plenty of slack
371         Assert.assertTrue("Expected parse to take less than " + limit + "ms, actual " + millisec, millisec < limit);
372     }
373 
374     public static class fn98 {
375         public String replace(final String str, final String target, final String replacement) {
376             return str.replace(target, replacement);
377         }
378     }
379 
380     @Test
381     public void test98() throws Exception {
382         final String[] exprs = {
383             "fn:replace('DOMAIN\\somename', '\\\\', '\\\\\\\\')",
384             "fn:replace(\"DOMAIN\\somename\", \"\\\\\", \"\\\\\\\\\")",
385             "fn:replace('DOMAIN\\somename', '\\u005c', '\\u005c\\u005c')"
386         };
387         final Map<String, Object> funcs = new HashMap<>();
388         funcs.put("fn", new fn98());
389         final JexlEngine jexl = new JexlBuilder().namespaces(funcs).create();
390         for (final String expr : exprs) {
391             final Object value = jexl.createExpression(expr).evaluate(null);
392             Assert.assertEquals(expr, "DOMAIN\\\\somename", value);
393         }
394     }
395 
396 }