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