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.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.util.Arrays;
26  import java.util.Date;
27  import java.util.HashMap;
28  import java.util.Map;
29  
30  import org.apache.commons.jexl3.introspection.JexlMethod;
31  import org.apache.commons.jexl3.introspection.JexlPropertyGet;
32  import org.apache.commons.jexl3.introspection.JexlPropertySet;
33  import org.apache.commons.jexl3.introspection.JexlUberspect;
34  import org.apache.commons.jexl3.junit.Asserter;
35  import org.junit.jupiter.api.BeforeEach;
36  import org.junit.jupiter.api.Test;
37  
38  /**
39   * Tests for calling methods on objects
40   */
41  @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
42  class MethodTest extends JexlTestCase {
43      public static class ContextualFunctor {
44          private final EnhancedContext context;
45  
46          public ContextualFunctor(final EnhancedContext theContext) {
47              context = theContext;
48          }
49  
50          public int ratio(final int n) {
51              context.factor -= 1;
52              return n / context.factor;
53          }
54      }
55      public static class Edge {
56          private Edge() {
57          }
58  
59          public int exec(final Boolean x, final int arg) {
60              return 1;
61          }
62  
63          public int exec(final Boolean x, final int[] arg) {
64              return 20;
65          }
66  
67          public int exec(final Boolean x, final Object args) {
68              return 3;
69          }
70  
71          public int exec(final Boolean x, final Object... args) {
72              return 4;
73          }
74  
75          public int exec(final Boolean x, final String arg) {
76              return 2;
77          }
78  
79          public int exec(final int arg) {
80              return 1;
81          }
82  
83          public int exec(final int[] arg) {
84              return 20;
85          }
86  
87          public int exec(final Object args) {
88              return 3;
89          }
90  
91          public int exec(final Object... args) {
92              return 4;
93          }
94  
95          public int exec(final String arg) {
96              return 2;
97          }
98  
99          public int exec(final String... arg) {
100             return 200;
101         }
102 
103         public Class<?>[] execute(final Object... args) {
104             final Class<?>[] clazz = new Class<?>[args.length];
105             for (int a = 0; a < args.length; ++a) {
106                 clazz[a] = args[a] != null ? args[a].getClass() : Void.class;
107             }
108             return clazz;
109         }
110     }
111 
112     public static class EnhancedContext extends JexlEvalContext {
113         int factor = 6;
114         final Map<String, Object> funcs;
115 
116         EnhancedContext(final Map<String, Object> funcs) {
117             this.funcs = funcs;
118         }
119 
120         @Override
121         public Object resolveNamespace(final String name) {
122             return funcs.get(name);
123         }
124     }
125 
126     public static class Functor {
127         public static Class<?> NPEIfNull(final Object x) {
128             return x.getClass();
129         }
130         public static int PLUS20(final int num) {
131             return num + 20;
132         }
133 
134         public static int TWENTY() {
135             return 20;
136         }
137 
138         private boolean overKill;
139 
140         private String under;
141 
142         public String getUnder() {
143             if (overKill) {
144                 throw new UnsupportedOperationException("kill " + under);
145             }
146             return under;
147         }
148 
149         public Object over(final String f, final Date g) {
150             return f + " + " + g;
151         }
152 
153         public Object over(final String f, final int i) {
154             if (overKill) {
155                 throw new UnsupportedOperationException("kill " + f + " + " + i);
156             }
157             return f + " + " + i;
158         }
159 
160         public Object over(final String f, final String g) {
161             return f + " + " + g;
162         }
163 
164         public int plus10(final int num) {
165             return num + 10;
166         }
167 
168         void setKill(final boolean ok) {
169             overKill = ok;
170         }
171 
172         public void setUnder(final String str) {
173             if (overKill) {
174                 throw new UnsupportedOperationException("kill " + str);
175             }
176             under = str;
177         }
178 
179         public int ten() {
180             return 10;
181         }
182     }
183 
184     public static class FunctorOver extends Functor {
185 
186         public Object over(final Object f, final Object g) {
187             return f + " + " + g;
188         }
189     }
190 
191     public static class MyMath {
192         public double cos(final double x) {
193             return Math.cos(x);
194         }
195     }
196 
197     public static class ScriptContext extends MapContext implements JexlContext.NamespaceResolver {
198         Map<String, Object> nsScript;
199 
200         ScriptContext(final Map<String, Object> ns) {
201             nsScript = ns;
202         }
203 
204         @Override
205         public Object resolveNamespace(final String name) {
206             if (name == null) {
207                 return this;
208             }
209             if ("script".equals(name)) {
210                 return nsScript;
211             }
212             if ("functor".equals(name)) {
213                 return (NamespaceFunctor) context -> {
214                     final Map<String, Object> values = new HashMap<>();
215                     if ("gin".equals(context.get("base"))) {
216                         values.put("drink", "gin fizz");
217                     } else {
218                         values.put("drink", "champaign");
219                     }
220                     return values;
221                 };
222             }
223             return null;
224         }
225     }
226 
227     public static class VarArgs {
228         public String callInts() {
229             final int result = -5000;
230             return "Varargs:" + result;
231         }
232 
233         public String callInts(final Integer... args) {
234             int result = 0;
235             if (args != null) {
236                 for (final Integer arg : args) {
237                     result += arg != null ? arg : -100;
238                 }
239             } else {
240                 result = -1000;
241             }
242             return "Varargs:" + result;
243         }
244 
245         public String callMixed(final Integer fixed, final Integer... args) {
246             int result = fixed;
247             if (args != null) {
248                 for (final Integer arg : args) {
249                     result += arg != null ? arg : -100;
250                 }
251             } else {
252                 result -= 1000;
253             }
254             return "Mixed:" + result;
255         }
256 
257         public String callMixed(final String mixed, final Integer... args) {
258             int result = 0;
259             if (args != null) {
260                 for (final Integer arg : args) {
261                     result += arg != null ? arg : -100;
262                 }
263             } else {
264                 result = -1000;
265             }
266             return mixed + ":" + result;
267         }
268 
269         public String concat(final String... strs) {
270             if (strs.length <= 0) {
271                 return "";
272             }
273             final StringBuilder strb = new StringBuilder(strs[0]);
274             for (int s = 1; s < strs.length; ++s) {
275                 strb.append(", ");
276                 strb.append(strs[s]);
277             }
278             return strb.toString();
279         }
280     }
281 
282     public static class ZArithmetic extends JexlArithmetic {
283         public ZArithmetic(final boolean astrict) {
284             super(astrict);
285         }
286 
287         public int zzzz(final int z) {
288             return 38 + z;
289         }
290     }
291 
292     public static class ZContext extends MapContext {
293         public ZContext(final Map<String,Object> map) {
294             super(map);
295         }
296 
297         public int z(final int z) {
298             return 181 + z;
299         }
300 
301         public int zz(final int z) {
302             return 40 + z;
303         }
304     }
305 
306     public static class ZSpace {
307         public int zzz(final int z) {
308             return 39 + z;
309         }
310     }
311 
312     private static final String METHOD_STRING = "Method string";
313 
314     private Asserter asserter;
315 
316     public MethodTest() {
317         super("MethodTest");
318     }
319 
320     private boolean eqExecute(final Object lhs, final Object rhs) {
321         if (lhs instanceof Class<?>[] && rhs instanceof Class<?>[]) {
322             final Class<?>[] lhsa = (Class<?>[]) lhs;
323             final Class<?>[] rhsa = (Class<?>[]) rhs;
324             return Arrays.deepEquals(lhsa, rhsa);
325         }
326         return false;
327     }
328 
329     @BeforeEach
330     @Override
331     public void setUp() {
332         asserter = new Asserter(JEXL);
333     }
334 
335     @Test
336     void testAmbiguousInvoke() throws Exception {
337         // JEXL-299
338         final Functor func = new Functor();
339         final JexlContext ctxt = new MapContext();
340         ctxt.set("func", func);
341         Object result;
342         // basic call works
343         result = JEXL.invokeMethod(func, "over", "foo", 42);
344         assertEquals("foo + 42", result);
345         // ambiguous call fails
346         JexlException.Method xinvoke = assertThrows(JexlException.Method.class, () -> JEXL.invokeMethod(func, "over", "not null", null));
347         assertEquals("over(String, Object)", xinvoke.getMethodSignature());
348 
349         // another ambiguous call fails
350         final String[] arg2 = { "more", "than", "one" };
351         xinvoke = assertThrows(JexlException.Method.class, () -> JEXL.invokeMethod(func, "over", "not null", arg2));
352         assertEquals("over(String, String[])", xinvoke.getMethodSignature());
353     }
354 
355     @Test
356     void testCallJexlVarArgMethod() throws Exception {
357         final VarArgs test = new VarArgs();
358         asserter.setVariable("test", test);
359         assertEquals("jexl:0", test.callMixed("jexl"));
360         asserter.assertExpression("test.callMixed('jexl')", "jexl:0");
361         // Java and JEXL equivalent behavior: 'jexl:-1000' expected
362         //{
363         assertEquals("jexl:-1000", test.callMixed("jexl", (Integer[]) null));
364         asserter.assertExpression("test.callMixed('jexl', null)", "jexl:-1000");
365         //}
366         asserter.assertExpression("test.callMixed('jexl', 2)", test.callMixed("jexl", 2));
367         asserter.assertExpression("test.callMixed('jexl',2,3,4,5)", test.callMixed("jexl", 2, 3, 4, 5));
368     }
369 
370     @Test
371     void testCallMixedVarArgMethod() throws Exception {
372         final VarArgs test = new VarArgs();
373         asserter.setVariable("test", test);
374         assertEquals("Mixed:1", test.callMixed(Integer.valueOf(1)));
375         asserter.assertExpression("test.callMixed(1)", test.callMixed(1));
376         // Java and JEXL equivalent behavior: 'Mixed:-999' expected
377         //{
378         assertEquals("Mixed:-999", test.callMixed(Integer.valueOf(1), (Integer[]) null));
379         asserter.assertExpression("test.callMixed(1, null)", "Mixed:-999");
380         //}
381         asserter.assertExpression("test.callMixed(1,2)", test.callMixed(1, 2));
382         asserter.assertExpression("test.callMixed(1,2,3,4,5)", test.callMixed(1, 2, 3, 4, 5));
383     }
384 
385     @Test
386     void testCallVarArgMethod() throws Exception {
387         final VarArgs test = new VarArgs();
388         asserter.setVariable("test", test);
389         asserter.assertExpression("test.callInts()", test.callInts());
390         asserter.assertExpression("test.callInts(1)", test.callInts(1));
391         asserter.assertExpression("test.callInts(1,2,3,4,5)", test.callInts(1, 2, 3, 4, 5));
392         asserter.assertExpression("test.concat(['1', '2', '3'])", test.concat(new String[]{"1", "2", "3"}));
393         asserter.assertExpression("test.concat('1', '2', '3')", test.concat("1", "2", "3"));
394 
395     }
396 
397     @Test
398     void testFizzCall() throws Exception {
399         final ScriptContext context = new ScriptContext(new HashMap<>());
400 
401         JexlScript bar = JEXL.createScript("functor:get('drink')");
402         Object o;
403         o = bar.execute(context);
404         assertEquals("champaign", o, "Wrong choice");
405         context.set("base", "gin");
406         o = bar.execute(context);
407         assertEquals("gin fizz", o, "Wrong choice");
408 
409         // despite being called twice, the functor is created only once.
410         context.set("base", "wine");
411         bar = JEXL.createScript("var glass = functor:get('drink'); base = 'gin'; functor:get('drink')");
412         o = bar.execute(context);
413         assertEquals("champaign", o, "Wrong choice");
414     }
415 
416     @Test
417     void testInvoke() throws Exception {
418         final Functor func = new Functor();
419         assertEquals(Integer.valueOf(10), JEXL.invokeMethod(func, "ten"));
420         assertEquals(Integer.valueOf(42), JEXL.invokeMethod(func, "PLUS20", Integer.valueOf(22)));
421         assertThrows(Exception.class, () -> JEXL.invokeMethod(func, "nonExistentMethod"), "method does not exist!");
422         assertThrows(Exception.class, () -> JEXL.invokeMethod(func, "NPEIfNull", (Object[]) null), "method should have thrown!");
423         Object result = JEXL.invokeMethod(func, "over", "foo", 42);
424         assertEquals("foo + 42", result);
425         assertThrows(Exception.class, () -> JEXL.invokeMethod(func, "over", null, null));
426         final Functor func1 = new FunctorOver();
427         result = JEXL.invokeMethod(func1, "over", null, null);
428         assertEquals("null + null", result);
429     }
430 
431     /**
432      * test a simple method expression
433      */
434     @Test
435     void testMethod() throws Exception {
436         // tests a simple method expression
437         asserter.setVariable("foo", new Foo());
438         asserter.assertExpression("foo.bar()", METHOD_STRING);
439     }
440 
441     @Test
442     void testMulti() throws Exception {
443         asserter.setVariable("foo", new Foo());
444         asserter.assertExpression("foo.innerFoo.bar()", METHOD_STRING);
445     }
446 
447     @Test
448     void testNamespaceCall() throws Exception {
449         final java.util.Map<String, Object> funcs = new java.util.HashMap<>();
450         funcs.put("func", new Functor());
451         funcs.put("FUNC", Functor.class);
452 
453         JexlExpression e = JEXL.createExpression("func:ten()");
454         final JexlEvalContext jc = new EnhancedContext(funcs);
455 
456         Object o = e.evaluate(jc);
457         assertEquals(Integer.valueOf(10), o);
458 
459         e = JEXL.createExpression("func:plus10(10)");
460         o = e.evaluate(jc);
461         assertEquals(Integer.valueOf(20), o);
462 
463         e = JEXL.createExpression("func:plus10(func:ten())");
464         o = e.evaluate(jc);
465         assertEquals(Integer.valueOf(20), o);
466 
467         e = JEXL.createExpression("FUNC:PLUS20(10)");
468         o = e.evaluate(jc);
469         assertEquals(Integer.valueOf(30), o);
470 
471         e = JEXL.createExpression("FUNC:PLUS20(FUNC:TWENTY())");
472         o = e.evaluate(jc);
473         assertEquals(Integer.valueOf(40), o);
474     }
475 
476     @Test
477     void testNamespaceCallEdge() throws Exception {
478         final java.util.Map<String, Object> funcs = new java.util.HashMap<>();
479         final Edge func = new Edge();
480         funcs.put("func", func);
481 
482         Object o;
483         Object c;
484         JexlExpression e;
485         final JexlEvalContext jc = new EnhancedContext(funcs);
486         for (int i = 0; i < 2; ++i) {
487             e = JEXL.createExpression("func:exec([1, 2])");
488             o = e.evaluate(jc);
489             assertEquals(20, o, "exec(int[] arg): " + i);
490 
491             e = JEXL.createExpression("func:exec(1, 2)");
492             o = e.evaluate(jc);
493             assertEquals(4, o, "exec(Object... args): " + i);
494 
495             e = JEXL.createExpression("func:exec([10.0, 20.0])");
496             o = e.evaluate(jc);
497             assertEquals(3, o, "exec(Object args): " + i);
498 
499             e = JEXL.createExpression("func:exec('1', 2)");
500             o = e.evaluate(jc);
501             assertEquals(4, o, "exec(Object... args): " + i);
502 
503             // no way to differentiate between a single arg call with an array and a vararg call with same args
504             assertEquals(func.exec("1", "2"), func.exec(new String[] { "1", "2" }), "exec(String... args): " + i);
505             e = JEXL.createExpression("func:exec(['1', '2'])");
506             o = e.evaluate(jc);
507             assertEquals(func.exec(new String[] { "1", "2" }), o, "exec(String... args): " + i);
508             e = JEXL.createExpression("func:exec('1', '2')");
509             o = e.evaluate(jc);
510             assertEquals(func.exec("1", "2"), o, "exec(String... args): " + i);
511 
512             e = JEXL.createExpression("func:exec(true, [1, 2])");
513             o = e.evaluate(jc);
514             assertEquals(20, o, "exec(int[] arg): " + i);
515 
516             e = JEXL.createExpression("func:exec(true, 1, 2)");
517             o = e.evaluate(jc);
518             assertEquals(4, o, "exec(Object... args): " + i);
519 
520             e = JEXL.createExpression("func:exec(true, ['1', '2'])");
521             o = e.evaluate(jc);
522             assertEquals(3, o, "exec(Object args): " + i);
523 
524             e = JEXL.createExpression("func:exec(true, '1', '2')");
525             o = e.evaluate(jc);
526             assertEquals(4, o, "exec(Object... args): " + i);
527 
528             e = JEXL.createExpression("func:execute(true, '1', '2')");
529             o = e.evaluate(jc);
530             c = func.execute(Boolean.TRUE, "1", "2");
531             assertTrue(eqExecute(o, c), "execute(Object... args): " + i);
532 
533             e = JEXL.createExpression("func:execute([true])");
534             o = e.evaluate(jc);
535             c = func.execute(new boolean[] { true });
536             assertTrue(eqExecute(o, c), "execute(Object... args): " + i);
537         }
538     }
539 
540     @Test
541     void testScriptCall() throws Exception {
542         JexlContext context = new MapContext();
543         final JexlScript plus = JEXL.createScript("a + b", new String[]{"a", "b"});
544         context.set("plus", plus);
545         JexlScript forty2 = JEXL.createScript("plus(4, 2) * plus(4, 3)");
546         Object o = forty2.execute(context);
547         assertEquals(Integer.valueOf(42), o);
548 
549         final Map<String, Object> foo = new HashMap<>();
550         foo.put("plus", plus);
551         context.set("foo", foo);
552         forty2 = JEXL.createScript("foo.plus(4, 2) * foo.plus(4, 3)");
553         o = forty2.execute(context);
554         assertEquals(Integer.valueOf(42), o);
555 
556         context = new ScriptContext(foo);
557         forty2 = JEXL.createScript("script:plus(4, 2) * script:plus(4, 3)");
558         o = forty2.execute(context);
559         assertEquals(Integer.valueOf(42), o);
560 
561         final JexlArithmetic ja = JEXL.getArithmetic();
562         final JexlMethod mplus = new JexlMethod() {
563             @Override
564             public Class<?> getReturnType() {
565                 return Object.class;
566             }
567 
568             @Override
569             public Object invoke(final Object obj, final Object ... params) throws Exception {
570                 if (obj instanceof Map<?, ?>) {
571                     return ja.add(params[0], params[1]);
572                 }
573                 throw new Exception("not a script context");
574             }
575 
576             @Override
577             public boolean isCacheable() {
578                 return true;
579             }
580 
581             @Override
582             public boolean tryFailed(final Object rval) {
583                 // this is the marker for failure
584                 return rval == this;
585             }
586 
587             @Override
588             public Object tryInvoke(final String name, final Object obj, final Object ... params) {
589                 try {
590                     if ("plus".equals(name)) {
591                         return invoke(obj, params);
592                     }
593                 } catch (final Exception xany) {
594                     // ignore and fail by returning this
595                 }
596                 return this;
597             }
598         };
599 
600         foo.put("PLUS", mplus);
601         forty2 = JEXL.createScript("script:PLUS(4, 2) * script:PLUS(4, 3)");
602         o = forty2.execute(context);
603         assertEquals(Integer.valueOf(42), o);
604 
605         context.set("foo.bar", foo);
606         forty2 = JEXL.createScript("foo.'bar'.PLUS(4, 2) * foo.bar.PLUS(4, 3)");
607         o = forty2.execute(context);
608         assertEquals(Integer.valueOf(42), o);
609     }
610 
611     /**
612      * Ensures static methods on objects can be called.
613      */
614     @Test
615     void testStaticMethodInvocation() throws Exception {
616         asserter.setVariable("aBool", Boolean.FALSE);
617         asserter.assertExpression("aBool.valueOf('true')", Boolean.TRUE);
618     }
619 
620     @Test
621     void testStaticMethodInvocationOnClasses() throws Exception {
622         asserter.setVariable("Boolean", Boolean.class);
623         asserter.assertExpression("Boolean.valueOf('true')", Boolean.TRUE);
624     }
625 
626     /**
627      * test some String method calls
628      */
629     @Test
630     void testStringMethods() throws Exception {
631         asserter.setVariable("foo", "abcdef");
632         asserter.assertExpression("foo.substring(3)", "def");
633         asserter.assertExpression("foo.substring(0,(size(foo)-3))", "abc");
634         asserter.assertExpression("foo.substring(0,size(foo)-3)", "abc");
635         asserter.assertExpression("foo.substring(0,foo.length()-3)", "abc");
636         asserter.assertExpression("foo.substring(0, 1+1)", "ab");
637     }
638 
639     @Test
640     void testTopLevelCall() throws Exception {
641         final java.util.Map<String, Object> funcs = new java.util.HashMap<>();
642         funcs.put(null, new Functor());
643         funcs.put("math", new MyMath());
644         funcs.put("cx", ContextualFunctor.class);
645 
646         final EnhancedContext jc = new EnhancedContext(funcs);
647 
648         JexlExpression e = JEXL.createExpression("ten()");
649         Object o = e.evaluate(jc);
650         assertEquals(Integer.valueOf(10), o);
651 
652         e = JEXL.createExpression("plus10(10)");
653         o = e.evaluate(jc);
654         assertEquals(Integer.valueOf(20), o);
655 
656         e = JEXL.createExpression("plus10(ten())");
657         o = e.evaluate(jc);
658         assertEquals(Integer.valueOf(20), o);
659 
660         jc.set("pi", Double.valueOf(Math.PI));
661         e = JEXL.createExpression("math:cos(pi)");
662         o = e.evaluate(jc);
663         assertEquals(Double.valueOf(-1), o);
664 
665         e = JEXL.createExpression("cx:ratio(10) + cx:ratio(20)");
666         o = e.evaluate(jc);
667         assertEquals(Integer.valueOf(7), o);
668     }
669 
670     @Test
671     void testTryFailed() throws Exception {
672         // JEXL-257
673         final Functor func = new Functor();
674         final JexlContext ctxt = new MapContext();
675         ctxt.set("func", func);
676         Object result;
677         final JexlUberspect uber = JEXL.getUberspect();
678         // tryInvoke
679         final JexlMethod method = uber.getMethod(func, "over", "foo", 42);
680         assertNotNull(method);
681         // tryInvoke succeeds
682         result = method.tryInvoke("over", func, "foo", 42);
683         assertEquals("foo + 42", result);
684         // tryInvoke fails
685         func.setKill(true);
686         JexlException.TryFailed xfail = assertThrows(JexlException.TryFailed.class, () -> method.tryInvoke("over", func, "foo", 42));
687         assertEquals(UnsupportedOperationException.class, xfail.getCause().getClass());
688 
689         func.setKill(false);
690         final JexlPropertySet setter = uber.getPropertySet(func, "under", "42");
691         result = setter.tryInvoke(func, "under", "42");
692         assertFalse(setter.tryFailed(result));
693         assertEquals("42", result);
694 
695         final JexlPropertyGet getter = uber.getPropertyGet(func, "under");
696         result = getter.tryInvoke(func, "under");
697         assertFalse(getter.tryFailed(result));
698         assertEquals("42", result);
699 
700         func.setKill(true);
701         xfail = assertThrows(JexlException.TryFailed.class, () -> setter.tryInvoke(func, "under", "42"), "should throw TryFailed");
702         assertEquals(UnsupportedOperationException.class, xfail.getCause().getClass());
703 
704         func.setKill(false);
705         result = setter.tryInvoke(func, "under", "-42");
706         assertEquals("-42", result);
707 
708         func.setKill(true);
709         xfail = assertThrows(JexlException.TryFailed.class, () -> getter.tryInvoke(func, "under"), "should throw TryFailed");
710         assertEquals(UnsupportedOperationException.class, xfail.getCause().getClass());
711 
712         func.setKill(false);
713         result = getter.tryInvoke(func, "under");
714         assertFalse(getter.tryFailed(result));
715         assertEquals("-42", result);
716     }
717 
718     @Test
719     void testTryFailedScript() throws Exception {
720         // JEXL-257
721         final Functor func = new Functor();
722         final JexlContext ctxt = new MapContext();
723         ctxt.set("func", func);
724         Object result;
725         final JexlUberspect uber = JEXL.getUberspect();
726         final JexlScript method = JEXL.createScript("(x, y)->{ func.over(x, y) }");
727         // tryInvoke
728         // JexlMethod method = uber.getMethod(func, "over", "foo", 42);
729         assertNotNull(method);
730         // tryInvoke succeeds
731         result = method.execute(ctxt, "foo", 42);
732         assertEquals("foo + 42", result);
733         // tryInvoke fails
734         func.setKill(true);
735         JexlException xfail = assertThrows(JexlException.class, () -> method.execute(ctxt, "foo", 42), "should throw TryFailed");
736         assertEquals(UnsupportedOperationException.class, xfail.getCause().getClass());
737 
738         func.setKill(false);
739         final JexlScript setter = JEXL.createScript("(x)->{ func.under = x }");
740         // JexlPropertySet setter = uber.getPropertySet(func, "under", "42");
741         result = setter.execute(ctxt, "42");
742         assertEquals("42", result);
743 
744         final JexlScript getter = JEXL.createScript("func.under");
745         assertEquals("42", result);
746 
747         func.setKill(true);
748         xfail = assertThrows(JexlException.class, () -> setter.execute(ctxt, "42"), "should throw TryFailed");
749         assertEquals(UnsupportedOperationException.class, xfail.getCause().getClass());
750 
751         func.setKill(false);
752         result = setter.execute(ctxt, "-42");
753         assertEquals("-42", result);
754 
755         func.setKill(true);
756         xfail = assertThrows(JexlException.class, () -> getter.execute(ctxt), "should throw TryFailed");
757         assertEquals(UnsupportedOperationException.class, xfail.getCause().getClass());
758 
759         func.setKill(false);
760         result = getter.execute(ctxt);
761         assertEquals("-42", result);
762     }
763 
764     @Test
765     void testVariousFunctionLocation() throws Exception {
766         // see JEXL-190
767         final Map<String, Object> vars = new HashMap<>();
768         final Map<String,Object> funcs = new HashMap<>();
769         funcs.put(null, new ZSpace());
770         final JexlEngine jexl = new JexlBuilder().namespaces(funcs).arithmetic(new ZArithmetic(true)).create();
771 
772         final JexlContext zjc = new ZContext(vars); // that implements a z(int x) function
773         final String z41 = "z(41)";
774         final JexlScript callz41 = jexl.createScript(z41);
775         Object onovar = callz41.execute(zjc);
776         assertEquals(222, onovar);
777 
778         // override z() with global var
779         final JexlScript z241 = jexl.createScript("(x)->{ return x + 241}");
780         vars.put("z", z241);
781         final Object oglobal = callz41.execute(zjc);
782         assertEquals(282, oglobal);
783         // clear global and execute again
784         vars.remove("z");
785         onovar = callz41.execute(zjc);
786         assertEquals(222, onovar);
787 
788         // override z() with local var
789         final String slocal = "var z = (x)->{ return x + 141}; z(1)";
790         final JexlScript jlocal = jexl.createScript(slocal);
791         final Object olocal = jlocal.execute(zjc);
792         assertEquals(142, olocal);
793 
794         // and now try the context, the null namespace and the arithmetic
795         assertEquals(42, jexl.createScript("zz(2)").execute(zjc));
796         assertEquals(42, jexl.createScript("zzz(3)").execute(zjc));
797         assertEquals(42, jexl.createScript("zzzz(4)").execute(zjc));
798     }
799 
800 }