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