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}