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.ast;
018
019import java.util.Map;
020
021import org.apache.camel.Exchange;
022import org.apache.camel.Expression;
023import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
024import org.apache.camel.language.simple.types.SimpleParserException;
025import org.apache.camel.language.simple.types.SimpleToken;
026import org.apache.camel.util.StringHelper;
027
028/**
029 * Starts a function
030 */
031public class SimpleFunctionStart extends BaseSimpleNode implements BlockStart {
032
033    // use caches to avoid re-parsing the same expressions over and over again
034    private final Map<String, Expression> cacheExpression;
035    private final CompositeNodes block;
036
037    public SimpleFunctionStart(SimpleToken token, Map<String, Expression> cacheExpression) {
038        super(token);
039        this.block = new CompositeNodes(token);
040        this.cacheExpression = cacheExpression;
041    }
042
043    public boolean lazyEval(SimpleNode child) {
044        String text = child.toString();
045        // don't lazy evaluate nested type references as they are static
046        return !text.startsWith("${type:");
047    }
048
049    @Override
050    public String toString() {
051        // output a nice toString so it makes debugging easier as we can see the entire block
052        return "${" + block + "}";
053    }
054
055    @Override
056    public Expression createExpression(String expression) {
057        // a function can either be a simple literal function, or contain nested functions
058        if (block.getChildren().size() == 1 && block.getChildren().get(0) instanceof LiteralNode) {
059            return doCreateLiteralExpression(expression);
060        } else {
061            return doCreateCompositeExpression(expression);
062        }
063    }
064
065    private Expression doCreateLiteralExpression(final String expression) {
066        SimpleFunctionExpression function = new SimpleFunctionExpression(this.getToken(), cacheExpression);
067        LiteralNode literal = (LiteralNode) block.getChildren().get(0);
068        function.addText(literal.getText());
069        return function.createExpression(expression);
070    }
071
072    private Expression doCreateCompositeExpression(final String expression) {
073        final SimpleToken token = getToken();
074        return new Expression() {
075            @Override
076            public <T> T evaluate(Exchange exchange, Class<T> type) {
077                StringBuilder sb = new StringBuilder();
078                boolean quoteEmbeddedFunctions = false;
079
080                // we need to concat the block so we have the expression
081                for (SimpleNode child : block.getChildren()) {
082                    // whether a nested function should be lazy evaluated or not
083                    boolean lazy = true;
084                    if (child instanceof SimpleFunctionStart) {
085                        lazy = ((SimpleFunctionStart) child).lazyEval(child);
086                    }
087                    if (child instanceof LiteralNode) {
088                        String text = ((LiteralNode) child).getText();
089                        sb.append(text);
090                        quoteEmbeddedFunctions |= ((LiteralNode) child).quoteEmbeddedNodes();
091                    // if its quoted literal then embed that as text
092                    } else if (!lazy || child instanceof SingleQuoteStart || child instanceof DoubleQuoteStart) {
093                        try {
094                            // pass in null when we evaluate the nested expressions
095                            Expression nested = child.createExpression(null);
096                            String text = nested.evaluate(exchange, String.class);
097                            if (text != null) {
098                                if (quoteEmbeddedFunctions && !StringHelper.isQuoted(text)) {
099                                    sb.append("'").append(text).append("'");
100                                } else {
101                                    sb.append(text);
102                                }
103                            }
104                        } catch (SimpleParserException e) {
105                            // must rethrow parser exception as illegal syntax with details about the location
106                            throw new SimpleIllegalSyntaxException(expression, e.getIndex(), e.getMessage(), e);
107                        }
108                    // if its an inlined function then embed that function as text so it can be evaluated lazy
109                    } else if (child instanceof SimpleFunctionStart) {
110                        sb.append(child);
111                    }
112                }
113
114                // we have now concat the block as a String which contains the function expression
115                // which we then need to evaluate as a function
116                String exp = sb.toString();
117                SimpleFunctionExpression function = new SimpleFunctionExpression(token, cacheExpression);
118                function.addText(exp);
119                try {
120                    return function.createExpression(exp).evaluate(exchange, type);
121                } catch (SimpleParserException e) {
122                    // must rethrow parser exception as illegal syntax with details about the location
123                    throw new SimpleIllegalSyntaxException(expression, e.getIndex(), e.getMessage(), e);
124                }
125            }
126
127            @Override
128            public String toString() {
129                return expression;
130            }
131        };
132    }
133
134    @Override
135    public boolean acceptAndAddNode(SimpleNode node) {
136        // only accept literals, quotes or embedded functions
137        if (node instanceof LiteralNode || node instanceof SimpleFunctionStart
138                || node instanceof SingleQuoteStart || node instanceof DoubleQuoteStart) {
139            block.addChild(node);
140            return true;
141        } else {
142            return false;
143        }
144    }
145
146}