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.Debugger;
20  import org.apache.commons.jexl3.internal.TemplateDebugger;
21  import org.apache.commons.jexl3.internal.TemplateInterpreter;
22  import org.apache.commons.jexl3.internal.introspection.Permissions;
23  import org.apache.commons.jexl3.internal.introspection.Uberspect;
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  
27  import java.io.PrintWriter;
28  import java.io.StringReader;
29  import java.io.StringWriter;
30  import java.io.Writer;
31  import java.lang.reflect.Method;
32  import java.util.Arrays;
33  import java.util.Collections;
34  import java.util.List;
35  import java.util.Set;
36  import org.junit.After;
37  import org.junit.Assert;
38  import org.junit.Before;
39  import org.junit.Test;
40  import org.junit.runner.RunWith;
41  import org.junit.runners.Parameterized;
42  
43  /**
44   * Test cases for the UnifiedEL.
45   */
46  @SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
47  @RunWith(Parameterized.class)
48  public class JXLTTest extends JexlTestCase {
49      private static final Log LOGGER = LogFactory.getLog(JxltEngine.class);
50      private final MapContext vars = new MapContext();
51      private JexlEvalContext context = null;
52      private final JexlBuilder BUILDER;
53      private final JexlEngine ENGINE;
54      private final JxltEngine JXLT;
55  
56      public JXLTTest(final JexlBuilder builder) {
57          super("JXLTTest");
58          BUILDER = builder;
59          ENGINE = BUILDER.create();
60          JXLT = ENGINE.createJxltEngine();
61      }
62  
63  
64     @Parameterized.Parameters
65     public static List<JexlBuilder> engines() {
66         final JexlFeatures f = new JexlFeatures();
67         f.lexical(true).lexicalShade(true);
68        return Arrays.<JexlBuilder>asList(
69                new JexlBuilder().silent(false).lexical(true).lexicalShade(true).cache(128).strict(true),
70                new JexlBuilder().features(f).silent(false).cache(128).strict(true),
71                new JexlBuilder().silent(false).cache(128).strict(true));
72     }
73  
74      @Before
75      @Override
76      public void setUp() throws Exception {
77          // ensure jul logging is only error
78          java.util.logging.Logger.getLogger(org.apache.commons.jexl3.JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
79          context = new JexlEvalContext(vars);
80      }
81  
82      @After
83      @Override
84      public void tearDown() throws Exception {
85          debuggerCheck(ENGINE);
86          super.tearDown();
87      }
88  
89      private boolean isLexicalShade() {
90          JexlOptions options = context.getEngineOptions();
91          if (options.isLexicalShade()) {
92              return true;
93          }
94          options = new JexlOptions().set(ENGINE);
95          return options.isLexicalShade();
96      }
97  
98      private static String refactor(final TemplateDebugger td, final JxltEngine.Template ts) {
99          final boolean dbg = td.debug(ts);
100         if (dbg) {
101             return td.toString();
102         }
103         return "";
104     }
105 
106     /** Extract the source from a toString-ed expression. */
107     private String getSource(final String tostring) {
108         final int len = tostring.length();
109         int sc = tostring.lastIndexOf(" /*= ");
110         if (sc >= 0) {
111             sc += " /*= ".length();
112         }
113         final int ec = tostring.lastIndexOf(" */");
114         if (sc >= 0 && ec >= 0 && ec > sc && ec < len) {
115             return tostring.substring(sc, ec);
116         }
117         return tostring;
118 
119     }
120 
121     public static class Froboz {
122         int value;
123 
124         public Froboz(final int v) {
125             value = v;
126         }
127 
128         public void setValue(final int v) {
129             value = v;
130         }
131 
132         public int getValue() {
133             return value;
134         }
135 
136         public int plus10() {
137             final int i = value;
138             value += 10;
139             return i;
140         }
141     }
142 
143     @Test
144     public void testStatement() throws Exception {
145         final Froboz froboz = new Froboz(32);
146         context.set("froboz", froboz);
147         final JxltEngine.Expression check = JXLT.createExpression("${ froboz.plus10() }");
148         final Object o = check.evaluate(context);
149         Assert.assertEquals("Result is not 32", new Integer(32), o);
150         Assert.assertEquals("Result is not 42", 42, froboz.getValue());
151         final Set<List<String>> evars = check.getVariables();
152         Assert.assertEquals(1, evars.size());
153     }
154 
155     @Test
156     public void testAssign() throws Exception {
157         final Froboz froboz = new Froboz(32);
158         context.set("froboz", froboz);
159         final JxltEngine.Expression assign = JXLT.createExpression("${froboz.value = 42}");
160         final JxltEngine.Expression check = JXLT.createExpression("${froboz.value}");
161         Object o = assign.evaluate(context);
162         Assert.assertEquals("Result is not 10", new Integer(42), o);
163         o = check.evaluate(context);
164         Assert.assertEquals("Result is not 10", new Integer(42), o);
165     }
166 
167     @Test
168     public void testComposite() throws Exception {
169         final String source = "Dear ${p} ${name};";
170         final JxltEngine.Expression expr = JXLT.createExpression(source);
171         context.set("p", "Mr");
172         context.set("name", "Doe");
173         Assert.assertTrue("expression should be immediate", expr.isImmediate());
174         Object o = expr.evaluate(context);
175         Assert.assertEquals("Dear Mr Doe;", o);
176         context.set("p", "Ms");
177         context.set("name", "Jones");
178         o = expr.evaluate(context);
179         Assert.assertEquals("Dear Ms Jones;", o);
180         Assert.assertEquals(source, getSource(expr.toString()));
181     }
182 
183     boolean contains(final Set<List<String>> set, final List<String> list) {
184         for (final List<String> sl : set) {
185             if (sl.equals(list)) {
186                 return true;
187             }
188         }
189         return false;
190     }
191 
192     @Test
193     public void testPrepareEvaluate() throws Exception {
194         final String source = "Dear #{p} ${name};";
195         final JxltEngine.Expression expr = JXLT.createExpression("Dear #{p} ${name};");
196         Assert.assertTrue("expression should be deferred", expr.isDeferred());
197 
198         final Set<List<String>> evars = expr.getVariables();
199         Assert.assertEquals(1, evars.size());
200         Assert.assertTrue(contains(evars, Collections.singletonList("name")));
201         context.set("name", "Doe");
202         final JxltEngine.Expression phase1 = expr.prepare(context);
203         final String as = phase1.asString();
204         Assert.assertEquals("Dear ${p} Doe;", as);
205         final Set<List<String>> evars1 = phase1.getVariables();
206         Assert.assertEquals(1, evars1.size());
207         Assert.assertTrue(contains(evars1, Collections.singletonList("p")));
208         vars.clear();
209         context.set("p", "Mr");
210         context.set("name", "Should not be used in 2nd phase");
211         final Object o = phase1.evaluate(context);
212         Assert.assertEquals("Dear Mr Doe;", o);
213 
214         final String p1 = getSource(phase1.toString());
215         Assert.assertEquals(source, getSource(phase1.toString()));
216         Assert.assertEquals(source, getSource(expr.toString()));
217     }
218 
219     @Test
220     public void testNested() throws Exception {
221         final String source = "#{${hi}+'.world'}";
222         final JxltEngine.Expression expr = JXLT.createExpression(source);
223 
224         final Set<List<String>> evars = expr.getVariables();
225         Assert.assertEquals(1, evars.size());
226         Assert.assertTrue(contains(evars, Collections.singletonList("hi")));
227 
228         context.set("hi", "greeting");
229         context.set("greeting.world", "Hello World!");
230         Assert.assertTrue("expression should be deferred", expr.isDeferred());
231         final Object o = expr.evaluate(context);
232         Assert.assertEquals("Hello World!", o);
233 
234         Assert.assertEquals(source, getSource(expr.toString()));
235     }
236 
237     @Test
238     public void testNestedTemplate() throws Exception {
239         final String source = "#{${hi}+'.world'}";
240         final JxltEngine.Template expr = JXLT.createTemplate(source, "hi");
241 
242         context.set("greeting.world", "Hello World!");
243         final StringWriter strw = new StringWriter();
244         expr.evaluate(context, strw, "greeting");
245         final String o = strw.toString();
246         Assert.assertEquals("Hello World!", o);
247 
248         Assert.assertEquals(source, getSource(expr.toString()));
249     }
250 
251     @Test
252     public void testImmediate() throws Exception {
253         final JexlContext none = null;
254         final String source = "${'Hello ' + 'World!'}";
255         final JxltEngine.Expression expr = JXLT.createExpression(source);
256         final JxltEngine.Expression prepared = expr.prepare(none);
257         Assert.assertEquals("prepare should return same expression", "Hello World!", prepared.asString());
258         final Object o = expr.evaluate(none);
259         Assert.assertTrue("expression should be immediate", expr.isImmediate());
260         Assert.assertEquals("Hello World!", o);
261 
262         Assert.assertEquals(source, getSource(expr.toString()));
263     }
264 
265     @Test
266     public void testConstant0() throws Exception {
267         final JexlContext none = null;
268         final String source = "Hello World!";
269         final JxltEngine.Expression expr = JXLT.createExpression(source);
270         Assert.assertSame("prepare should return same expression", expr.prepare(none), expr);
271         final Object o = expr.evaluate(none);
272         Assert.assertTrue("expression should be immediate", expr.isImmediate());
273         Assert.assertEquals("Hello World!", o);
274 
275         Assert.assertEquals(source, getSource(expr.toString()));
276     }
277 
278     @Test
279     public void testConstant2() throws Exception {
280         final JexlContext none = null;
281         final String source = "${size({'map':123,'map2':456})}";
282         final JxltEngine.Expression expr = JXLT.createExpression(source);
283         //Assert.assertTrue("prepare should return same expression", expr.prepare(none) == expr);
284         final Object o = expr.evaluate(none);
285         Assert.assertTrue("expression should be immediate", expr.isImmediate());
286         Assert.assertEquals(2, o);
287 
288         Assert.assertEquals(source, getSource(expr.toString()));
289     }
290 
291     @Test
292     public void testConstant3() throws Exception {
293         final JexlContext none = null;
294         final String source = "#{size({'map':123,'map2':456})}";
295         final JxltEngine.Expression expr = JXLT.createExpression(source);
296         //Assert.assertTrue("prepare should return same expression", expr.prepare(none) == expr);
297         final Object o = expr.evaluate(none);
298         Assert.assertTrue("expression should be deferred", expr.isDeferred());
299         Assert.assertEquals(2, o);
300 
301         Assert.assertEquals(source, getSource(expr.toString()));
302     }
303 
304     @Test
305     public void testConstant4() throws Exception {
306         final JexlContext none = null;
307         final String source = "#{ ${size({'1':2,'2': 3})} }";
308         final JxltEngine.Expression expr = JXLT.createExpression(source);
309         //Assert.assertTrue("prepare should return same expression", expr.prepare(none) == expr);
310         final Object o = expr.evaluate(none);
311         Assert.assertTrue("expression should be deferred", expr.isDeferred());
312         Assert.assertEquals(2, o);
313 
314         Assert.assertEquals(source, getSource(expr.toString()));
315     }
316 
317     @Test
318     public void testDeferred() throws Exception {
319         final JexlContext none = null;
320         final String source = "#{'world'}";
321         final JxltEngine.Expression expr = JXLT.createExpression(source);
322         Assert.assertTrue("expression should be deferred", expr.isDeferred());
323         final String as = expr.prepare(none).asString();
324         Assert.assertEquals("prepare should return immediate version", "${'world'}", as);
325         final Object o = expr.evaluate(none);
326         Assert.assertEquals("world", o);
327 
328         Assert.assertEquals(source, getSource(expr.toString()));
329     }
330 
331     @Test
332     public void testEscape() throws Exception {
333         final JexlContext none = null;
334         JxltEngine.Expression expr;
335         Object o;
336         // $ and # are escapable in TemplateEngine
337         expr = JXLT.createExpression("\\#{'world'}");
338         o = expr.evaluate(none);
339         Assert.assertEquals("#{'world'}", o);
340         expr = JXLT.createExpression("\\${'world'}");
341         o = expr.evaluate(none);
342         Assert.assertEquals("${'world'}", o);
343     }
344 
345     @Test
346     public void testEscapeString() throws Exception {
347         final JxltEngine.Expression expr = JXLT.createExpression("\\\"${'world\\'s finest'}\\\"");
348         final JexlContext none = null;
349         final Object o = expr.evaluate(none);
350         Assert.assertEquals("\"world's finest\"", o);
351     }
352 
353     @Test
354     public void testNonEscapeString() throws Exception {
355         final JxltEngine.Expression expr = JXLT.createExpression("c:\\some\\windows\\path");
356         final JexlContext none = null;
357         final Object o = expr.evaluate(none);
358         Assert.assertEquals("c:\\some\\windows\\path", o);
359     }
360 
361     @Test
362     public void testMalformed() throws Exception {
363         try {
364             final JxltEngine.Expression expr = JXLT.createExpression("${'world'");
365             final JexlContext none = null;
366             expr.evaluate(none);
367             Assert.fail("should be malformed");
368         } catch (final JxltEngine.Exception xjexl) {
369             // expected
370             final String xmsg = xjexl.getMessage();
371             LOGGER.debug(xmsg);
372         }
373     }
374 
375     @Test
376     public void testMalformedNested() throws Exception {
377         try {
378             final JxltEngine.Expression expr = JXLT.createExpression("#{${hi} world}");
379             final JexlContext none = null;
380             expr.evaluate(none);
381             Assert.fail("should be malformed");
382         } catch (final JxltEngine.Exception xjexl) {
383             // expected
384             final String xmsg = xjexl.getMessage();
385             LOGGER.debug(xmsg);
386         }
387     }
388 
389     @Test
390     public void testMalformedNested2() throws Exception {
391         try {
392             final JxltEngine.Expression expr = JXLT.createExpression("#{${hi} world}");
393             final JexlContext ctxt = new MapContext();
394             ctxt.set("hi", "hello");
395             expr.evaluate(ctxt);
396             Assert.fail("should be malformed");
397         } catch (final JxltEngine.Exception xjexl) {
398             // expected
399             final String xmsg = xjexl.getMessage();
400             LOGGER.debug(xmsg);
401         }
402     }
403 
404     @Test
405     public void testBadContextNested() throws Exception {
406         try {
407             final JxltEngine.Expression expr = JXLT.createExpression("#{${hi}+'.world'}");
408             final JexlContext none = null;
409             expr.evaluate(none);
410             Assert.fail("should be malformed");
411         } catch (final JxltEngine.Exception xjexl) {
412             // expected
413             final String xmsg = xjexl.getMessage();
414             LOGGER.debug(xmsg);
415         }
416     }
417 
418     @Test
419     public void testCharAtBug() throws Exception {
420         context.set("foo", "abcdef");
421         final JexlOptions options = context.getEngineOptions();
422         JxltEngine.Expression expr = JXLT.createExpression("${foo.substring(2,4)/*comment*/}");
423         Object o = expr.evaluate(context);
424         Assert.assertEquals("cd", o);
425 
426         context.set("bar", "foo");
427         try {
428             options.setSilent(true);
429             expr = JXLT.createExpression("#{${bar}+'.charAt(-2)'}");
430             expr = expr.prepare(context);
431             o = expr.evaluate(context);
432             Assert.assertNull(o);
433         } finally {
434             options.setSilent(false);
435         }
436 
437     }
438 
439     @Test
440     public void testTemplate0() throws Exception {
441         final String source = "   $$ if(x) {\nx is ${x}\n   $$ } else {\n${'no x'}\n$$ }\n";
442         StringWriter strw;
443         String output;
444 
445         final JxltEngine.Template t = JXLT.createTemplate(source);
446 
447         context.set("x", 42);
448         strw = new StringWriter();
449         t.evaluate(context, strw);
450         output = strw.toString();
451         Assert.assertEquals("x is 42\n", output);
452 
453         strw = new StringWriter();
454         context.set("x", "");
455         t.evaluate(context, strw);
456         output = strw.toString();
457         Assert.assertEquals("no x\n", output);
458 
459         final String dstr = t.toString();
460         Assert.assertNotNull(dstr);
461     }
462 
463     @Test
464     public void testTemplate10() throws Exception {
465         final String source = "$$(x)->{ if(x) {\nx is ${x}\n$$ } else {\n${'no x'}\n$$ } }\n";
466         StringWriter strw;
467         String output;
468 
469         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(source), (String[]) null);
470         final String dstr = t.asString();
471         Assert.assertNotNull(dstr);
472 
473         final String[] ps = t.getParameters();
474         Assert.assertTrue(Arrays.asList(ps).contains("x"));
475 
476         strw = new StringWriter();
477         t.evaluate(context, strw, 42);
478         output = strw.toString();
479         Assert.assertEquals("x is 42\n", output);
480     }
481 
482     @Test
483     public void testTemplate1() throws Exception {
484         final String source = "$$ if(x) {\nx is ${x}\n$$ } else {\n${'no x'}\n$$ }\n";
485         StringWriter strw;
486         String output;
487 
488         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(source), "x");
489         final String dstr = t.asString();
490         Assert.assertNotNull(dstr);
491 
492         strw = new StringWriter();
493         t.evaluate(context, strw, 42);
494         output = strw.toString();
495         Assert.assertEquals("x is 42\n", output);
496 
497         strw = new StringWriter();
498         t.evaluate(context, strw, "");
499         output = strw.toString();
500         Assert.assertEquals("no x\n", output);
501     }
502 
503     @Test
504     public void testTemplate2() throws Exception {
505         final String source = "The answer: ${x}";
506         StringWriter strw;
507         String output;
508 
509         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(source), "x");
510         final String dstr = t.asString();
511         Assert.assertNotNull(dstr);
512 
513         strw = new StringWriter();
514         t.evaluate(context, strw, 42);
515         output = strw.toString();
516         Assert.assertEquals("The answer: 42", output);
517     }
518 
519     @Test
520     public void testPrepareTemplate() throws Exception {
521         final String source
522                 = "$$ for(var x : list) {\n"
523                 + "${l10n}=#{x}\n"
524                 + "$$ }\n";
525         final int[] args = {42};
526         final JxltEngine.Template tl10n = JXLT.createTemplate(source, "list");
527         final String dstr = tl10n.asString();
528         Assert.assertNotNull(dstr);
529         final Set<List<String>> vars = tl10n.getVariables();
530         Assert.assertFalse(vars.isEmpty());
531         context.set("l10n", "valeur");
532         final JxltEngine.Template tpFR = tl10n.prepare(context);
533         context.set("l10n", "value");
534         final JxltEngine.Template tpEN = tl10n.prepare(context);
535         context.set("l10n", null);
536 
537         StringWriter strw;
538         strw = new StringWriter();
539         tpFR.evaluate(context, strw, args);
540         final String outFR = strw.toString();
541         Assert.assertEquals("valeur=42\n", outFR);
542 
543         context.set("l10n", null);
544         strw = new StringWriter();
545         tpEN.evaluate(context, strw, args);
546         final String outEN = strw.toString();
547         Assert.assertEquals("value=42\n", outEN);
548     }
549 
550     @Test
551     public void test42() throws Exception {
552         final String test42
553                 = "$$ for(var x : list) {\n"
554                 + "$$   if (x == 42) {\n"
555                 + "Life, the universe, and everything\n"
556                 + "$$   } else if (x > 42) {\n"
557                 + "The value ${x} is over fourty-two\n"
558                 + "$$   } else {\n"
559                 + "The value ${x} is under fourty-two\n"
560                 + "$$   }\n"
561                 + "$$ }\n";
562         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(test42), "list");
563         final StringWriter strw = new StringWriter();
564         final int[] list = {1, 3, 5, 42, 169};
565         t.evaluate(context, strw, list);
566         final String output = strw.toString();
567         final String out42
568                 = "The value 1 is under fourty-two\n"
569                 + "The value 3 is under fourty-two\n"
570                 + "The value 5 is under fourty-two\n"
571                 + "Life, the universe, and everything\n"
572                 + "The value 169 is over fourty-two\n";
573         Assert.assertEquals(out42, output);
574 
575         final String dstr = t.asString();
576         Assert.assertNotNull(dstr);
577 
578         final TemplateDebugger td = new TemplateDebugger();
579         final String refactored = refactor(td, t);
580         Assert.assertNotNull(refactored);
581         Assert.assertEquals(test42, refactored);
582     }
583 
584     @Test
585     public void testInheritedDebugger() throws Exception {
586         final String src = "if ($A) { $B + 1; } else { $C - 2 }";
587         final JexlEngine jexl = JXLT.getEngine();
588         final JexlScript script = jexl.createScript(src);
589 
590         final Debugger sd = new Debugger();
591         final String rscript = sd.debug(script)? sd.toString() : null;
592         Assert.assertNotNull(rscript);
593 
594         final TemplateDebugger td = new TemplateDebugger();
595         final String refactored = td.debug(script)? td.toString() : null;
596         Assert.assertNotNull(refactored);
597         Assert.assertEquals(refactored, rscript);
598     }
599 
600     public static class FrobozWriter extends PrintWriter {
601         public FrobozWriter(final Writer w) {
602             super(w);
603         }
604 
605         public void print(final Froboz froboz) {
606             super.print("froboz{");
607             super.print(froboz.value);
608             super.print("}");
609         }
610 
611         @Override
612         public String toString() {
613             return out.toString();
614         }
615     }
616 
617     @Test
618     public void testWriter() throws Exception {
619         final Froboz froboz = new Froboz(42);
620         final Writer writer = new FrobozWriter(new StringWriter());
621         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader("$$$jexl.print(froboz)"), "froboz");
622         t.evaluate(context, writer, froboz);
623         Assert.assertEquals("froboz{42}", writer.toString());
624     }
625 
626     @Test
627     public void testReport() throws Exception {
628         final String rpt
629                 = "<report>\n"
630                 + "\n"
631                 + "\n$$ var a = 1;"
632                 + "\n$$ var x = 2;"
633                 + "\n"
634                 + "\n$$ var y = 9;"
635                 + "\n"
636                 + "\n        ${x + y}"
637                 + "\n</report>\n";
638         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt));
639         final StringWriter strw = new StringWriter();
640         t.evaluate(context, strw);
641         final String output = strw.toString();
642         final String ctl = "<report>\n\n\n\n\n        11\n</report>\n";
643         Assert.assertEquals(ctl, output);
644 
645         final TemplateDebugger td = new TemplateDebugger();
646         final String refactored = refactor(td, t);
647         Assert.assertNotNull(refactored);
648         Assert.assertEquals(rpt, refactored);
649     }
650 
651     @Test
652     public void testReport1() throws Exception {
653         final String rpt
654                 = "<report>\n"
655                 + "this is ${x}\n"
656                 + "${x + 1}\n"
657                 + "${x + 2}\n"
658                 + "${x + 3}\n"
659                 + "</report>\n";
660         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt));
661         final StringWriter strw = new StringWriter();
662         context.set("x", 42);
663         t.evaluate(context, strw, 42);
664         final String output = strw.toString();
665         int count = 0;
666         for (int i = 0; i < output.length(); ++i) {
667             final char c = output.charAt(i);
668             if ('\n' == c) {
669                 count += 1;
670             }
671         }
672         Assert.assertEquals(6, count);
673         Assert.assertTrue(output.indexOf("42") > 0);
674         Assert.assertTrue(output.indexOf("43") > 0);
675         Assert.assertTrue(output.indexOf("44") > 0);
676         Assert.assertTrue(output.indexOf("45") > 0);
677     }
678 
679     @Test
680     public void testReport2() throws Exception {
681         final String rpt
682                 = "<report>\n"
683                 + "this is ${x}\n"
684                 + "${x + 1}\n"
685                 + "${x + 2}\n"
686                 + "${x + 3}\n"
687                 + "</report>\n";
688         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt), "x");
689         final StringWriter strw = new StringWriter();
690         t.evaluate(context, strw, 42);
691         final String output = strw.toString();
692         int count = 0;
693         for (int i = 0; i < output.length(); ++i) {
694             final char c = output.charAt(i);
695             if ('\n' == c) {
696                 count += 1;
697             }
698         }
699         Assert.assertEquals(6, count);
700         Assert.assertTrue(output.indexOf("42") > 0);
701         Assert.assertTrue(output.indexOf("43") > 0);
702         Assert.assertTrue(output.indexOf("44") > 0);
703         Assert.assertTrue(output.indexOf("45") > 0);
704 
705         final TemplateDebugger td = new TemplateDebugger();
706         final String xxx = refactor(td, t);
707         Assert.assertNotNull(xxx);
708         Assert.assertEquals(rpt, xxx);
709     }
710     @Test
711     public void testOneLiner() throws Exception {
712         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader("fourty-two"));
713         final StringWriter strw = new StringWriter();
714         t.evaluate(context, strw);
715         final String output = strw.toString();
716         Assert.assertEquals("fourty-two", output);
717     }
718 
719     @Test
720     public void testOneLinerVar() throws Exception {
721         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader("fourty-${x}"));
722         final StringWriter strw = new StringWriter();
723         context.set("x", "two");
724         t.evaluate(context, strw);
725         final String output = strw.toString();
726         Assert.assertEquals("fourty-two", output);
727     }
728 
729     @Test
730     public void testInterpolation() throws Exception {
731         final String expr =  "`Hello \n${user}`";
732         final JexlScript script = ENGINE.createScript(expr);
733         context.set("user", "Dimitri");
734         Object value = script.execute(context);
735         Assert.assertEquals(expr, "Hello \nDimitri", value);
736         context.set("user", "Rahul");
737         value = script.execute(context);
738         Assert.assertEquals(expr, "Hello \nRahul", value);
739     }
740 
741     @Test
742     public void testInterpolationGlobal() throws Exception {
743         if (isLexicalShade()) {
744             context.set("user", null);
745         }
746         final String expr =  "user='Dimitri'; `Hello \n${user}`";
747         final Object value = ENGINE.createScript(expr).execute(context);
748         Assert.assertEquals(expr, "Hello \nDimitri", value);
749     }
750 
751     @Test
752     public void testInterpolationLocal() throws Exception {
753         final String expr =  "var user='Henrib'; `Hello \n${user}`";
754         final Object value = ENGINE.createScript(expr).execute(context);
755         Assert.assertEquals(expr, "Hello \nHenrib", value);
756     }
757 
758     @Test
759     public void testInterpolationLvsG() throws Exception {
760         if (isLexicalShade()) {
761             context.set("user", null);
762         }
763         final String expr =  "user='Dimitri'; var user='Henrib'; `H\\\"ello \n${user}`";
764         final Object value = ENGINE.createScript(expr).execute(context);
765         Assert.assertEquals(expr, "H\"ello \nHenrib", value);
766     }
767 
768     @Test
769     public void testInterpolationLvsG2() throws Exception {
770         if (isLexicalShade()) {
771             context.set("user", null);
772         }
773         final String expr =  "user='Dimitri'; var user='Henrib'; `H\\`ello \n${user}`";
774         final Object value = ENGINE.createScript(expr).execute(context);
775         Assert.assertEquals(expr, "H`ello \nHenrib", value);
776     }
777 
778     @Test
779     public void testInterpolationParameter() throws Exception {
780         final String expr =  "(user)->{`Hello \n${user}`}";
781         JexlScript script = ENGINE.createScript(expr);
782         Object value = script.execute(context, "Henrib");
783         Assert.assertEquals(expr, "Hello \nHenrib", value);
784         value = ENGINE.createScript(expr).execute(context, "Dimitri");
785         Assert.assertEquals(expr, "Hello \nDimitri", value);
786     }
787 
788     @Test
789     public void testDbgEscapes() throws Exception {
790         String[] srcs = new String[]{
791                 "jexl:print('hello\\'\\nworld')",
792                 "'hello\\tworld'",
793                 "'hello\\nworld'",
794                 "'hello\\fworld'",
795                 "'hello\\rworld'"
796         };
797         for(String src : srcs) {
798             JexlScript script = ENGINE.createScript(src);
799             Debugger dbg = new Debugger();
800             dbg.debug(script);
801             String msrc = dbg.toString();
802             Assert.assertEquals(src, msrc);
803         }
804     }
805 
806     @Test
807     public void testImmediateTemplate() throws Exception {
808         context.set("tables", new String[]{"table1", "table2"});
809         context.set("w" ,"x=1");
810         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(
811              "select * from \n"+
812              "$$var comma = false; \n"+
813              "$$for(var c : tables) { \n"+
814              "$$  if (comma) $jexl.write(','); else comma = true;\n"+
815              "${c}"+
816              "\n$$}\n"+
817              "where ${w}\n"
818         ));
819         final StringWriter strw = new StringWriter();
820         //vars.clear();
821         t.evaluate(context, strw);
822         final String output = strw.toString();
823         Assert.assertTrue(output.contains("table1") && output.contains("table2"));
824     }
825 
826     public static class Executor311 {
827         private final String name;
828 
829         public Executor311(final String name) {
830             this.name = name;
831         }
832         // Injects name as first arg of any called script
833         public Object execute(final JexlScript script, final Object ...args) {
834             Object[] actuals;
835             if (args != null && args.length > 0) {
836                 actuals = new Object[args.length + 1] ;
837                 System.arraycopy(args, 0, actuals, 1, args.length);
838                 actuals[0] = name;
839             } else {
840                 actuals = new Object[]{name};
841             }
842             return script.execute(JexlEngine.getThreadContext(), actuals);
843         }
844     }
845 
846     public static class Context311 extends MapContext
847       implements JexlContext.OptionsHandle, JexlContext.ThreadLocal {
848         private JexlOptions options = null;
849 
850         public void setOptions(final JexlOptions o) {
851             options = o;
852         }
853 
854         public Executor311 exec(final String name) {
855             return new Executor311(name);
856         }
857 
858         @Override
859         public JexlOptions getEngineOptions() {
860             return options;
861         }
862 
863         JexlOptions newOptions() {
864             options = new JexlOptions();
865             return options;
866         }
867     }
868 
869     @Test
870     public void test311a() throws Exception {
871         final JexlContext ctx = null;
872         final String rpt
873                 = "$$((a)->{\n"
874                 + "<p>Universe ${a}</p>\n"
875                 + "$$})(42)";
876         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt));
877         final StringWriter strw = new StringWriter();
878         t.evaluate(ctx, strw);
879         final String output = strw.toString();
880         Assert.assertEquals("<p>Universe 42</p>\n", output);
881     }
882 
883     @Test
884     public void test311b() throws Exception {
885         final JexlContext ctx311 = new Context311();
886         final String rpt
887                 = "$$ exec('42').execute(()->{\n"
888                 + "<p>Universe 42</p>\n"
889                 + "$$})";
890         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt));
891         final StringWriter strw = new StringWriter();
892         t.evaluate(ctx311, strw, 42);
893         final String output = strw.toString();
894         Assert.assertEquals("<p>Universe 42</p>\n", output);
895     }
896 
897     @Test
898     public void test311c() throws Exception {
899         final Context311 ctx311 = new Context311();
900         ctx311.newOptions().setLexical(true);
901         final String rpt
902                 = "$$ exec('42').execute((a)->{"
903                 + "\n<p>Universe ${a}</p>"
904                 + "\n$$})";
905         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt));
906         final StringWriter strw = new StringWriter();
907         t.evaluate(ctx311, strw, 42);
908         final String output = strw.toString();
909         Assert.assertEquals("<p>Universe 42</p>\n", output);
910     }
911 
912     @Test
913     public void test311d() throws Exception {
914         final Context311 ctx311 = new Context311();
915         ctx311.newOptions().setLexical(true);
916         final String rpt
917                 = "$$ exec('4').execute((a, b)->{"
918                 + "\n<p>Universe ${a}${b}</p>"
919                 + "\n$$}, '2')";
920         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt));
921         final StringWriter strw = new StringWriter();
922         t.evaluate(ctx311, strw, 42);
923         final String output = strw.toString();
924         Assert.assertEquals("<p>Universe 42</p>\n", output);
925     }
926 
927     @Test
928     public void test311e() throws Exception {
929         final Context311 ctx311 = new Context311();
930         ctx311.newOptions().setLexical(true);
931         final String rpt
932                 = "exec('4').execute((a, b)->{"
933                 + " '<p>Universe ' + a + b + '</p>'"
934                 + "}, '2')";
935         final JexlScript script = JEXL.createScript(rpt);
936         final String output = script.execute(ctx311, 42).toString();
937         Assert.assertEquals("<p>Universe 42</p>", output);
938     }
939 
940     @Test
941     public void test311f() throws Exception {
942         final Context311 ctx311 = new Context311();
943         ctx311.newOptions().setLexical(true);
944         final String rpt
945                 = "exec('4').execute((a, b)->{"
946                 + " `<p>Universe ${a}${b}</p>`"
947                 + "}, '2')";
948         final JexlScript script = JEXL.createScript(rpt);
949         final String output = script.execute(ctx311, 42).toString();
950         Assert.assertEquals("<p>Universe 42</p>", output);
951     }
952 
953     @Test
954     public void test311g() throws Exception {
955         final Context311 ctx311 = new Context311();
956         ctx311.newOptions().setLexical(true);
957         final String rpt
958                 = "(a, b)->{"
959                 + " `<p>Universe ${a}${b}</p>`"
960                 + "}";
961         final JexlScript script = JEXL.createScript(rpt);
962         final String output = script.execute(ctx311, "4", "2").toString();
963         Assert.assertEquals("<p>Universe 42</p>", output);
964     }
965 
966     @Test
967     public void test311h() throws Exception {
968         final Context311 ctx311 = new Context311();
969         ctx311.newOptions().setLexical(true);
970         final String rpt= " `<p>Universe ${a}${b}</p>`";
971         final JexlScript script = JEXL.createScript(rpt, "a", "b");
972         final String output = script.execute(ctx311, "4", "2").toString();
973         Assert.assertEquals("<p>Universe 42</p>", output);
974     }
975 
976     @Test
977     public void test311i() throws Exception {
978         final JexlContext ctx311 = new Context311();
979         final String rpt
980                 = "$$var u = 'Universe'; exec('4').execute((a, b)->{"
981                 + "\n<p>${u} ${a}${b}</p>"
982                 + "\n$$}, '2')";
983         final JxltEngine.Template t = JXLT.createTemplate("$$", new StringReader(rpt));
984         final StringWriter strw = new StringWriter();
985         t.evaluate(ctx311, strw, 42);
986         final String output = strw.toString();
987         Assert.assertEquals("<p>Universe 42</p>\n", output);
988     }
989 
990     @Test
991     public void test315() throws Exception {
992         String s315;
993         StringWriter strw;
994         JxltEngine.Template t315;
995         String output;
996 
997         s315 = "<report/>$";
998         t315 = JXLT.createTemplate("$$", new StringReader(s315));
999         strw = new StringWriter();
1000         t315.evaluate(context, strw);
1001         output = strw.toString();
1002         Assert.assertEquals(s315, output);
1003 
1004         s315 = "<foo/>#";
1005         t315 = JXLT.createTemplate("$$", new StringReader(s315));
1006          strw = new StringWriter();
1007         t315.evaluate(context, strw);
1008         output = strw.toString();
1009         Assert.assertEquals(s315, output);
1010 
1011         s315 = "<bar/>\\";
1012         t315 = JXLT.createTemplate("$$", new StringReader(s315));
1013         strw = new StringWriter();
1014         t315.evaluate(context, strw);
1015         output = strw.toString();
1016         Assert.assertEquals(s315, output);
1017     }
1018 
1019     @Test
1020     public void testLexicalTemplate() throws Exception {
1021         final JexlOptions opts = new JexlOptions();
1022         final JexlContext ctxt = new PragmaticContext(opts);
1023         opts.setCancellable(false);
1024         opts.setStrict(false);
1025         opts.setSafe(true);
1026         opts.setLexical(false);
1027         opts.setLexicalShade(false);
1028         final String src0 = "${$options.strict?'+':'-'}strict"
1029                 + " ${$options.cancellable?'+':'-'}cancellable"
1030                 + " ${$options.lexical?'+':'-'}lexical"
1031                 + " ${$options.lexicalShade?'+':'-'}lexicalShade"
1032                 + " ${$options.safe?'+':'-'}safe";
1033 
1034         final JxltEngine.Template tmplt0 = JXLT.createTemplate("$$", new StringReader(src0));
1035         final Writer strw0 = new StringWriter();
1036         tmplt0.evaluate(ctxt, strw0);
1037         final String output0 = strw0.toString();
1038         JexlFeatures features = BUILDER.features();
1039         if (features != null && features.isLexical() && features.isLexicalShade()) {
1040             Assert.assertEquals("-strict -cancellable +lexical +lexicalShade +safe", output0);
1041         } else {
1042             Assert.assertEquals("-strict -cancellable -lexical -lexicalShade +safe", output0);
1043         }
1044 
1045         final String src = "$$ #pragma script.mode pro50\n" + src0;
1046 
1047         final JxltEngine.Template tmplt = JXLT.createTemplate("$$", new StringReader(src));
1048         final Writer strw = new StringWriter();
1049         tmplt.evaluate(ctxt, strw);
1050         final String output = strw.toString();
1051         Assert.assertEquals("+strict +cancellable +lexical +lexicalShade -safe", output);
1052     }
1053 
1054     @Test
1055     public void testTemplatePragmaPro50() throws Exception {
1056         final JexlOptions opts = new JexlOptions();
1057         opts.setCancellable(false);
1058         opts.setStrict(false);
1059         opts.setSafe(true);
1060         opts.setLexical(false);
1061         opts.setLexicalShade(false);
1062         opts.setSharedInstance(true);
1063         final JexlContext ctxt = new PragmaticContext(opts);
1064         final String src = "$$ #pragma script.mode pro50\n"
1065                 + "$$ var tab = null;\n"
1066                 + "$$ tab.dummy();";
1067         final JxltEngine.Template tmplt = JXLT.createTemplate("$$", new StringReader(src));
1068         final Writer strw = new StringWriter();
1069         try {
1070             tmplt.evaluate(ctxt, strw);
1071             Assert.fail("tab var is null");
1072         } catch (final JexlException.Variable xvar) {
1073             Assert.assertEquals("tab", xvar.getVariable());
1074             Assert.assertFalse(xvar.isUndefined());
1075         }
1076     }
1077 
1078     @Test
1079     public void testTemplateOutOfScope() throws Exception {
1080         final JexlOptions opts = new JexlOptions();
1081         opts.setCancellable(false);
1082         opts.setStrict(false);
1083         opts.setLexical(false);
1084         opts.setLexicalShade(false);
1085         opts.setSharedInstance(true);
1086         final JexlContext ctxt = new PragmaticContext(opts);
1087         final String src = "$$if (false) { var tab = 42; }\n"
1088                 + "${tab}";
1089         JxltEngine.Template tmplt;
1090         final JexlFeatures features = BUILDER.features();
1091         try {
1092             tmplt = JXLT.createTemplate("$$", new StringReader(src));
1093         } catch (final JexlException xparse) {
1094             if (features != null && features.isLexicalShade()) {
1095                 return;
1096             }
1097             throw xparse;
1098         }
1099         final Writer strw = new StringWriter();
1100         opts.setSafe(true);
1101         try {
1102             tmplt.evaluate(ctxt, strw);
1103             Assert.assertTrue(strw.toString().isEmpty());
1104         } catch (final JexlException.Variable xvar) {
1105             Assert.fail("safe should prevent local shade");
1106         }
1107         opts.setStrict(true);
1108         opts.setSafe(false);
1109         try {
1110             tmplt.evaluate(ctxt, strw);
1111             Assert.fail("tab var is undefined");
1112         } catch (final JexlException.Variable xvar) {
1113             Assert.assertTrue("tab".equals(xvar.getVariable()));
1114             Assert.assertTrue(xvar.isUndefined());
1115         } catch (final JexlException xany) {
1116             Assert.assertTrue(xany.getMessage().contains("tab"));
1117         }
1118     }
1119 
1120     @Test
1121     public void testCommentedTemplate0() throws Exception {
1122         final JexlContext ctxt = new MapContext();
1123         final JexlEngine jexl = new JexlBuilder().create();
1124         final JxltEngine jxlt = jexl.createJxltEngine();
1125         JxltEngine.Template tmplt;
1126         final String src = "$$/*\n"
1127                 + "Hello\n"
1128                 + "$$*/";
1129         tmplt = jxlt.createTemplate(src);
1130         Assert.assertNotNull(tmplt);
1131         final Writer strw = new StringWriter();
1132         tmplt.evaluate(ctxt, strw);
1133         Assert.assertTrue(strw.toString().isEmpty());
1134     }
1135 
1136     @Test
1137     public void testCommentedTemplate1() throws Exception {
1138         final JexlContext ctxt = new MapContext();
1139         final JexlEngine jexl = new JexlBuilder().create();
1140         final JxltEngine jxlt = jexl.createJxltEngine();
1141         JxltEngine.Template tmplt;
1142         final String src = "$$/*\n"
1143                 + "one\n"
1144                 + "$$*/\n"
1145                 + "42\n"
1146                 + "$$/*\n"
1147                 + "three\n"
1148                 + "$$*/\n";
1149         tmplt = jxlt.createTemplate(src);
1150         Assert.assertNotNull(tmplt);
1151         final Writer strw = new StringWriter();
1152         tmplt.evaluate(ctxt, strw);
1153         Assert.assertEquals("42\n", strw.toString());
1154     }
1155 
1156     @Test
1157     public void testConstantTemplate() {
1158         String src = "<script>\n" +
1159                 "      function test(src){\n" +
1160                 "        var res = src.replace(/\\n\\t\\s/g, '\\n');\n" +
1161                 "      }\n" +
1162                 "      test();\n" +
1163                 "    </script>";
1164         final JexlContext ctxt = new MapContext();
1165         final JexlEngine jexl = new JexlBuilder().create();
1166         final JxltEngine jxlt = jexl.createJxltEngine();
1167         JxltEngine.Template tmplt;
1168         tmplt = jxlt.createTemplate(src);
1169         Assert.assertNotNull(tmplt);
1170         final Writer strw = new StringWriter();
1171         tmplt.evaluate(ctxt, strw);
1172         String result = strw.toString();
1173         Assert.assertEquals(src, result);
1174     }
1175 
1176     private static final Permissions NOJEXL3 = new Permissions() {
1177         @Override public boolean allow(Class<?> clazz) {
1178             String cname = clazz.getName();
1179             return !cname.contains("jexl3") || cname.contains("311");
1180         }
1181     };
1182 
1183     @Test
1184     public void testSanboxedTemplate() throws Exception {
1185         final String src = "Hello ${user}";
1186         final JexlContext ctxt = new MapContext();
1187         ctxt.set("user", "Francesco");
1188         /// this uberspect can not access jexl3 classes (besides test)
1189         Uberspect uberspect = new Uberspect(LogFactory.getLog(JXLTTest.class), null, NOJEXL3);
1190         Method method = uberspect.getMethod(TemplateInterpreter.class, "print", new Object[]{Integer.TYPE});
1191         Assert.assertNull(method);
1192         // ensures JXLT sandboxed still executes
1193         final JexlEngine jexl= new JexlBuilder().uberspect(uberspect).create();
1194         final JxltEngine jxlt = jexl.createJxltEngine();
1195 
1196         JxltEngine.Template tmplt = jxlt.createTemplate(src);
1197         Writer strw = new StringWriter();
1198         tmplt.evaluate(ctxt, strw);
1199         String result = strw.toString();
1200         Assert.assertEquals("Hello Francesco", result);
1201     }
1202 
1203     @Test
1204     public void testSanboxed311i() throws Exception {
1205         /// this uberspect can not access jexl3 classes (besides test)
1206         Uberspect uberspect = new Uberspect(LogFactory.getLog(JXLTTest.class), null, NOJEXL3);
1207         Method method = uberspect.getMethod(TemplateInterpreter.class, "print", new Object[]{Integer.TYPE});
1208         final JexlEngine jexl= new JexlBuilder().uberspect(uberspect).create();
1209         final JxltEngine jxlt = jexl.createJxltEngine();
1210         final JexlContext ctx311 = new Context311();
1211         final String rpt
1212                 = "$$var u = 'Universe'; exec('4').execute((a, b)->{"
1213                 + "\n<p>${u} ${a}${b}</p>"
1214                 + "\n$$}, '2')";
1215         final JxltEngine.Template t = jxlt.createTemplate("$$", new StringReader(rpt));
1216         final StringWriter strw = new StringWriter();
1217         t.evaluate(ctx311, strw, 42);
1218         final String output = strw.toString();
1219         Assert.assertEquals("<p>Universe 42</p>\n", output);
1220     }
1221 }