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  
18  package org.apache.struts2.jasper.compiler;
19  
20  /***
21   * This class implements a parser for EL expressions.
22   * <p/>
23   * It takes strings of the form xxx${..}yyy${..}zzz etc, and turn it into
24   * a ELNode.Nodes.
25   * <p/>
26   * Currently, it only handles text outside ${..} and functions in ${ ..}.
27   *
28   * @author Kin-man Chung
29   */
30  
31  public class ELParser {
32  
33      private Token curToken;        // current token
34      private ELNode.Nodes expr;
35      private ELNode.Nodes ELexpr;
36      private int index;                // Current index of the expression
37      private String expression;        // The EL expression
38      private boolean escapeBS;        // is '\' an escape char in text outside EL?
39  
40      private static final String reservedWords[] = {
41              "and", "div", "empty", "eq", "false",
42              "ge", "gt", "instanceof", "le", "lt", "mod",
43              "ne", "not", "null", "or", "true"};
44  
45      public ELParser(String expression) {
46          index = 0;
47          this.expression = expression;
48          expr = new ELNode.Nodes();
49      }
50  
51      /***
52       * Parse an EL expression
53       *
54       * @param expression The input expression string of the form
55       *                   Char* ('${' Char* '}')* Char*
56       * @return Parsed EL expression in ELNode.Nodes
57       */
58      public static ELNode.Nodes parse(String expression) {
59          ELParser parser = new ELParser(expression);
60          while (parser.hasNextChar()) {
61              String text = parser.skipUntilEL();
62              if (text.length() > 0) {
63                  parser.expr.add(new ELNode.Text(text));
64              }
65              ELNode.Nodes elexpr = parser.parseEL();
66              if (!elexpr.isEmpty()) {
67                  parser.expr.add(new ELNode.Root(elexpr));
68              }
69          }
70          return parser.expr;
71      }
72  
73      /***
74       * Parse an EL expression string '${...}'
75       *
76       * @return An ELNode.Nodes representing the EL expression
77       *         TODO: Currently only parsed into functions and text strings.  This
78       *         should be rewritten for a full parser.
79       */
80      private ELNode.Nodes parseEL() {
81  
82          StringBuffer buf = new StringBuffer();
83          ELexpr = new ELNode.Nodes();
84          while (hasNext()) {
85              curToken = nextToken();
86              if (curToken instanceof Char) {
87                  if (curToken.toChar() == '}') {
88                      break;
89                  }
90                  buf.append(curToken.toChar());
91              } else {
92                  // Output whatever is in buffer
93                  if (buf.length() > 0) {
94                      ELexpr.add(new ELNode.ELText(buf.toString()));
95                  }
96                  if (!parseFunction()) {
97                      ELexpr.add(new ELNode.ELText(curToken.toString()));
98                  }
99              }
100         }
101         if (buf.length() > 0) {
102             ELexpr.add(new ELNode.ELText(buf.toString()));
103         }
104 
105         return ELexpr;
106     }
107 
108     /***
109      * Parse for a function
110      * FunctionInvokation ::= (identifier ':')? identifier '('
111      * (Expression (,Expression)*)? ')'
112      * Note: currently we don't parse arguments
113      */
114     private boolean parseFunction() {
115         if (!(curToken instanceof Id) || isELReserved(curToken.toString())) {
116             return false;
117         }
118         String s1 = null;                 // Function prefix
119         String s2 = curToken.toString();  // Function name
120         int mark = getIndex();
121         if (hasNext()) {
122             Token t = nextToken();
123             if (t.toChar() == ':') {
124                 if (hasNext()) {
125                     Token t2 = nextToken();
126                     if (t2 instanceof Id) {
127                         s1 = s2;
128                         s2 = t2.toString();
129                         if (hasNext()) {
130                             t = nextToken();
131                         }
132                     }
133                 }
134             }
135             if (t.toChar() == '(') {
136                 ELexpr.add(new ELNode.Function(s1, s2));
137                 return true;
138             }
139         }
140         setIndex(mark);
141         return false;
142     }
143 
144     /***
145      * Test if an id is a reserved word in EL
146      */
147     private boolean isELReserved(String id) {
148         int i = 0;
149         int j = reservedWords.length;
150         while (i < j) {
151             int k = (i + j) / 2;
152             int result = reservedWords[k].compareTo(id);
153             if (result == 0) {
154                 return true;
155             }
156             if (result < 0) {
157                 i = k + 1;
158             } else {
159                 j = k;
160             }
161         }
162         return false;
163     }
164 
165     /***
166      * Skip until an EL expression ('${') is reached, allowing escape sequences
167      * '//' and '\$'.
168      *
169      * @return The text string up to the EL expression
170      */
171     private String skipUntilEL() {
172         char prev = 0;
173         StringBuffer buf = new StringBuffer();
174         while (hasNextChar()) {
175             char ch = nextChar();
176             if (prev == '//') {
177                 prev = 0;
178                 if (ch == '//') {
179                     buf.append('//');
180                     if (!escapeBS)
181                         prev = '//';
182                 } else if (ch == '$') {
183                     buf.append('$');
184                 }
185                 // else error!
186             } else if (prev == '$') {
187                 if (ch == '{') {
188                     prev = 0;
189                     break;
190                 }
191                 buf.append('$');
192                 buf.append(ch);
193                 prev = 0;
194             } else if (ch == '//' || ch == '$') {
195                 prev = ch;
196             } else {
197                 buf.append(ch);
198             }
199         }
200         if (prev != 0) {
201             buf.append(prev);
202         }
203         return buf.toString();
204     }
205 
206     /*
207      * @return true if there is something left in EL expression buffer other
208      *         than white spaces.
209      */
210     private boolean hasNext() {
211         skipSpaces();
212         return hasNextChar();
213     }
214 
215     /*
216      * @return The next token in the EL expression buffer.
217      */
218     private Token nextToken() {
219         skipSpaces();
220         if (hasNextChar()) {
221             char ch = nextChar();
222             if (Character.isJavaIdentifierStart(ch)) {
223                 StringBuffer buf = new StringBuffer();
224                 buf.append(ch);
225                 while ((ch = peekChar()) != -1 &&
226                         Character.isJavaIdentifierPart(ch)) {
227                     buf.append(ch);
228                     nextChar();
229                 }
230                 return new Id(buf.toString());
231             }
232 
233             if (ch == '\'' || ch == '"') {
234                 return parseQuotedChars(ch);
235             } else {
236                 // For now...
237                 return new Char(ch);
238             }
239         }
240         return null;
241     }
242 
243     /*
244      * Parse a string in single or double quotes, allowing for escape sequences
245      * '//', and ('\"', or "\'")
246      */
247     private Token parseQuotedChars(char quote) {
248         StringBuffer buf = new StringBuffer();
249         buf.append(quote);
250         while (hasNextChar()) {
251             char ch = nextChar();
252             if (ch == '//') {
253                 ch = nextChar();
254                 if (ch == '//' || ch == quote) {
255                     buf.append(ch);
256                 }
257                 // else error!
258             } else if (ch == quote) {
259                 buf.append(ch);
260                 break;
261             } else {
262                 buf.append(ch);
263             }
264         }
265         return new QuotedString(buf.toString());
266     }
267 
268     /*
269      * A collection of low level parse methods dealing with character in
270      * the EL expression buffer.
271      */
272 
273     private void skipSpaces() {
274         while (hasNextChar()) {
275             if (expression.charAt(index) > ' ')
276                 break;
277             index++;
278         }
279     }
280 
281     private boolean hasNextChar() {
282         return index < expression.length();
283     }
284 
285     private char nextChar() {
286         if (index >= expression.length()) {
287             return (char) -1;
288         }
289         return expression.charAt(index++);
290     }
291 
292     private char peekChar() {
293         if (index >= expression.length()) {
294             return (char) -1;
295         }
296         return expression.charAt(index);
297     }
298 
299     private int getIndex() {
300         return index;
301     }
302 
303     private void setIndex(int i) {
304         index = i;
305     }
306 
307     /*
308      * Represents a token in EL expression string
309      */
310     private static class Token {
311 
312         char toChar() {
313             return 0;
314         }
315 
316         public String toString() {
317             return "";
318         }
319     }
320 
321     /*
322      * Represents an ID token in EL
323      */
324     private static class Id extends Token {
325         String id;
326 
327         Id(String id) {
328             this.id = id;
329         }
330 
331         public String toString() {
332             return id;
333         }
334     }
335 
336     /*
337      * Represents a character token in EL
338      */
339     private static class Char extends Token {
340 
341         private char ch;
342 
343         Char(char ch) {
344             this.ch = ch;
345         }
346 
347         char toChar() {
348             return ch;
349         }
350 
351         public String toString() {
352             return (new Character(ch)).toString();
353         }
354     }
355 
356     /*
357      * Represents a quoted (single or double) string token in EL
358      */
359     private static class QuotedString extends Token {
360 
361         private String value;
362 
363         QuotedString(String v) {
364             this.value = v;
365         }
366 
367         public String toString() {
368             return value;
369         }
370     }
371 }
372