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.GT) {
084            return createExpression(leftExp, rightExp, PredicateBuilder.isGreaterThan(leftExp, rightExp));
085        } else if (operator == BinaryOperatorType.GTE) {
086            return createExpression(leftExp, rightExp, PredicateBuilder.isGreaterThanOrEqualTo(leftExp, rightExp));
087        } else if (operator == BinaryOperatorType.LT) {
088            return createExpression(leftExp, rightExp, PredicateBuilder.isLessThan(leftExp, rightExp));
089        } else if (operator == BinaryOperatorType.LTE) {
090            return createExpression(leftExp, rightExp, PredicateBuilder.isLessThanOrEqualTo(leftExp, rightExp));
091        } else if (operator == BinaryOperatorType.NOT_EQ) {
092            return createExpression(leftExp, rightExp, PredicateBuilder.isNotEqualTo(leftExp, rightExp));
093        } else if (operator == BinaryOperatorType.CONTAINS) {
094            return createExpression(leftExp, rightExp, PredicateBuilder.contains(leftExp, rightExp));
095        } else if (operator == BinaryOperatorType.NOT_CONTAINS) {
096            return createExpression(leftExp, rightExp, PredicateBuilder.not(PredicateBuilder.contains(leftExp, rightExp)));
097        } else if (operator == BinaryOperatorType.IS || operator == BinaryOperatorType.NOT_IS) {
098            return createIsExpression(expression, leftExp, rightExp);
099        } else if (operator == BinaryOperatorType.REGEX || operator == BinaryOperatorType.NOT_REGEX) {
100            return createRegexExpression(leftExp, rightExp);
101        } else if (operator == BinaryOperatorType.IN || operator == BinaryOperatorType.NOT_IN) {
102            return createInExpression(leftExp, rightExp);
103        } else if (operator == BinaryOperatorType.RANGE || operator == BinaryOperatorType.NOT_RANGE) {
104            return createRangeExpression(expression, leftExp, rightExp);
105        }
106
107        throw new SimpleParserException("Unknown binary operator " + operator, token.getIndex());
108    }
109
110    private Expression createIsExpression(final String expression, final Expression leftExp, final Expression rightExp) {
111        return new Expression() {
112            @Override
113            public <T> T evaluate(Exchange exchange, Class<T> type) {
114                Predicate predicate;
115                String name = rightExp.evaluate(exchange, String.class);
116                if (name == null || "null".equals(name)) {
117                    throw new SimpleIllegalSyntaxException(expression, right.getToken().getIndex(), operator + " operator cannot accept null. A class type must be provided.");
118                }
119                Class<?> rightType = exchange.getContext().getClassResolver().resolveClass(name);
120                if (rightType == null) {
121                    throw new SimpleIllegalSyntaxException(expression, right.getToken().getIndex(), operator + " operator cannot find class with name: " + name);
122                }
123
124                predicate = PredicateBuilder.isInstanceOf(leftExp, rightType);
125                if (operator == BinaryOperatorType.NOT_IS) {
126                    predicate = PredicateBuilder.not(predicate);
127                }
128                boolean answer = predicate.matches(exchange);
129
130                return exchange.getContext().getTypeConverter().convertTo(type, answer);
131            }
132
133            @Override
134            public String toString() {
135                return left + " " + token.getText() + " " + right;
136            }
137        };
138    }
139
140    private Expression createRegexExpression(final Expression leftExp, final Expression rightExp) {
141        return new Expression() {
142            @Override
143            public <T> T evaluate(Exchange exchange, Class<T> type) {
144                // reg ex should use String pattern, so we evaluate the right hand side as a String
145                Predicate predicate = PredicateBuilder.regex(leftExp, rightExp.evaluate(exchange, String.class));
146                if (operator == BinaryOperatorType.NOT_REGEX) {
147                    predicate = PredicateBuilder.not(predicate);
148                }
149                boolean answer = predicate.matches(exchange);
150                return exchange.getContext().getTypeConverter().convertTo(type, answer);
151            }
152
153            @Override
154            public String toString() {
155                return left + " " + token.getText() + " " + right;
156            }
157        };
158    }
159
160    private Expression createInExpression(final Expression leftExp, final Expression rightExp) {
161        return new Expression() {
162            @Override
163            public <T> T evaluate(Exchange exchange, Class<T> type) {
164                // okay the in operator is a bit more complex as we need to build a list of values
165                // from the right hand side expression.
166                // each element on the right hand side must be separated by comma (default for create iterator)
167                Iterator<Object> it = ObjectHelper.createIterator(rightExp.evaluate(exchange, Object.class));
168                List<Object> values = new ArrayList<Object>();
169                while (it.hasNext()) {
170                    values.add(it.next());
171                }
172                // then reuse value builder to create the in predicate with the list of values
173                ValueBuilder vb = new ValueBuilder(leftExp);
174                Predicate predicate = vb.in(values.toArray());
175                if (operator == BinaryOperatorType.NOT_IN) {
176                    predicate = PredicateBuilder.not(predicate);
177                }
178                boolean answer = predicate.matches(exchange);
179                return exchange.getContext().getTypeConverter().convertTo(type, answer);
180            }
181
182            @Override
183            public String toString() {
184                return left + " " + token.getText() + " " + right;
185            }
186        };
187    }
188
189    private Expression createRangeExpression(final String expression, final Expression leftExp, final Expression rightExp) {
190        return new Expression() {
191            @Override
192            public <T> T evaluate(Exchange exchange, Class<T> type) {
193                Predicate predicate;
194
195                String range = rightExp.evaluate(exchange, String.class);
196                Matcher matcher = RANGE_PATTERN.matcher(range);
197                if (matcher.matches()) {
198                    // wrap as constant expression for the from and to values
199                    Expression from = ExpressionBuilder.constantExpression(matcher.group(1));
200                    Expression to = ExpressionBuilder.constantExpression(matcher.group(3));
201
202                    // build a compound predicate for the range
203                    predicate = PredicateBuilder.isGreaterThanOrEqualTo(leftExp, from);
204                    predicate = PredicateBuilder.and(predicate, PredicateBuilder.isLessThanOrEqualTo(leftExp, to));
205                } else {
206                    throw new SimpleIllegalSyntaxException(expression, right.getToken().getIndex(), operator + " operator is not valid. Valid syntax:'from..to' (where from and to are numbers).");
207                }
208                if (operator == BinaryOperatorType.NOT_RANGE) {
209                    predicate = PredicateBuilder.not(predicate);
210                }
211
212                boolean answer = predicate.matches(exchange);
213                return exchange.getContext().getTypeConverter().convertTo(type, answer);
214            }
215
216            @Override
217            public String toString() {
218                return left + " " + token.getText() + " " + right;
219            }
220        };
221    }
222
223    private Expression createExpression(final Expression left, final Expression right, final Predicate predicate) {
224        return new Expression() {
225            @Override
226            public <T> T evaluate(Exchange exchange, Class<T> type) {
227                boolean answer = predicate.matches(exchange);
228                return exchange.getContext().getTypeConverter().convertTo(type, answer);
229            }
230
231            @Override
232            public String toString() {
233                return left + " " + token.getText() + " " + right;
234            }
235        };
236    }
237
238}