| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.jasper.compiler; |
| |
| import org.apache.jasper.JasperException; |
| import org.apache.jasper.compiler.ELNode.ELText; |
| import org.apache.jasper.compiler.ELNode.Function; |
| import org.apache.jasper.compiler.ELNode.Root; |
| import org.apache.jasper.compiler.ELNode.Text; |
| |
| /** |
| * This class implements a parser for EL expressions. |
| * |
| * It takes strings of the form xxx${..}yyy${..}zzz etc, and turn it into a |
| * ELNode.Nodes. |
| * |
| * Currently, it only handles text outside ${..} and functions in ${ ..}. |
| * |
| * @author Kin-man Chung |
| */ |
| |
| public class ELParser { |
| |
| private Token curToken; // current token |
| private Token prevToken; // previous token |
| private String whiteSpace = ""; |
| |
| private final ELNode.Nodes expr; |
| |
| private ELNode.Nodes ELexpr; |
| |
| private int index; // Current index of the expression |
| |
| private final String expression; // The EL expression |
| |
| private char type; |
| |
| private final boolean isDeferredSyntaxAllowedAsLiteral; |
| |
| private static final String reservedWords[] = { "and", "div", "empty", |
| "eq", "false", "ge", "gt", "instanceof", "le", "lt", "mod", "ne", |
| "not", "null", "or", "true" }; |
| |
| public ELParser(String expression, boolean isDeferredSyntaxAllowedAsLiteral) { |
| index = 0; |
| this.expression = expression; |
| this.isDeferredSyntaxAllowedAsLiteral = isDeferredSyntaxAllowedAsLiteral; |
| expr = new ELNode.Nodes(); |
| } |
| |
| /** |
| * Parse an EL expression |
| * |
| * @param expression |
| * The input expression string of the form Char* ('${' Char* |
| * '}')* Char* |
| * @param isDeferredSyntaxAllowedAsLiteral |
| * Are deferred expressions treated as literals? |
| * @return Parsed EL expression in ELNode.Nodes |
| */ |
| public static ELNode.Nodes parse(String expression, |
| boolean isDeferredSyntaxAllowedAsLiteral) { |
| ELParser parser = new ELParser(expression, |
| isDeferredSyntaxAllowedAsLiteral); |
| while (parser.hasNextChar()) { |
| String text = parser.skipUntilEL(); |
| if (text.length() > 0) { |
| parser.expr.add(new ELNode.Text(text)); |
| } |
| ELNode.Nodes elexpr = parser.parseEL(); |
| if (!elexpr.isEmpty()) { |
| parser.expr.add(new ELNode.Root(elexpr, parser.type)); |
| } |
| } |
| return parser.expr; |
| } |
| |
| /** |
| * Parse an EL expression string '${...}'. Currently only separates the EL |
| * into functions and everything else. |
| * |
| * @return An ELNode.Nodes representing the EL expression |
| * |
| * Note: This can not be refactored to use the standard EL implementation as |
| * the EL API does not provide the level of access required to the |
| * parsed expression. |
| */ |
| private ELNode.Nodes parseEL() { |
| |
| StringBuilder buf = new StringBuilder(); |
| ELexpr = new ELNode.Nodes(); |
| curToken = null; |
| prevToken = null; |
| int openBraces = 0; |
| while (hasNext()) { |
| curToken = nextToken(); |
| if (curToken instanceof Char) { |
| if (curToken.toChar() == '}') { |
| openBraces--; |
| if (openBraces < 0) { |
| break; |
| } |
| } else if (curToken.toChar() == '{') { |
| openBraces++; |
| } |
| buf.append(curToken.toString()); |
| } else { |
| // Output whatever is in buffer |
| if (buf.length() > 0) { |
| ELexpr.add(new ELNode.ELText(buf.toString())); |
| buf.setLength(0); |
| } |
| if (!parseFunction()) { |
| ELexpr.add(new ELNode.ELText(curToken.toString())); |
| } |
| } |
| } |
| if (curToken != null) { |
| buf.append(curToken.getWhiteSpace()); |
| } |
| if (buf.length() > 0) { |
| ELexpr.add(new ELNode.ELText(buf.toString())); |
| } |
| |
| return ELexpr; |
| } |
| |
| /** |
| * Parse for a function FunctionInvokation ::= (identifier ':')? identifier |
| * '(' (Expression (,Expression)*)? ')' Note: currently we don't parse |
| * arguments |
| */ |
| private boolean parseFunction() { |
| if (!(curToken instanceof Id) || isELReserved(curToken.toTrimmedString()) || |
| prevToken instanceof Char && prevToken.toChar() == '.') { |
| return false; |
| } |
| String s1 = null; // Function prefix |
| String s2 = curToken.toTrimmedString(); // Function name |
| int start = index - curToken.toString().length(); |
| Token original = curToken; |
| if (hasNext()) { |
| int mark = getIndex() - whiteSpace.length(); |
| curToken = nextToken(); |
| if (curToken.toChar() == ':') { |
| if (hasNext()) { |
| Token t2 = nextToken(); |
| if (t2 instanceof Id) { |
| s1 = s2; |
| s2 = t2.toTrimmedString(); |
| if (hasNext()) { |
| curToken = nextToken(); |
| } |
| } |
| } |
| } |
| if (curToken.toChar() == '(') { |
| ELexpr.add(new ELNode.Function(s1, s2, expression.substring(start, index - 1))); |
| return true; |
| } |
| curToken = original; |
| setIndex(mark); |
| } |
| return false; |
| } |
| |
| /** |
| * Test if an id is a reserved word in EL |
| */ |
| private boolean isELReserved(String id) { |
| int i = 0; |
| int j = reservedWords.length; |
| while (i < j) { |
| int k = (i + j) / 2; |
| int result = reservedWords[k].compareTo(id); |
| if (result == 0) { |
| return true; |
| } |
| if (result < 0) { |
| i = k + 1; |
| } else { |
| j = k; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Skip until an EL expression ('${' || '#{') is reached, allowing escape |
| * sequences '\$' and '\#'. |
| * |
| * @return The text string up to the EL expression |
| */ |
| private String skipUntilEL() { |
| StringBuilder buf = new StringBuilder(); |
| while (hasNextChar()) { |
| char ch = nextChar(); |
| if (ch == '\\') { |
| // Is this the start of a "\$" or "\#" escape sequence? |
| char p0 = peek(0); |
| if (p0 == '$' || (p0 == '#' && !isDeferredSyntaxAllowedAsLiteral)) { |
| buf.append(nextChar()); |
| } else { |
| buf.append(ch); |
| } |
| } else if ((ch == '$' || (ch == '#' && !isDeferredSyntaxAllowedAsLiteral)) && |
| peek(0) == '{') { |
| this.type = ch; |
| nextChar(); |
| break; |
| } else { |
| buf.append(ch); |
| } |
| } |
| return buf.toString(); |
| } |
| |
| |
| /** |
| * Escape '$' and '#', inverting the unescaping performed in |
| * {@link #skipUntilEL()} but only for ${ and #{ sequences since escaping |
| * for $ and # is optional. |
| * |
| * @param input Non-EL input to be escaped |
| * @param isDeferredSyntaxAllowedAsLiteral |
| * |
| * @return The escaped version of the input |
| */ |
| static String escapeLiteralExpression(String input, |
| boolean isDeferredSyntaxAllowedAsLiteral) { |
| int len = input.length(); |
| int lastAppend = 0; |
| StringBuilder output = null; |
| for (int i = 0; i < len; i++) { |
| char ch = input.charAt(i); |
| if (ch =='$' || (!isDeferredSyntaxAllowedAsLiteral && ch == '#')) { |
| if (i + 1 < len && input.charAt(i + 1) == '{') { |
| if (output == null) { |
| output = new StringBuilder(len + 20); |
| } |
| output.append(input.substring(lastAppend, i)); |
| lastAppend = i + 1; |
| output.append('\\'); |
| output.append(ch); |
| } |
| } |
| } |
| if (output == null) { |
| return input; |
| } else { |
| output.append(input.substring(lastAppend, len)); |
| return output.toString(); |
| } |
| } |
| |
| |
| /** |
| * Escape '\\', '\'' and '\"', inverting the unescaping performed in |
| * {@link #skipUntilEL()}. |
| * |
| * @param input Non-EL input to be escaped |
| * @param isDeferredSyntaxAllowedAsLiteral |
| * |
| * @return The escaped version of the input |
| */ |
| private static String escapeELText(String input) { |
| int len = input.length(); |
| char quote = 0; |
| int lastAppend = 0; |
| int start = 0; |
| int end = len; |
| |
| // Look to see if the value is quoted |
| String trimmed = input.trim(); |
| int trimmedLen = trimmed.length(); |
| if (trimmedLen > 1) { |
| // Might be quoted |
| quote = trimmed.charAt(0); |
| if (quote == '\'' || quote == '\"') { |
| if (trimmed.charAt(trimmedLen - 1) != quote) { |
| throw new IllegalArgumentException(Localizer.getMessage( |
| "org.apache.jasper.compiler.ELParser.invalidQuotesForStringLiteral", |
| input)); |
| } |
| start = input.indexOf(quote) + 1; |
| end = start + trimmedLen - 2; |
| } else { |
| quote = 0; |
| } |
| } |
| |
| StringBuilder output = null; |
| for (int i = start; i < end; i++) { |
| char ch = input.charAt(i); |
| if (ch == '\\' || ch == quote) { |
| if (output == null) { |
| output = new StringBuilder(len + 20); |
| } |
| output.append(input.substring(lastAppend, i)); |
| lastAppend = i + 1; |
| output.append('\\'); |
| output.append(ch); |
| } |
| } |
| if (output == null) { |
| return input; |
| } else { |
| output.append(input.substring(lastAppend, len)); |
| return output.toString(); |
| } |
| } |
| |
| |
| /* |
| * @return true if there is something left in EL expression buffer other |
| * than white spaces. |
| */ |
| private boolean hasNext() { |
| skipSpaces(); |
| return hasNextChar(); |
| } |
| |
| private String getAndResetWhiteSpace() { |
| String result = whiteSpace; |
| whiteSpace = ""; |
| return result; |
| } |
| |
| /* |
| * Implementation note: This method assumes that it is always preceded by a |
| * call to hasNext() in order for whitespace handling to be correct. |
| * |
| * @return The next token in the EL expression buffer. |
| */ |
| private Token nextToken() { |
| prevToken = curToken; |
| if (hasNextChar()) { |
| char ch = nextChar(); |
| if (Character.isJavaIdentifierStart(ch)) { |
| int start = index - 1; |
| while (index < expression.length() && |
| Character.isJavaIdentifierPart( |
| ch = expression.charAt(index))) { |
| nextChar(); |
| } |
| return new Id(getAndResetWhiteSpace(), expression.substring(start, index)); |
| } |
| |
| if (ch == '\'' || ch == '"') { |
| return parseQuotedChars(ch); |
| } else { |
| // For now... |
| return new Char(getAndResetWhiteSpace(), ch); |
| } |
| } |
| return null; |
| } |
| |
| /* |
| * Parse a string in single or double quotes, allowing for escape sequences |
| * '\\', '\"' and "\'" |
| */ |
| private Token parseQuotedChars(char quote) { |
| StringBuilder buf = new StringBuilder(); |
| buf.append(quote); |
| while (hasNextChar()) { |
| char ch = nextChar(); |
| if (ch == '\\') { |
| ch = nextChar(); |
| if (ch == '\\' || ch == '\'' || ch == '\"') { |
| buf.append(ch); |
| } else { |
| throw new IllegalArgumentException(Localizer.getMessage( |
| "org.apache.jasper.compiler.ELParser.invalidQuoting", |
| expression)); |
| } |
| } else if (ch == quote) { |
| buf.append(ch); |
| break; |
| } else { |
| buf.append(ch); |
| } |
| } |
| return new QuotedString(getAndResetWhiteSpace(), buf.toString()); |
| } |
| |
| /* |
| * A collection of low level parse methods dealing with character in the EL |
| * expression buffer. |
| */ |
| |
| private void skipSpaces() { |
| int start = index; |
| while (hasNextChar()) { |
| char c = expression.charAt(index); |
| if (c > ' ') |
| break; |
| index++; |
| } |
| whiteSpace = expression.substring(start, index); |
| } |
| |
| private boolean hasNextChar() { |
| return index < expression.length(); |
| } |
| |
| private char nextChar() { |
| if (index >= expression.length()) { |
| return (char) -1; |
| } |
| return expression.charAt(index++); |
| } |
| |
| private char peek(int advance) { |
| int target = index + advance; |
| if (target >= expression.length()) { |
| return (char) -1; |
| } |
| return expression.charAt(target); |
| } |
| |
| private int getIndex() { |
| return index; |
| } |
| |
| private void setIndex(int i) { |
| index = i; |
| } |
| |
| /* |
| * Represents a token in EL expression string |
| */ |
| private static class Token { |
| |
| protected final String whiteSpace; |
| |
| Token(String whiteSpace) { |
| this.whiteSpace = whiteSpace; |
| } |
| |
| char toChar() { |
| return 0; |
| } |
| |
| @Override |
| public String toString() { |
| return whiteSpace; |
| } |
| |
| String toTrimmedString() { |
| return ""; |
| } |
| |
| String getWhiteSpace() { |
| return whiteSpace; |
| } |
| } |
| |
| /* |
| * Represents an ID token in EL |
| */ |
| private static class Id extends Token { |
| String id; |
| |
| Id(String whiteSpace, String id) { |
| super(whiteSpace); |
| this.id = id; |
| } |
| |
| @Override |
| public String toString() { |
| return whiteSpace + id; |
| } |
| |
| @Override |
| String toTrimmedString() { |
| return id; |
| } |
| } |
| |
| /* |
| * Represents a character token in EL |
| */ |
| private static class Char extends Token { |
| |
| private char ch; |
| |
| Char(String whiteSpace, char ch) { |
| super(whiteSpace); |
| this.ch = ch; |
| } |
| |
| @Override |
| char toChar() { |
| return ch; |
| } |
| |
| @Override |
| public String toString() { |
| return whiteSpace + ch; |
| } |
| |
| @Override |
| String toTrimmedString() { |
| return "" + ch; |
| } |
| } |
| |
| /* |
| * Represents a quoted (single or double) string token in EL |
| */ |
| private static class QuotedString extends Token { |
| |
| private String value; |
| |
| QuotedString(String whiteSpace, String v) { |
| super(whiteSpace); |
| this.value = v; |
| } |
| |
| @Override |
| public String toString() { |
| return whiteSpace + value; |
| } |
| |
| @Override |
| String toTrimmedString() { |
| return value; |
| } |
| } |
| |
| public char getType() { |
| return type; |
| } |
| |
| |
| static class TextBuilder extends ELNode.Visitor { |
| |
| protected final boolean isDeferredSyntaxAllowedAsLiteral; |
| protected final StringBuilder output = new StringBuilder(); |
| |
| protected TextBuilder(boolean isDeferredSyntaxAllowedAsLiteral) { |
| this.isDeferredSyntaxAllowedAsLiteral = isDeferredSyntaxAllowedAsLiteral; |
| } |
| |
| public String getText() { |
| return output.toString(); |
| } |
| |
| @Override |
| public void visit(Root n) throws JasperException { |
| output.append(n.getType()); |
| output.append('{'); |
| n.getExpression().visit(this); |
| output.append('}'); |
| } |
| |
| @Override |
| public void visit(Function n) throws JasperException { |
| output.append(escapeLiteralExpression(n.getOriginalText(), isDeferredSyntaxAllowedAsLiteral)); |
| output.append('('); |
| } |
| |
| @Override |
| public void visit(Text n) throws JasperException { |
| output.append(escapeLiteralExpression(n.getText(),isDeferredSyntaxAllowedAsLiteral)); |
| } |
| |
| @Override |
| public void visit(ELText n) throws JasperException { |
| output.append(escapeELText(n.getText())); |
| } |
| } |
| } |