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