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.parser;
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  
24  import org.apache.commons.jexl3.JexlException;
25  import org.apache.commons.jexl3.JexlFeatures;
26  import org.junit.jupiter.api.Test;
27  
28  /**
29   */
30  class ParserTest {
31      static final JexlFeatures FEATURES = new JexlFeatures();
32      public ParserTest() {}
33  
34      /**
35       * Test the escaped control characters.
36       */
37      @Test
38      void testControlCharacters() {
39          // Both '' and "" are valid JEXL string
40          // The array of tuples where the first element is an expected result and the second element is a test string.
41          final String[][] strings = {
42              new String[] {"a\nb\tc", "'a\nb\tc'"}, // we still honor the actual characters
43              new String[] {"a\nb\tc", "'a\\nb\\tc'"},
44              new String[] {"a\nb\tc", "\"a\\nb\\tc\""},
45              new String[] {"\b\t\n\f\r", "'\\b\\t\\n\\f\\r'"},
46              new String[] {"'hi'", "'\\'hi\\''"},
47              new String[] {"\"hi\"", "'\"hi\"'"},
48              new String[] {"\"hi\"", "'\"hi\"'"},
49          };
50          for(final String[] pair: strings) {
51              final String output = StringParser.buildString(pair[1], true);
52              assertEquals(pair[0], output);
53          }
54      }
55  
56      /**
57       * Test unicode escape sequences: only 4 hex digits (0-9, a-f, A-F) form a unicode char;
58       * an out-of-range digit (g/h/...) is not a valid escape and passes through literally.
59       */
60      @Test
61      void testUnicodeEscape() {
62          // valid unicode escapes decode to the expected character
63          assertEquals("A", StringParser.buildString("'\\u0041'", true));
64          assertEquals(String.valueOf((char) 0x00ff), StringParser.buildString("'\\u00ff'", true));
65          assertEquals(String.valueOf((char) 0x00ff), StringParser.buildString("'\\u00FF'", true));
66          assertEquals("Z9", StringParser.buildString("'\\u005a\\u0039'", true));
67          // out-of-range hex digits ('g'/'h'/'G'/'H') are NOT consumed as a unicode escape:
68          // the escape is not recognized, so the '\\u' is re-emitted followed by the raw characters.
69          assertEquals("\\ug000", StringParser.buildString("'\\ug000'", true));
70          assertEquals("\\u00fh", StringParser.buildString("'\\u00fh'", true));
71          assertEquals("\\uH000", StringParser.buildString("'\\uH000'", true));
72      }
73  
74      @Test
75      void testErrorAmbiguous() throws Exception {
76          final Parser parser = new Parser(";");
77          assertThrows(JexlException.Ambiguous.class, () -> parser.parse(null, FEATURES, "x = 1 y = 5", null), "should have failed on ambiguous statement");
78      }
79  
80      @Test
81      void testErrorAssign() throws Exception {
82          final String[] ops = { "=", "+=", "-=", "/=", "*=", "^=", "&=", "|=" };
83          for (final String op : ops) {
84              final Parser parser = new Parser(";");
85              final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> parser.parse(null, FEATURES, "foo() " + op + " 1;", null),
86                      () -> "should have failed on invalid assignment " + op);
87              assertNotNull(xparse.getDetail());
88              assertNotNull(xparse.toString());
89          }
90      }
91  
92      @Test
93      void testIdentifierEscape() {
94          final String[] ids = {"a\\ b", "a\\ b\\ c", "a\\'b\\\"c", "a\\ \\ c"};
95          for(final String id : ids) {
96              final String esc0 = StringParser.unescapeIdentifier(id);
97              assertFalse(esc0.contains("\\"));
98              final String esc1 = StringParser.escapeIdentifier(esc0);
99              assertEquals(id, esc1);
100         }
101     }
102 
103     /**
104      * See if we can parse simple scripts
105      */
106     @Test
107     void testParse() throws Exception {
108         final Parser parser = new Parser(";");
109         JexlNode sn;
110         sn = parser.parse(null, FEATURES, "foo = 1;", null);
111         assertNotNull(sn, "parsed node is null");
112 
113         sn = parser.parse(null, FEATURES, "foo = \"bar\";", null);
114         assertNotNull(sn, "parsed node is null");
115 
116         sn = parser.parse(null, FEATURES, "foo = 'bar';", null);
117         assertNotNull(sn, "parsed node is null");
118     }
119 }