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.ArrayList;
020import java.util.Iterator;
021import java.util.List;
022import java.util.regex.Matcher;
023import java.util.regex.Pattern;
024
025import org.apache.camel.Exchange;
026import org.apache.camel.Expression;
027import org.apache.camel.Predicate;
028import org.apache.camel.builder.ExpressionBuilder;
029import org.apache.camel.builder.PredicateBuilder;
030import org.apache.camel.builder.ValueBuilder;
031import org.apache.camel.language.simple.types.BinaryOperatorType;
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.util.ObjectHelper;
036
037/**
038 * Represents a binary expression in the AST.
039 */
040public class BinaryExpression extends BaseSimpleNode {
041
042    // this is special for the range operator where you define the range as from..to (where from and to are numbers)
043    private static final Pattern RANGE_PATTERN = Pattern.compile("^(\\d+)(\\.\\.)(\\d+)$");
044
045    private final BinaryOperatorType operator;
046    private SimpleNode left;
047    private SimpleNode right;
048
049    public BinaryExpression(SimpleToken token) {
050        super(token);
051        operator = BinaryOperatorType.asOperator(token.getText());
052    }
053
054    @Override
055    public String toString() {
056        return left + " " + token.getText() + " " + right;
057    }
058
059    public boolean acceptLeftNode(SimpleNode lef) {
060        this.left = lef;
061        return true;
062    }
063
064    public boolean acceptRightNode(SimpleNode right) {
065        this.right = right;
066        return true;
067    }
068
069    public BinaryOperatorType getOperator() {
070        return operator;
071    }
072
073    @Override
074    public Expression createExpression(String expression) {
075        ObjectHelper.notNull(left, "left node", this);
076        ObjectHelper.notNull(right, "right node", this);
077
078        final Expression leftExp = left.createExpression(expression);
079        final Expression rightExp = right.createExpression(expression);
080
081        if (operator == BinaryOperatorType.EQ) {
082            return createExpression(leftExp, rightExp, PredicateBuilder.isEqualTo(leftExp, rightExp));
083        } else if (operator == BinaryOperatorType.EQ_IGNORE) {
084            return createExpression(leftExp, rightExp, PredicateBuilder.isEqualToIgnoreCase(leftExp, rightExp));
085        } else if (operator == BinaryOperatorType.GT) {
086            return createExpression(leftExp, rightExp, PredicateBuilder.isGreaterThan(leftExp, rightExp));
087        } else if (operator == BinaryOperatorType.GTE) {
088            return createExpression(leftExp, rightExp, PredicateBuilder.isGreaterThanOrEqualTo(leftExp, rightExp));
089        } else if (operator == BinaryOperatorType.LT) {
090            return createExpression(leftExp, rightExp, PredicateBuilder.isLessThan(leftExp, rightExp));
091        } else if (operator == BinaryOperatorType.LTE) {
092            return createExpression(leftExp, rightExp, PredicateBuilder.isLessThanOrEqualTo(leftExp, rightExp));
093        } else if (operator == BinaryOperatorType.NOT_EQ) {
094            return createExpression(leftExp, rightExp, PredicateBuilder.isNotEqualTo(leftExp, rightExp));
095        } else if (operator == BinaryOperatorType.CONTAINS) {
096            return createExpression(leftExp, rightExp, PredicateBuilder.contains(leftExp, rightExp));
097        } else if (operator == BinaryOperatorType.NOT_CONTAINS) {
098            return createExpression(leftExp, rightExp, PredicateBuilder.not(PredicateBuilder.contains(leftExp, rightExp)));
099        } else if (operator == BinaryOperatorType.CONTAINS_IGNORECASE) {
100            return createExpression(leftExp, rightExp, PredicateBuilder.containsIgnoreCase(leftExp, rightExp));
101        } else if (operator == BinaryOperatorType.IS || operator == BinaryOperatorType.NOT_IS) {
102            return createIsExpression(expression, leftExp, rightExp);
103        } else if (operator == BinaryOperatorType.REGEX || operator == BinaryOperatorType.NOT_REGEX) {
104            return createRegexExpression(leftExp, rightExp);
105        } else if (operator == BinaryOperatorType.IN || operator == BinaryOperatorType.NOT_IN) {
106            return createInExpression(leftExp, rightExp);
107        } else if (operator == BinaryOperatorType.RANGE || operator == BinaryOperatorType.NOT_RANGE) {
108            return createRangeExpression(expression, leftExp, rightExp);
109        } else if (operator == BinaryOperatorType.STARTS_WITH) {
110            return createExpression(leftExp, rightExp, PredicateBuilder.startsWith(leftExp, rightExp));
111        } else if (operator == BinaryOperatorType.ENDS_WITH) {
112            return createExpression(leftExp, rightExp, PredicateBuilder.endsWith(leftExp, rightExp));
113        }
114
115        throw new SimpleParserException("Unknown binary operator " + operator, token.getIndex());
116    }
117
118    private Expression createIsExpression(final String expression, final Expression leftExp, final Expression rightExp) {
119        return new Expression() {
120            @Override
121            public <T> T evaluate(Exchange exchange, Class<T> type) {
122                Predicate predicate;
123                String name = rightExp.evaluate(exchange, String.class);
124                if (name == null || "null".equals(name)) {
125                    throw new SimpleIllegalSyntaxException(expression, right.getToken().getIndex(), operator + " operator cannot accept null. A class type must be provided.");
126                }
127                Class<?> rightType = exchange.getContext().getClassResolver().resolveClass(name);
128                if (rightType == null) {
129                    throw new SimpleIllegalSyntaxException(expression, right.getToken().getIndex(), operator + " operator cannot find class with name: " + name);
130                }
131
132                predicate = PredicateBuilder.isInstanceOf(leftExp, rightType);
133                if (operator == BinaryOperatorType.NOT_IS) {
134                    predicate = PredicateBuilder.not(predicate);
135                }
136                boolean answer = predicate.matches(exchange);
137
138                return exchange.getContext().getTypeConverter().convertTo(type, answer);
139            }
140
141            @Override
142            public String toString() {
143                return left + " " + token.getText() + " " + right;
144            }
145        };
146    }
147
148    private Expression createRegexExpression(final Expression leftExp, final Expression rightExp) {
149        return new Expression() {
150            @Override
151            public <T> T evaluate(Exchange exchange, Class<T> type) {
152                // reg ex should use String pattern, so we evaluate the right hand side as a String
153                Predicate predicate = PredicateBuilder.regex(leftExp, rightExp.evaluate(exchange, String.class));
154                if (operator == BinaryOperatorType.NOT_REGEX) {
155                    predicate = PredicateBuilder.not(predicate);
156                }
157                boolean answer = predicate.matches(exchange);
158                return exchange.getContext().getTypeConverter().convertTo(type, answer);
159            }
160
161            @Override
162            public String toString() {
163                return left + " " + token.getText() + " " + right;
164            }
165        };
166    }
167
168    private Expression createInExpression(final Expression leftExp, final Expression rightExp) {
169        return new Expression() {
170            @Override
171            public <T> T evaluate(Exchange exchange, Class<T> type) {
172                // okay the in operator is a bit more complex as we need to build a list of values
173                // from the right hand side expression.
174                // each element on the right hand side must be separated by comma (default for create iterator)
175                Iterator<Object> it = ObjectHelper.createIterator(rightExp.evaluate(exchange, Object.class));
176                List<Object> values = new ArrayList<Object>();
177                while (it.hasNext()) {
178                    values.add(it.next());
179                }
180                // then reuse value builder to create the in predicate with the list of values
181                ValueBuilder vb = new ValueBuilder(leftExp);
182                Predicate predicate = vb.in(values.toArray());
183                if (operator == BinaryOperatorType.NOT_IN) {
184                    predicate = PredicateBuilder.not(predicate);
185                }
186                boolean answer = predicate.matches(exchange);
187                return exchange.getContext().getTypeConverter().convertTo(type, answer);
188            }
189
190            @Override
191            public String toString() {
192                return left + " " + token.getText() + " " + right;
193            }
194        };
195    }
196
197    private Expression createRangeExpression(final String expression, final Expression leftExp, final Expression rightExp) {
198        return new Expression() {
199            @Override
200            public <T> T evaluate(Exchange exchange, Class<T> type) {
201                Predicate predicate;
202
203                String range = rightExp.evaluate(exchange, String.class);
204                Matcher matcher = RANGE_PATTERN.matcher(range);
205                if (matcher.matches()) {
206                    // wrap as constant expression for the from and to values
207                    Expression from = ExpressionBuilder.constantExpression(matcher.group(1));
208                    Expression to = ExpressionBuilder.constantExpression(matcher.group(3));
209
210                    // build a compound predicate for the range
211                    predicate = PredicateBuilder.isGreaterThanOrEqualTo(leftExp, from);
212                    predicate = PredicateBuilder.and(predicate, PredicateBuilder.isLessThanOrEqualTo(leftExp, to));
213                } else {
214                    throw new SimpleIllegalSyntaxException(expression, right.getToken().getIndex(), operator + " operator is not valid. Valid syntax:'from..to' (where from and to are numbers).");
215                }
216                if (operator == BinaryOperatorType.NOT_RANGE) {
217                    predicate = PredicateBuilder.not(predicate);
218                }
219
220                boolean answer = predicate.matches(exchange);
221                return exchange.getContext().getTypeConverter().convertTo(type, answer);
222            }
223
224            @Override
225            public String toString() {
226                return left + " " + token.getText() + " " + right;
227            }
228        };
229    }
230
231    private Expression createExpression(final Expression left, final Expression right, final Predicate predicate) {
232        return new Expression() {
233            @Override
234            public <T> T evaluate(Exchange exchange, Class<T> type) {
235                boolean answer = predicate.matches(exchange);
236                return exchange.getContext().getTypeConverter().convertTo(type, answer);
237            }
238
239            @Override
240            public String toString() {
241                return left + " " + token.getText() + " " + right;
242            }
243        };
244    }
245
246}