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     */
017    package org.apache.camel.language.simple;
018    
019    import java.util.ArrayList;
020    import java.util.List;
021    import java.util.concurrent.atomic.AtomicInteger;
022    
023    import org.apache.camel.Expression;
024    import org.apache.camel.builder.ExpressionBuilder;
025    import org.apache.camel.language.simple.ast.LiteralExpression;
026    import org.apache.camel.language.simple.ast.LiteralNode;
027    import org.apache.camel.language.simple.ast.SimpleFunctionEnd;
028    import org.apache.camel.language.simple.ast.SimpleFunctionStart;
029    import org.apache.camel.language.simple.ast.SimpleNode;
030    import org.apache.camel.language.simple.ast.UnaryExpression;
031    import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
032    import org.apache.camel.language.simple.types.SimpleParserException;
033    import org.apache.camel.language.simple.types.SimpleToken;
034    import org.apache.camel.language.simple.types.TokenType;
035    
036    /**
037     * A parser to parse simple language as a Camel {@link Expression}
038     */
039    public 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    }