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     */
017    package org.apache.camel.language.simple.ast;
018    
019    import java.util.ArrayList;
020    import java.util.Iterator;
021    import java.util.List;
022    import java.util.regex.Matcher;
023    import java.util.regex.Pattern;
024    
025    import org.apache.camel.Exchange;
026    import org.apache.camel.Expression;
027    import org.apache.camel.Predicate;
028    import org.apache.camel.builder.ExpressionBuilder;
029    import org.apache.camel.builder.PredicateBuilder;
030    import org.apache.camel.builder.ValueBuilder;
031    import org.apache.camel.language.simple.types.BinaryOperatorType;
032    import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
033    import org.apache.camel.language.simple.types.SimpleParserException;
034    import org.apache.camel.language.simple.types.SimpleToken;
035    import org.apache.camel.util.ObjectHelper;
036    
037    /**
038     * Represents a binary expression in the AST.
039     */
040    public 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    }