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