001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.language.simple;
018
019import java.util.ArrayList;
020import java.util.List;
021import java.util.concurrent.atomic.AtomicInteger;
022
023import org.apache.camel.Expression;
024import org.apache.camel.builder.ExpressionBuilder;
025import org.apache.camel.language.simple.ast.LiteralExpression;
026import org.apache.camel.language.simple.ast.LiteralNode;
027import org.apache.camel.language.simple.ast.SimpleFunctionEnd;
028import org.apache.camel.language.simple.ast.SimpleFunctionStart;
029import org.apache.camel.language.simple.ast.SimpleNode;
030import org.apache.camel.language.simple.ast.UnaryExpression;
031import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
032import org.apache.camel.language.simple.types.SimpleParserException;
033import org.apache.camel.language.simple.types.SimpleToken;
034import org.apache.camel.language.simple.types.TokenType;
035
036/**
037 * A parser to parse simple language as a Camel {@link Expression}
038 */
039public class SimpleExpressionParser extends BaseSimpleParser {
040
041    @Deprecated
042    public SimpleExpressionParser(String expression) {
043        super(expression, true);
044    }
045
046    public SimpleExpressionParser(String expression, boolean allowEscape) {
047        super(expression, allowEscape);
048    }
049
050    public Expression parseExpression() {
051        clear();
052        try {
053            return doParseExpression();
054        } catch (SimpleParserException e) {
055            // catch parser exception and turn that into a syntax exceptions
056            throw new SimpleIllegalSyntaxException(expression, e.getIndex(), e.getMessage(), e);
057        } catch (Exception e) {
058            // include exception in rethrown exception
059            throw new SimpleIllegalSyntaxException(expression, -1, e.getMessage(), e);
060        }
061    }
062
063    protected Expression doParseExpression() {
064        // parse the expression using the following grammar
065        nextToken();
066        while (!token.getType().isEol()) {
067            // an expression supports just template (eg text), functions, or unary operator
068            templateText();
069            functionText();
070            unaryOperator();
071            nextToken();
072        }
073
074        // now after parsing we need a bit of work to do, to make it easier to turn the tokens
075        // into and ast, and then from the ast, to Camel expression(s).
076        // hence why there is a number of tasks going on below to accomplish this
077
078        // turn the tokens into the ast model
079        parseAndCreateAstModel();
080        // compact and stack blocks (eg function start/end)
081        prepareBlocks();
082        // compact and stack unary operators
083        prepareUnaryExpressions();
084
085        // create and return as a Camel expression
086        List<Expression> expressions = createExpressions();
087        if (expressions.isEmpty()) {
088            // return an empty string as response as there was nothing to parse
089            return ExpressionBuilder.constantExpression("");
090        } else if (expressions.size() == 1) {
091            return expressions.get(0);
092        } else {
093            // concat expressions as evaluating an expression is like a template language
094            return ExpressionBuilder.concatExpression(expressions, expression);
095        }
096    }
097
098    protected void parseAndCreateAstModel() {
099        // we loop the tokens and create a sequence of ast nodes
100
101        // counter to keep track of number of functions in the tokens
102        AtomicInteger functions = new AtomicInteger();
103
104        LiteralNode imageToken = null;
105        for (SimpleToken token : tokens) {
106            // break if eol
107            if (token.getType().isEol()) {
108                break;
109            }
110
111            // create a node from the token
112            SimpleNode node = createNode(token, functions);
113            if (node != null) {
114                // a new token was created so the current image token need to be added first
115                if (imageToken != null) {
116                    nodes.add(imageToken);
117                    imageToken = null;
118                }
119                // and then add the created node
120                nodes.add(node);
121                // continue to next
122                continue;
123            }
124
125            // if no token was created then its a character/whitespace/escaped symbol
126            // which we need to add together in the same image
127            if (imageToken == null) {
128                imageToken = new LiteralExpression(token);
129            }
130            imageToken.addText(token.getText());
131        }
132
133        // append any leftover image tokens (when we reached eol)
134        if (imageToken != null) {
135            nodes.add(imageToken);
136        }
137    }
138
139    private SimpleNode createNode(SimpleToken token, AtomicInteger functions) {
140        // expression only support functions and unary operators
141        if (token.getType().isFunctionStart()) {
142            // starting a new function
143            functions.incrementAndGet();
144            return new SimpleFunctionStart(token);
145        } else if (functions.get() > 0 && token.getType().isFunctionEnd()) {
146            // there must be a start function already, to let this be a end function
147            functions.decrementAndGet();
148            return new SimpleFunctionEnd(token);
149        } else if (token.getType().isUnary()) {
150            // there must be a end function as previous, to let this be a unary function
151            if (!nodes.isEmpty() && nodes.get(nodes.size() - 1) instanceof SimpleFunctionEnd) {
152                return new UnaryExpression(token);
153            }
154        }
155
156        // by returning null, we will let the parser determine what to do
157        return null;
158    }
159
160    private List<Expression> createExpressions() {
161        List<Expression> answer = new ArrayList<Expression>();
162        for (SimpleNode token : nodes) {
163            Expression exp = token.createExpression(expression);
164            if (exp != null) {
165                answer.add(exp);
166            }
167        }
168        return answer;
169    }
170
171    // --------------------------------------------------------------
172    // grammar
173    // --------------------------------------------------------------
174
175    // the expression parser only understands
176    // - template = literal texts with can contain embedded functions
177    // - function = simple functions such as ${body} etc
178    // - unary operator = operator attached to the left hand side node
179
180    protected void templateText() {
181        // for template we accept anything but functions
182        while (!token.getType().isFunctionStart() && !token.getType().isFunctionEnd() && !token.getType().isEol()) {
183            nextToken();
184        }
185    }
186
187    protected boolean functionText() {
188        if (accept(TokenType.functionStart)) {
189            nextToken();
190            while (!token.getType().isFunctionEnd() && !token.getType().isEol()) {
191                if (token.getType().isFunctionStart()) {
192                    // embedded function
193                    functionText();
194                }
195                // we need to loop until we find the ending function quote, an embedded function, or the eol
196                nextToken();
197            }
198            // if its not an embedded function then we expect the end token
199            if (!token.getType().isFunctionStart()) {
200                expect(TokenType.functionEnd);
201            }
202            return true;
203        }
204        return false;
205    }
206
207    protected boolean unaryOperator() {
208        if (accept(TokenType.unaryOperator)) {
209            nextToken();
210            // there should be a whitespace after the operator
211            expect(TokenType.whiteSpace);
212            return true;
213        }
214        return false;
215    }
216}