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}