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