001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.expression.common; 018 019import java.util.LinkedList; 020import java.util.List; 021import java.util.Stack; 022 023import org.springframework.expression.Expression; 024import org.springframework.expression.ExpressionParser; 025import org.springframework.expression.ParseException; 026import org.springframework.expression.ParserContext; 027 028/** 029 * An expression parser that understands templates. It can be subclassed by expression 030 * parsers that do not offer first class support for templating. 031 * 032 * @author Keith Donald 033 * @author Juergen Hoeller 034 * @author Andy Clement 035 * @since 3.0 036 */ 037public abstract class TemplateAwareExpressionParser implements ExpressionParser { 038 039 /** 040 * Default ParserContext instance for non-template expressions. 041 */ 042 private static final ParserContext NON_TEMPLATE_PARSER_CONTEXT = new ParserContext() { 043 @Override 044 public String getExpressionPrefix() { 045 return null; 046 } 047 @Override 048 public String getExpressionSuffix() { 049 return null; 050 } 051 @Override 052 public boolean isTemplate() { 053 return false; 054 } 055 }; 056 057 058 @Override 059 public Expression parseExpression(String expressionString) throws ParseException { 060 return parseExpression(expressionString, NON_TEMPLATE_PARSER_CONTEXT); 061 } 062 063 @Override 064 public Expression parseExpression(String expressionString, ParserContext context) throws ParseException { 065 if (context == null) { 066 context = NON_TEMPLATE_PARSER_CONTEXT; 067 } 068 069 if (context.isTemplate()) { 070 return parseTemplate(expressionString, context); 071 } 072 else { 073 return doParseExpression(expressionString, context); 074 } 075 } 076 077 078 private Expression parseTemplate(String expressionString, ParserContext context) throws ParseException { 079 if (expressionString.isEmpty()) { 080 return new LiteralExpression(""); 081 } 082 083 Expression[] expressions = parseExpressions(expressionString, context); 084 if (expressions.length == 1) { 085 return expressions[0]; 086 } 087 else { 088 return new CompositeStringExpression(expressionString, expressions); 089 } 090 } 091 092 /** 093 * Helper that parses given expression string using the configured parser. The 094 * expression string can contain any number of expressions all contained in "${...}" 095 * markers. For instance: "foo${expr0}bar${expr1}". The static pieces of text will 096 * also be returned as Expressions that just return that static piece of text. As a 097 * result, evaluating all returned expressions and concatenating the results produces 098 * the complete evaluated string. Unwrapping is only done of the outermost delimiters 099 * found, so the string 'hello ${foo${abc}}' would break into the pieces 'hello ' and 100 * 'foo${abc}'. This means that expression languages that used ${..} as part of their 101 * functionality are supported without any problem. The parsing is aware of the 102 * structure of an embedded expression. It assumes that parentheses '(', square 103 * brackets '[' and curly brackets '}' must be in pairs within the expression unless 104 * they are within a string literal and a string literal starts and terminates with a 105 * single quote '. 106 * @param expressionString the expression string 107 * @return the parsed expressions 108 * @throws ParseException when the expressions cannot be parsed 109 */ 110 private Expression[] parseExpressions(String expressionString, ParserContext context) throws ParseException { 111 List<Expression> expressions = new LinkedList<Expression>(); 112 String prefix = context.getExpressionPrefix(); 113 String suffix = context.getExpressionSuffix(); 114 int startIdx = 0; 115 116 while (startIdx < expressionString.length()) { 117 int prefixIndex = expressionString.indexOf(prefix, startIdx); 118 if (prefixIndex >= startIdx) { 119 // an inner expression was found - this is a composite 120 if (prefixIndex > startIdx) { 121 expressions.add(new LiteralExpression(expressionString.substring(startIdx, prefixIndex))); 122 } 123 int afterPrefixIndex = prefixIndex + prefix.length(); 124 int suffixIndex = skipToCorrectEndSuffix(suffix, expressionString, afterPrefixIndex); 125 if (suffixIndex == -1) { 126 throw new ParseException(expressionString, prefixIndex, 127 "No ending suffix '" + suffix + "' for expression starting at character " + 128 prefixIndex + ": " + expressionString.substring(prefixIndex)); 129 } 130 if (suffixIndex == afterPrefixIndex) { 131 throw new ParseException(expressionString, prefixIndex, 132 "No expression defined within delimiter '" + prefix + suffix + 133 "' at character " + prefixIndex); 134 } 135 String expr = expressionString.substring(prefixIndex + prefix.length(), suffixIndex); 136 expr = expr.trim(); 137 if (expr.isEmpty()) { 138 throw new ParseException(expressionString, prefixIndex, 139 "No expression defined within delimiter '" + prefix + suffix + 140 "' at character " + prefixIndex); 141 } 142 expressions.add(doParseExpression(expr, context)); 143 startIdx = suffixIndex + suffix.length(); 144 } 145 else { 146 // no more ${expressions} found in string, add rest as static text 147 expressions.add(new LiteralExpression(expressionString.substring(startIdx))); 148 startIdx = expressionString.length(); 149 } 150 } 151 152 return expressions.toArray(new Expression[expressions.size()]); 153 } 154 155 /** 156 * Return true if the specified suffix can be found at the supplied position in the 157 * supplied expression string. 158 * @param expressionString the expression string which may contain the suffix 159 * @param pos the start position at which to check for the suffix 160 * @param suffix the suffix string 161 */ 162 private boolean isSuffixHere(String expressionString, int pos, String suffix) { 163 int suffixPosition = 0; 164 for (int i = 0; i < suffix.length() && pos < expressionString.length(); i++) { 165 if (expressionString.charAt(pos++) != suffix.charAt(suffixPosition++)) { 166 return false; 167 } 168 } 169 if (suffixPosition != suffix.length()) { 170 // the expressionString ran out before the suffix could entirely be found 171 return false; 172 } 173 return true; 174 } 175 176 /** 177 * Copes with nesting, for example '${...${...}}' where the correct end for the first 178 * ${ is the final }. 179 * @param suffix the suffix 180 * @param expressionString the expression string 181 * @param afterPrefixIndex the most recently found prefix location for which the 182 * matching end suffix is being sought 183 * @return the position of the correct matching nextSuffix or -1 if none can be found 184 */ 185 private int skipToCorrectEndSuffix(String suffix, String expressionString, int afterPrefixIndex) 186 throws ParseException { 187 188 // Chew on the expression text - relying on the rules: 189 // brackets must be in pairs: () [] {} 190 // string literals are "..." or '...' and these may contain unmatched brackets 191 int pos = afterPrefixIndex; 192 int maxlen = expressionString.length(); 193 int nextSuffix = expressionString.indexOf(suffix, afterPrefixIndex); 194 if (nextSuffix == -1) { 195 return -1; // the suffix is missing 196 } 197 Stack<Bracket> stack = new Stack<Bracket>(); 198 while (pos < maxlen) { 199 if (isSuffixHere(expressionString, pos, suffix) && stack.isEmpty()) { 200 break; 201 } 202 char ch = expressionString.charAt(pos); 203 switch (ch) { 204 case '{': 205 case '[': 206 case '(': 207 stack.push(new Bracket(ch, pos)); 208 break; 209 case '}': 210 case ']': 211 case ')': 212 if (stack.isEmpty()) { 213 throw new ParseException(expressionString, pos, "Found closing '" + ch + 214 "' at position " + pos + " without an opening '" + 215 Bracket.theOpenBracketFor(ch) + "'"); 216 } 217 Bracket p = stack.pop(); 218 if (!p.compatibleWithCloseBracket(ch)) { 219 throw new ParseException(expressionString, pos, "Found closing '" + ch + 220 "' at position " + pos + " but most recent opening is '" + p.bracket + 221 "' at position " + p.pos); 222 } 223 break; 224 case '\'': 225 case '"': 226 // jump to the end of the literal 227 int endLiteral = expressionString.indexOf(ch, pos + 1); 228 if (endLiteral == -1) { 229 throw new ParseException(expressionString, pos, 230 "Found non terminating string literal starting at position " + pos); 231 } 232 pos = endLiteral; 233 break; 234 } 235 pos++; 236 } 237 if (!stack.isEmpty()) { 238 Bracket p = stack.pop(); 239 throw new ParseException(expressionString, p.pos, "Missing closing '" + 240 Bracket.theCloseBracketFor(p.bracket) + "' for '" + p.bracket + "' at position " + p.pos); 241 } 242 if (!isSuffixHere(expressionString, pos, suffix)) { 243 return -1; 244 } 245 return pos; 246 } 247 248 249 /** 250 * Actually parse the expression string and return an Expression object. 251 * @param expressionString the raw expression string to parse 252 * @param context a context for influencing this expression parsing routine (optional) 253 * @return an evaluator for the parsed expression 254 * @throws ParseException an exception occurred during parsing 255 */ 256 protected abstract Expression doParseExpression(String expressionString, ParserContext context) 257 throws ParseException; 258 259 260 /** 261 * This captures a type of bracket and the position in which it occurs in the 262 * expression. The positional information is used if an error has to be reported 263 * because the related end bracket cannot be found. Bracket is used to describe: 264 * square brackets [] round brackets () and curly brackets {} 265 */ 266 private static class Bracket { 267 268 char bracket; 269 270 int pos; 271 272 Bracket(char bracket, int pos) { 273 this.bracket = bracket; 274 this.pos = pos; 275 } 276 277 boolean compatibleWithCloseBracket(char closeBracket) { 278 if (this.bracket == '{') { 279 return closeBracket == '}'; 280 } 281 else if (this.bracket == '[') { 282 return closeBracket == ']'; 283 } 284 return closeBracket == ')'; 285 } 286 287 static char theOpenBracketFor(char closeBracket) { 288 if (closeBracket == '}') { 289 return '{'; 290 } 291 else if (closeBracket == ']') { 292 return '['; 293 } 294 return '('; 295 } 296 297 static char theCloseBracketFor(char openBracket) { 298 if (openBracket == '{') { 299 return '}'; 300 } 301 else if (openBracket == '[') { 302 return ']'; 303 } 304 return ')'; 305 } 306 } 307 308}