/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2015, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.filter.visitor; import java.util.Date; import java.util.List; import org.opengis.feature.type.FeatureType; import org.opengis.filter.PropertyIsBetween; import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.PropertyIsGreaterThan; import org.opengis.filter.PropertyIsGreaterThanOrEqualTo; import org.opengis.filter.PropertyIsLessThan; import org.opengis.filter.PropertyIsLessThanOrEqualTo; import org.opengis.filter.PropertyIsNotEqualTo; import org.opengis.filter.capability.FunctionName; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Function; import org.opengis.filter.expression.InternalFunction; import org.opengis.filter.expression.Literal; import org.opengis.filter.temporal.After; import org.opengis.filter.temporal.AnyInteracts; import org.opengis.filter.temporal.Before; import org.opengis.filter.temporal.Begins; import org.opengis.filter.temporal.BegunBy; import org.opengis.filter.temporal.During; import org.opengis.filter.temporal.EndedBy; import org.opengis.filter.temporal.Ends; import org.opengis.filter.temporal.Meets; import org.opengis.filter.temporal.MetBy; import org.opengis.filter.temporal.OverlappedBy; import org.opengis.filter.temporal.TContains; import org.opengis.filter.temporal.TEquals; import org.opengis.filter.temporal.TOverlaps; import org.opengis.parameter.Parameter; import org.opengis.temporal.Period; /** * Binds all literals in the filter to the target type they are compared to, in order to avoid the * usage of converters on a evaluation by evaluation basis. * * @author Andrea Aime - GeoSolutions * */ public class BindingFilterVisitor extends DuplicatingFilterVisitor { FeatureType schema; private ExpressionTypeVisitor expressionTypeVisitor; /** * Evaluates the * * @param schema */ public BindingFilterVisitor(FeatureType schema) { this.schema = schema; this.expressionTypeVisitor = new ExpressionTypeVisitor(schema); } public Object visit(PropertyIsEqualTo filter, Object extraData) { Class targetType = getTargetType(filter.getExpression1(), filter.getExpression2()); Expression expr1 = optimize(filter.getExpression1(), extraData, targetType); Expression expr2 = optimize(filter.getExpression2(), extraData, targetType); boolean matchCase = filter.isMatchingCase(); return getFactory(extraData).equal(expr1, expr2, matchCase, filter.getMatchAction()); } @Override public Object visit(PropertyIsNotEqualTo filter, Object extraData) { Class targetType = getTargetType(filter.getExpression1(), filter.getExpression2()); Expression expr1 = optimize(filter.getExpression1(), extraData, targetType); Expression expr2 = optimize(filter.getExpression2(), extraData, targetType); boolean matchCase = filter.isMatchingCase(); return getFactory(extraData).notEqual(expr1, expr2, matchCase, filter.getMatchAction()); } @Override public Object visit(PropertyIsBetween filter, Object extraData) { Class targetType = getTargetType(filter.getLowerBoundary(), filter.getExpression(), filter.getUpperBoundary()); Expression lb = optimize(filter.getLowerBoundary(), extraData, targetType); Expression ex = optimize(filter.getExpression(), extraData, targetType); Expression ub = optimize(filter.getUpperBoundary(), extraData, targetType); return getFactory(extraData).between(ex, lb, ub, filter.getMatchAction()); } @Override public Object visit(PropertyIsGreaterThan filter, Object extraData) { Class targetType = getTargetType(filter.getExpression1(), filter.getExpression2()); Expression expr1 = optimize(filter.getExpression1(), extraData, targetType); Expression expr2 = optimize(filter.getExpression2(), extraData, targetType); boolean matchCase = filter.isMatchingCase(); return getFactory(extraData).greater(expr1, expr2, matchCase, filter.getMatchAction()); } @Override public Object visit(PropertyIsGreaterThanOrEqualTo filter, Object extraData) { Class targetType = getTargetType(filter.getExpression1(), filter.getExpression2()); Expression expr1 = optimize(filter.getExpression1(), extraData, targetType); Expression expr2 = optimize(filter.getExpression2(), extraData, targetType); boolean matchCase = filter.isMatchingCase(); return getFactory(extraData).greaterOrEqual(expr1, expr2, matchCase, filter.getMatchAction()); } @Override public Object visit(PropertyIsLessThan filter, Object extraData) { Class targetType = getTargetType(filter.getExpression1(), filter.getExpression2()); Expression expr1 = optimize(filter.getExpression1(), extraData, targetType); Expression expr2 = optimize(filter.getExpression2(), extraData, targetType); boolean matchCase = filter.isMatchingCase(); return getFactory(extraData).less(expr1, expr2, matchCase, filter.getMatchAction()); } @Override public Object visit(Function expression, Object extraData) { List old = expression.getParameters(); FunctionName functionName = expression.getFunctionName(); List<Parameter<?>> arguments = null; if (functionName != null) { arguments = functionName.getArguments(); } Expression[] args = new Expression[old.size()]; for (int i = 0; i < old.size(); i++) { Expression exp = (Expression) old.get(i); if (arguments != null && i < arguments.size()) { args[i] = optimize(exp, extraData, arguments.get(i).getType()); } else { args[i] = visit(exp, extraData); } } Function duplicate; if (expression instanceof InternalFunction) { duplicate = ((InternalFunction) expression).duplicate(args); } else { duplicate = getFactory(extraData).function(expression.getName(), args); } return duplicate; } public Object visit(After after, Object extraData) { Expression expr1 = optimizeTime(after.getExpression1(), extraData); Expression expr2 = optimizeTime(after.getExpression2(), extraData); return getFactory(extraData).after(expr1, expr2, after.getMatchAction()); } public Object visit(AnyInteracts anyInteracts, Object extraData) { Expression expr1 = optimizeTime(anyInteracts.getExpression1(), extraData); Expression expr2 = optimizeTime(anyInteracts.getExpression2(), extraData); return getFactory(extraData).anyInteracts(expr1, expr2, anyInteracts.getMatchAction()); }; public Object visit(Before before, Object extraData) { Expression expr1 = optimizeTime(before.getExpression1(), extraData); Expression expr2 = optimizeTime(before.getExpression2(), extraData); return getFactory(extraData).before(expr1, expr2, before.getMatchAction()); } public Object visit(Begins begins, Object extraData) { Expression expr1 = optimizeTime(begins.getExpression1(), extraData); Expression expr2 = optimizeTime(begins.getExpression2(), extraData); return getFactory(extraData).begins(expr1, expr2, begins.getMatchAction()); }; public Object visit(BegunBy begunBy, Object extraData) { Expression expr1 = optimizeTime(begunBy.getExpression1(), extraData); Expression expr2 = optimizeTime(begunBy.getExpression2(), extraData); return getFactory(extraData).begins(expr1, expr2, begunBy.getMatchAction()); } public Object visit(During during, Object extraData) { Expression expr1 = optimizeTime(during.getExpression1(), extraData); Expression expr2 = optimizeTime(during.getExpression2(), extraData); return getFactory(extraData).during(expr1, expr2, during.getMatchAction()); } public Object visit(EndedBy endedBy, Object extraData) { Expression expr1 = optimizeTime(endedBy.getExpression1(), extraData); Expression expr2 = optimizeTime(endedBy.getExpression2(), extraData); return getFactory(extraData).endedBy(expr1, expr2, endedBy.getMatchAction()); } public Object visit(Ends ends, Object extraData) { Expression expr1 = optimizeTime(ends.getExpression1(), extraData); Expression expr2 = optimizeTime(ends.getExpression2(), extraData); return getFactory(extraData).ends(expr1, expr2, ends.getMatchAction()); } public Object visit(Meets meets, Object extraData) { Expression expr1 = optimizeTime(meets.getExpression1(), extraData); Expression expr2 = optimizeTime(meets.getExpression2(), extraData); return getFactory(extraData).meets(expr1, expr2, meets.getMatchAction()); } public Object visit(MetBy metBy, Object extraData) { Expression expr1 = optimizeTime(metBy.getExpression1(), extraData); Expression expr2 = optimizeTime(metBy.getExpression2(), extraData); return getFactory(extraData).metBy(expr1, expr2, metBy.getMatchAction()); } public Object visit(OverlappedBy overlappedBy, Object extraData) { Expression expr1 = optimizeTime(overlappedBy.getExpression1(), extraData); Expression expr2 = optimizeTime(overlappedBy.getExpression2(), extraData); return getFactory(extraData).overlappedBy(expr1, expr2, overlappedBy.getMatchAction()); } public Object visit(TContains contains, Object extraData) { Expression expr1 = optimizeTime(contains.getExpression1(), extraData); Expression expr2 = optimizeTime(contains.getExpression2(), extraData); return getFactory(extraData).tcontains(expr1, expr2, contains.getMatchAction()); } public Object visit(TEquals equals, Object extraData) { Expression expr1 = optimizeTime(equals.getExpression1(), extraData); Expression expr2 = optimizeTime(equals.getExpression2(), extraData); return getFactory(extraData).tequals(expr1, expr2, equals.getMatchAction()); } public Object visit(TOverlaps contains, Object extraData) { Expression expr1 = optimizeTime(contains.getExpression1(), extraData); Expression expr2 = optimizeTime(contains.getExpression2(), extraData); return getFactory(extraData).toverlaps(expr1, expr2, contains.getMatchAction()); } @Override public Object visit(PropertyIsLessThanOrEqualTo filter, Object extraData) { Class targetType = getTargetType(filter.getExpression1(), filter.getExpression2()); Expression expr1 = optimize(filter.getExpression1(), extraData, targetType); Expression expr2 = optimize(filter.getExpression2(), extraData, targetType); boolean matchCase = filter.isMatchingCase(); return getFactory(extraData).lessOrEqual(expr1, expr2, matchCase, filter.getMatchAction()); } protected Expression optimize(Expression expression, Object extraData, Class targetType) { if (expression instanceof Literal && targetType != null) { // perform the conversion and return the optimized literal Object converted = expression.evaluate(null, targetType); if (converted != null) { return ff.literal(converted); } } // in case we could not optimize, just duplicate return visit(expression, extraData); } protected Expression optimizeTime(Expression expression, Object extraData) { if (expression instanceof Literal) { // time based operators try period first, Object converted = expression.evaluate(null, Period.class); if (converted != null) { return ff.literal(converted); } // ideally this would be Istant, but there is very little // in the GT codebase able to deal with instants, so we // convert to date, and accept a little overhead to convert from Date to Instant // (simple object wrapping, no complex parsing) converted = expression.evaluate(null, Date.class); if (converted != null) { return ff.literal(converted); } } // in case we could not optimize, just duplicate return visit(expression, extraData); } private Class getTargetType(Expression... expressions) { Class result = null; for (Expression expression : expressions) { if (!(expression instanceof Literal)) { Class target = (Class) expression.accept(expressionTypeVisitor, null); if (target == null) { // could not find a target type, let the evaluation be dynamic return target; } else if (result == null) { result = target; } else if (!target.equals(result)) { // if we have two inconsistent properties being // compared, give up and handle this dynamically return null; } } } return result; } }