View Javadoc
1   /*
2    * Copyright 2002-2013 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.expression.common;
18  
19  import java.util.LinkedList;
20  import java.util.List;
21  import java.util.Stack;
22  
23  import org.springframework.expression.Expression;
24  import org.springframework.expression.ExpressionParser;
25  import org.springframework.expression.ParseException;
26  import org.springframework.expression.ParserContext;
27  
28  /**
29   * An expression parser that understands templates. It can be subclassed by expression
30   * parsers that do not offer first class support for templating.
31   *
32   * @author Keith Donald
33   * @author Juergen Hoeller
34   * @author Andy Clement
35   * @since 3.0
36   */
37  public abstract class TemplateAwareExpressionParser implements ExpressionParser {
38  
39  	/**
40  	 * Default ParserContext instance for non-template expressions.
41  	 */
42  	private static final ParserContext NON_TEMPLATE_PARSER_CONTEXT = new ParserContext() {
43  
44  		@Override
45  		public String getExpressionPrefix() {
46  			return null;
47  		}
48  
49  		@Override
50  		public String getExpressionSuffix() {
51  			return null;
52  		}
53  
54  		@Override
55  		public boolean isTemplate() {
56  			return false;
57  		}
58  	};
59  
60  	@Override
61  	public Expression parseExpression(String expressionString) throws ParseException {
62  		return parseExpression(expressionString, NON_TEMPLATE_PARSER_CONTEXT);
63  	}
64  
65  	@Override
66  	public Expression parseExpression(String expressionString, ParserContext context)
67  			throws ParseException {
68  		if (context == null) {
69  			context = NON_TEMPLATE_PARSER_CONTEXT;
70  		}
71  
72  		if (context.isTemplate()) {
73  			return parseTemplate(expressionString, context);
74  		}
75  		else {
76  			return doParseExpression(expressionString, context);
77  		}
78  	}
79  
80  	private Expression parseTemplate(String expressionString, ParserContext context)
81  			throws ParseException {
82  		if (expressionString.length() == 0) {
83  			return new LiteralExpression("");
84  		}
85  		Expression[] expressions = parseExpressions(expressionString, context);
86  		if (expressions.length == 1) {
87  			return expressions[0];
88  		}
89  		else {
90  			return new CompositeStringExpression(expressionString, expressions);
91  		}
92  	}
93  
94  	/**
95  	 * Helper that parses given expression string using the configured parser. The
96  	 * expression string can contain any number of expressions all contained in "${...}"
97  	 * markers. For instance: "foo${expr0}bar${expr1}". The static pieces of text will
98  	 * also be returned as Expressions that just return that static piece of text. As a
99  	 * result, evaluating all returned expressions and concatenating the results produces
100 	 * the complete evaluated string. Unwrapping is only done of the outermost delimiters
101 	 * found, so the string 'hello ${foo${abc}}' would break into the pieces 'hello ' and
102 	 * 'foo${abc}'. This means that expression languages that used ${..} as part of their
103 	 * functionality are supported without any problem. The parsing is aware of the
104 	 * structure of an embedded expression. It assumes that parentheses '(', square
105 	 * brackets '[' and curly brackets '}' must be in pairs within the expression unless
106 	 * they are within a string literal and a string literal starts and terminates with a
107 	 * single quote '.
108 	 * @param expressionString the expression string
109 	 * @return the parsed expressions
110 	 * @throws ParseException when the expressions cannot be parsed
111 	 */
112 	private Expression[] parseExpressions(String expressionString, ParserContext context)
113 			throws ParseException {
114 		List<Expression> expressions = new LinkedList<Expression>();
115 		String prefix = context.getExpressionPrefix();
116 		String suffix = context.getExpressionSuffix();
117 		int startIdx = 0;
118 		while (startIdx < expressionString.length()) {
119 			int prefixIndex = expressionString.indexOf(prefix, startIdx);
120 			if (prefixIndex >= startIdx) {
121 				// an inner expression was found - this is a composite
122 				if (prefixIndex > startIdx) {
123 					expressions.add(createLiteralExpression(context,
124 							expressionString.substring(startIdx, prefixIndex)));
125 				}
126 				int afterPrefixIndex = prefixIndex + prefix.length();
127 				int suffixIndex = skipToCorrectEndSuffix(prefix, suffix,
128 						expressionString, afterPrefixIndex);
129 
130 				if (suffixIndex == -1) {
131 					throw new ParseException(expressionString, prefixIndex,
132 							"No ending suffix '" + suffix
133 									+ "' for expression starting at character "
134 									+ prefixIndex + ": "
135 									+ expressionString.substring(prefixIndex));
136 				}
137 
138 				if (suffixIndex == afterPrefixIndex) {
139 					throw new ParseException(expressionString, prefixIndex,
140 							"No expression defined within delimiter '" + prefix + suffix
141 									+ "' at character " + prefixIndex);
142 				}
143 
144 				String expr = expressionString.substring(prefixIndex + prefix.length(),
145 						suffixIndex);
146 				expr = expr.trim();
147 
148 				if (expr.length() == 0) {
149 					throw new ParseException(expressionString, prefixIndex,
150 							"No expression defined within delimiter '" + prefix + suffix
151 									+ "' at character " + prefixIndex);
152 				}
153 
154 				expressions.add(doParseExpression(expr, context));
155 				startIdx = suffixIndex + suffix.length();
156 			}
157 			else {
158 				// no more ${expressions} found in string, add rest as static text
159 				expressions.add(createLiteralExpression(context,
160 						expressionString.substring(startIdx)));
161 				startIdx = expressionString.length();
162 			}
163 		}
164 		return expressions.toArray(new Expression[expressions.size()]);
165 	}
166 
167 	private Expression createLiteralExpression(ParserContext context, String text) {
168 		return new LiteralExpression(text);
169 	}
170 
171 	/**
172 	 * Return true if the specified suffix can be found at the supplied position in the
173 	 * supplied expression string.
174 	 * @param expressionString the expression string which may contain the suffix
175 	 * @param pos the start position at which to check for the suffix
176 	 * @param suffix the suffix string
177 	 */
178 	private boolean isSuffixHere(String expressionString, int pos, String suffix) {
179 		int suffixPosition = 0;
180 		for (int i = 0; i < suffix.length() && pos < expressionString.length(); i++) {
181 			if (expressionString.charAt(pos++) != suffix.charAt(suffixPosition++)) {
182 				return false;
183 			}
184 		}
185 		if (suffixPosition != suffix.length()) {
186 			// the expressionString ran out before the suffix could entirely be found
187 			return false;
188 		}
189 		return true;
190 	}
191 
192 	/**
193 	 * Copes with nesting, for example '${...${...}}' where the correct end for the first
194 	 * ${ is the final }.
195 	 * @param prefix the prefix
196 	 * @param suffix the suffix
197 	 * @param expressionString the expression string
198 	 * @param afterPrefixIndex the most recently found prefix location for which the
199 	 *        matching end suffix is being sought
200 	 * @return the position of the correct matching nextSuffix or -1 if none can be found
201 	 */
202 	private int skipToCorrectEndSuffix(String prefix, String suffix,
203 			String expressionString, int afterPrefixIndex) throws ParseException {
204 		// Chew on the expression text - relying on the rules:
205 		// brackets must be in pairs: () [] {}
206 		// string literals are "..." or '...' and these may contain unmatched brackets
207 		int pos = afterPrefixIndex;
208 		int maxlen = expressionString.length();
209 		int nextSuffix = expressionString.indexOf(suffix, afterPrefixIndex);
210 		if (nextSuffix == -1) {
211 			return -1; // the suffix is missing
212 		}
213 		Stack<Bracket> stack = new Stack<Bracket>();
214 		while (pos < maxlen) {
215 			if (isSuffixHere(expressionString, pos, suffix) && stack.isEmpty()) {
216 				break;
217 			}
218 			char ch = expressionString.charAt(pos);
219 			switch (ch) {
220 				case '{':
221 				case '[':
222 				case '(':
223 					stack.push(new Bracket(ch, pos));
224 					break;
225 				case '}':
226 				case ']':
227 				case ')':
228 					if (stack.isEmpty()) {
229 						throw new ParseException(expressionString, pos, "Found closing '"
230 								+ ch + "' at position " + pos + " without an opening '"
231 								+ Bracket.theOpenBracketFor(ch) + "'");
232 					}
233 					Bracket p = stack.pop();
234 					if (!p.compatibleWithCloseBracket(ch)) {
235 						throw new ParseException(expressionString, pos, "Found closing '"
236 								+ ch + "' at position " + pos
237 								+ " but most recent opening is '" + p.bracket
238 								+ "' at position " + p.pos);
239 					}
240 					break;
241 				case '\'':
242 				case '"':
243 					// jump to the end of the literal
244 					int endLiteral = expressionString.indexOf(ch, pos + 1);
245 					if (endLiteral == -1) {
246 						throw new ParseException(expressionString, pos,
247 								"Found non terminating string literal starting at position "
248 										+ pos);
249 					}
250 					pos = endLiteral;
251 					break;
252 			}
253 			pos++;
254 		}
255 		if (!stack.isEmpty()) {
256 			Bracket p = stack.pop();
257 			throw new ParseException(expressionString, p.pos, "Missing closing '"
258 					+ Bracket.theCloseBracketFor(p.bracket) + "' for '" + p.bracket
259 					+ "' at position " + p.pos);
260 		}
261 		if (!isSuffixHere(expressionString, pos, suffix)) {
262 			return -1;
263 		}
264 		return pos;
265 	}
266 
267 
268 	/**
269 	 * This captures a type of bracket and the position in which it occurs in the
270 	 * expression. The positional information is used if an error has to be reported
271 	 * because the related end bracket cannot be found. Bracket is used to describe:
272 	 * square brackets [] round brackets () and curly brackets {}
273 	 */
274 	private static class Bracket {
275 
276 		char bracket;
277 
278 		int pos;
279 
280 		Bracket(char bracket, int pos) {
281 			this.bracket = bracket;
282 			this.pos = pos;
283 		}
284 
285 		boolean compatibleWithCloseBracket(char closeBracket) {
286 			if (this.bracket == '{') {
287 				return closeBracket == '}';
288 			}
289 			else if (this.bracket == '[') {
290 				return closeBracket == ']';
291 			}
292 			return closeBracket == ')';
293 		}
294 
295 		static char theOpenBracketFor(char closeBracket) {
296 			if (closeBracket == '}') {
297 				return '{';
298 			}
299 			else if (closeBracket == ']') {
300 				return '[';
301 			}
302 			return '(';
303 		}
304 
305 		static char theCloseBracketFor(char openBracket) {
306 			if (openBracket == '{') {
307 				return '}';
308 			}
309 			else if (openBracket == '[') {
310 				return ']';
311 			}
312 			return ')';
313 		}
314 	}
315 
316 	/**
317 	 * Actually parse the expression string and return an Expression object.
318 	 * @param expressionString the raw expression string to parse
319 	 * @param context a context for influencing this expression parsing routine (optional)
320 	 * @return an evaluator for the parsed expression
321 	 * @throws ParseException an exception occurred during parsing
322 	 */
323 	protected abstract Expression doParseExpression(String expressionString,
324 			ParserContext context) throws ParseException;
325 
326 }