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