/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.language.simple; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.apache.camel.Expression; import org.apache.camel.builder.ExpressionBuilder; import org.apache.camel.language.simple.ast.LiteralExpression; import org.apache.camel.language.simple.ast.LiteralNode; import org.apache.camel.language.simple.ast.SimpleFunctionEnd; import org.apache.camel.language.simple.ast.SimpleFunctionStart; import org.apache.camel.language.simple.ast.SimpleNode; import org.apache.camel.language.simple.ast.UnaryExpression; import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException; import org.apache.camel.language.simple.types.SimpleParserException; import org.apache.camel.language.simple.types.SimpleToken; import org.apache.camel.language.simple.types.TokenType; /** * A parser to parse simple language as a Camel {@link Expression} */ public class SimpleExpressionParser extends BaseSimpleParser { @Deprecated public SimpleExpressionParser(String expression) { super(expression, true); } public SimpleExpressionParser(String expression, boolean allowEscape) { super(expression, allowEscape); } public Expression parseExpression() { clear(); try { return doParseExpression(); } catch (SimpleParserException e) { // catch parser exception and turn that into a syntax exceptions throw new SimpleIllegalSyntaxException(expression, e.getIndex(), e.getMessage(), e); } catch (Exception e) { // include exception in rethrown exception throw new SimpleIllegalSyntaxException(expression, -1, e.getMessage(), e); } } protected Expression doParseExpression() { // parse the expression using the following grammar nextToken(); while (!token.getType().isEol()) { // an expression supports just template (eg text), functions, or unary operator templateText(); functionText(); unaryOperator(); nextToken(); } // now after parsing we need a bit of work to do, to make it easier to turn the tokens // into an ast, and then from the ast, to Camel expression(s). // hence why there is a number of tasks going on below to accomplish this // turn the tokens into the ast model parseAndCreateAstModel(); // compact and stack blocks (eg function start/end) prepareBlocks(); // compact and stack unary operators prepareUnaryExpressions(); // create and return as a Camel expression List<Expression> expressions = createExpressions(); if (expressions.isEmpty()) { // return an empty string as response as there was nothing to parse return ExpressionBuilder.constantExpression(""); } else if (expressions.size() == 1) { return expressions.get(0); } else { // concat expressions as evaluating an expression is like a template language return ExpressionBuilder.concatExpression(expressions, expression); } } protected void parseAndCreateAstModel() { // we loop the tokens and create a sequence of ast nodes // counter to keep track of number of functions in the tokens AtomicInteger functions = new AtomicInteger(); LiteralNode imageToken = null; for (SimpleToken token : tokens) { // break if eol if (token.getType().isEol()) { break; } // create a node from the token SimpleNode node = createNode(token, functions); if (node != null) { // a new token was created so the current image token need to be added first if (imageToken != null) { nodes.add(imageToken); imageToken = null; } // and then add the created node nodes.add(node); // continue to next continue; } // if no token was created then its a character/whitespace/escaped symbol // which we need to add together in the same image if (imageToken == null) { imageToken = new LiteralExpression(token); } imageToken.addText(token.getText()); } // append any leftover image tokens (when we reached eol) if (imageToken != null) { nodes.add(imageToken); } } private SimpleNode createNode(SimpleToken token, AtomicInteger functions) { // expression only support functions and unary operators if (token.getType().isFunctionStart()) { // starting a new function functions.incrementAndGet(); return new SimpleFunctionStart(token); } else if (functions.get() > 0 && token.getType().isFunctionEnd()) { // there must be a start function already, to let this be a end function functions.decrementAndGet(); return new SimpleFunctionEnd(token); } else if (token.getType().isUnary()) { // there must be a end function as previous, to let this be a unary function if (!nodes.isEmpty() && nodes.get(nodes.size() - 1) instanceof SimpleFunctionEnd) { return new UnaryExpression(token); } } // by returning null, we will let the parser determine what to do return null; } private List<Expression> createExpressions() { List<Expression> answer = new ArrayList<Expression>(); for (SimpleNode token : nodes) { Expression exp = token.createExpression(expression); if (exp != null) { answer.add(exp); } } return answer; } // -------------------------------------------------------------- // grammar // -------------------------------------------------------------- // the expression parser only understands // - template = literal texts with can contain embedded functions // - function = simple functions such as ${body} etc // - unary operator = operator attached to the left hand side node protected void templateText() { // for template we accept anything but functions while (!token.getType().isFunctionStart() && !token.getType().isFunctionEnd() && !token.getType().isEol()) { nextToken(); } } protected boolean functionText() { if (accept(TokenType.functionStart)) { nextToken(); while (!token.getType().isFunctionEnd() && !token.getType().isEol()) { if (token.getType().isFunctionStart()) { // embedded function functionText(); } // we need to loop until we find the ending function quote, an embedded function, or the eol nextToken(); } // if its not an embedded function then we expect the end token if (!token.getType().isFunctionStart()) { expect(TokenType.functionEnd); } return true; } return false; } protected boolean unaryOperator() { if (accept(TokenType.unaryOperator)) { nextToken(); // there should be a whitespace after the operator expect(TokenType.whiteSpace); return true; } return false; } }