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 return new UnaryExpression(token);
151 }
152
153 // by returning null, we will let the parser determine what to do
154 return null;
155 }
156
157 private List<Expression> createExpressions() {
158 List<Expression> answer = new ArrayList<Expression>();
159 for (SimpleNode token : nodes) {
160 Expression exp = token.createExpression(expression);
161 if (exp != null) {
162 answer.add(exp);
163 }
164 }
165 return answer;
166 }
167
168 // --------------------------------------------------------------
169 // grammar
170 // --------------------------------------------------------------
171
172 // the expression parser only understands
173 // - template = literal texts with can contain embedded functions
174 // - function = simple functions such as ${body} etc
175 // - unary operator = operator attached to the left hand side node
176
177 protected void templateText() {
178 // for template we accept anything but functions
179 while (!token.getType().isFunctionStart() && !token.getType().isFunctionEnd() && !token.getType().isEol()) {
180 nextToken();
181 }
182 }
183
184 protected boolean functionText() {
185 if (accept(TokenType.functionStart)) {
186 nextToken();
187 while (!token.getType().isFunctionEnd() && !token.getType().isEol()) {
188 if (token.getType().isFunctionStart()) {
189 // embedded function
190 functionText();
191 }
192 // we need to loop until we find the ending function quote, an embedded function, or the eol
193 nextToken();
194 }
195 // if its not an embedded function then we expect the end token
196 if (!token.getType().isFunctionStart()) {
197 expect(TokenType.functionEnd);
198 }
199 return true;
200 }
201 return false;
202 }
203
204 protected boolean unaryOperator() {
205 if (accept(TokenType.unaryOperator)) {
206 nextToken();
207 // there should be a whitespace after the operator
208 expect(TokenType.whiteSpace);
209 return true;
210 }
211 return false;
212 }
213 }