/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.jdbcconfig.internal; import static com.google.common.base.Preconditions.*; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.geoserver.catalog.Predicates; import org.geoserver.function.IsInstanceOf; import org.geotools.filter.Capabilities; import org.geotools.filter.LikeFilterImpl; import org.opengis.filter.And; import org.opengis.filter.ExcludeFilter; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.FilterVisitor; import org.opengis.filter.Id; import org.opengis.filter.IncludeFilter; import org.opengis.filter.MultiValuedFilter.MatchAction; import org.opengis.filter.Not; import org.opengis.filter.Or; 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.PropertyIsLike; import org.opengis.filter.PropertyIsNil; import org.opengis.filter.PropertyIsNotEqualTo; import org.opengis.filter.PropertyIsNull; import org.opengis.filter.capability.FilterCapabilities; import org.opengis.filter.expression.Add; import org.opengis.filter.expression.Divide; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.ExpressionVisitor; import org.opengis.filter.expression.Function; import org.opengis.filter.expression.Literal; import org.opengis.filter.expression.Multiply; import org.opengis.filter.expression.NilExpression; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.expression.Subtract; import org.opengis.filter.spatial.BBOX; import org.opengis.filter.spatial.Beyond; import org.opengis.filter.spatial.Contains; import org.opengis.filter.spatial.Crosses; import org.opengis.filter.spatial.DWithin; import org.opengis.filter.spatial.Disjoint; import org.opengis.filter.spatial.Equals; import org.opengis.filter.spatial.Intersects; import org.opengis.filter.spatial.Overlaps; import org.opengis.filter.spatial.Touches; import org.opengis.filter.spatial.Within; 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 com.google.common.base.Preconditions; import com.google.common.collect.Maps; /** */ public class FilterToCatalogSQL implements FilterVisitor, ExpressionVisitor { public static final FilterCapabilities CAPABILITIES; static { Capabilities builder = new Capabilities(); builder.addType(PropertyIsEqualTo.class); builder.addType(PropertyIsNotEqualTo.class); builder.addType(PropertyIsLike.class); builder.addType(PropertyIsNull.class);// whether a property exists at all builder.addType(PropertyIsNil.class);// whether the property exists AND it's value is null builder.addType(And.class); builder.addType(Or.class); builder.addName(IsInstanceOf.NAME.getName()); CAPABILITIES = builder.getContents(); } private final Class<?> queryType; private final DbMappings dbMappings; private Map<String, Object> namedParams; public FilterToCatalogSQL(Class<?> queryType, DbMappings dbMappings) { this.queryType = queryType; this.dbMappings = dbMappings; namedParams = Maps.newHashMap(); List<Integer> concreteQueryTypes = dbMappings.getConcreteQueryTypes(queryType); namedParams.put("types", concreteQueryTypes); } /** * */ public Map<String, Object> getNamedParameters() { return namedParams; } private StringBuilder append(Object extraData, String... s) { StringBuilder sb = (StringBuilder) extraData; for (String p : s) { sb.append(p); } return sb; } /** * @see org.opengis.filter.FilterVisitor#visitNullFilter(java.lang.Object) */ @Override public Object visitNullFilter(Object extraData) { throw new UnsupportedOperationException("Do not use null as filter"); } /** * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.ExcludeFilter, * java.lang.Object) */ @Override public Object visit(ExcludeFilter filter, Object extraData) { append(extraData, "(1=0) /* EXCLUDE */\n"); return extraData; } /** * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.IncludeFilter, * java.lang.Object) */ @Override public Object visit(IncludeFilter filter, Object extraData) { append(extraData, "(1=1) /* INCLUDE */\n"); return extraData; } /** * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.PropertyIsEqualTo, * java.lang.Object) */ @Override public Object visit(PropertyIsEqualTo filter, Object extraData) { MatchAction matchAction = filter.getMatchAction(); boolean matchingCase = filter.isMatchingCase(); if (!(filter.getExpression1() instanceof Literal) && !(filter.getExpression2() instanceof Literal)) { //comparing two fields with each other PropertyName expression1 = (PropertyName) filter.getExpression1(); PropertyName expression2 = (PropertyName) filter.getExpression2(); final String propertyTypesParam1 = propertyTypesParam(expression1); final String propertyTypesParam2 = propertyTypesParam(expression2); //respect matchCase String valueCol1 = matchingCase ? "o1.value" : "UPPER(o1.value)"; String valueCol2 = matchingCase ? "o2.value" : "UPPER(o2.value)"; StringBuilder builder; switch (matchAction) { //respect matchaction case ALL: // all = another value for the property may not occur builder = append(extraData, "oid NOT IN (SELECT o1.oid FROM object_property o1, object_property o2 WHERE(o1.oid=o2.oid) ", "AND o1.property_type IN (:", propertyTypesParam1, ") ", "AND o2.property_type IN (:", propertyTypesParam2, ") ", "AND ", valueCol1, " != ", valueCol2, " ) /* ", filter.toString(), " */ \n"); break; case ANY: //any = the value for the property must occur at least once builder = append(extraData, "oid IN (SELECT o1.oid FROM object_property o1, object_property o2 WHERE(o1.oid=o2.oid) ", "AND o1.property_type IN (:", propertyTypesParam1, ") ", "AND o2.property_type IN (:", propertyTypesParam2, ") ", "AND ", valueCol1, " = ", valueCol2, " ) /* ", filter.toString(), " */ \n"); break; case ONE: //one = the value for the property must occur exactly once builder = append(extraData, "oid IN (SELECT o1.oid FROM object_property o1, object_property o2 WHERE(o1.oid=o2.oid) ", "AND o1.property_type IN (:", propertyTypesParam1, ") ", "AND o2.property_type IN (:", propertyTypesParam2, ") ", "AND ", valueCol1, " = ", valueCol2, " GROUP BY (oid) HAVING COUNT(oid)=1) /* ", filter.toString(), "/* ", filter.toString(), " */ \n"); break; default: throw new IllegalArgumentException("MatchAction: " + matchAction); } return builder; } else { if(filter.getExpression1() instanceof IsInstanceOf){ StringBuilder builder = append(extraData, handleInstanceOf((IsInstanceOf) filter.getExpression1())); return builder; } //comparing a literal with a field PropertyName expression1; Literal expression2; //decide which is the literal if (filter.getExpression1() instanceof Literal) { expression1 = (PropertyName) filter.getExpression2(); expression2 = (Literal) filter.getExpression1(); } else { expression1 = (PropertyName) filter.getExpression1(); expression2 = (Literal) filter.getExpression2(); } final String propertyTypesParam = propertyTypesParam(expression1); //respect match case String expectedValue = expression2.evaluate(null, String.class); if (!matchingCase) { expectedValue = expectedValue.toUpperCase(); } String valueParam = newParam("value", expectedValue); StringBuilder builder; String valueCol = matchingCase ? "value" : "UPPER(value)"; switch (matchAction) { // respect match action case ALL: // all = another value for the property may not occur builder = append(extraData, "oid NOT IN (SELECT oid FROM object_property WHERE property_type IN (:", propertyTypesParam, ") AND ", valueCol, " != :", valueParam, ") /* ", filter.toString(), " */ \n"); break; case ANY: //any = the value for the property must occur at least once builder = append(extraData, "oid IN (SELECT oid FROM object_property WHERE property_type IN (:", propertyTypesParam, ") AND ", valueCol, " = :", valueParam, ") /* ", filter.toString(), " */ \n"); break; case ONE: //one = the value for the property must occur exactly once builder = append(extraData, "oid IN (SELECT oid FROM object_property WHERE property_type IN (:", propertyTypesParam, ") AND ", valueCol, " = :", valueParam, " GROUP BY (oid) HAVING COUNT(oid)=1) /* ", filter.toString(), " */ \n"); break; default: throw new IllegalArgumentException("MatchAction: " + matchAction); } return builder; } } private String handleInstanceOf(IsInstanceOf instanceOf) { Expression expression1 = instanceOf.getParameters().get(0); Class clazz = expression1.evaluate(null, Class.class); if(clazz == null || dbMappings.getTypeId(clazz) == null){ return "(1=0) /* EXCLUDE */\n"; } Integer typeId = dbMappings.getTypeId(clazz); return "type_id = " + typeId + "/* isInstanceOf " + clazz.toString() + " */ \n"; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.PropertyIsLike, * java.lang.Object) */ @Override public Object visit(PropertyIsLike filter, Object extraData) { final PropertyName expression1 = (PropertyName) filter.getExpression(); // TODO: check for indexed property name final String propertyTypesParam = propertyTypesParam(expression1); final String literal = filter.getLiteral(); final MatchAction matchAction = filter.getMatchAction(); final char esc = filter.getEscape().charAt(0); final char multi = filter.getWildCard().charAt(0); final char single = filter.getSingleChar().charAt(0); final boolean matchCase = filter.isMatchingCase(); final String pattern = LikeFilterImpl .convertToSQL92(esc, multi, single, matchCase, literal); //respect match case String valueCol = matchCase ? "value" : "UPPER(value)"; StringBuilder builder; switch (matchAction) { // respect match action case ALL: // all = another value for the property may not occur builder = append(extraData, "oid NOT IN (SELECT oid FROM object_property WHERE property_type IN (:", propertyTypesParam, ") AND NOT(", valueCol, " LIKE '", pattern, "')) /* ", filter.toString(), " */ \n"); break; case ANY: //any = the value for the property must occur at least once builder = append(extraData, "oid IN (SELECT oid FROM object_property WHERE property_type IN (:", propertyTypesParam, ") AND ", valueCol, " LIKE '", pattern, "') /* ", filter.toString(), " */ \n"); break; case ONE: //one = the value for the property must occur exactly once builder = append(extraData, "oid IN (SELECT oid FROM object_property WHERE property_type IN (:", propertyTypesParam, ") AND ", valueCol, " LIKE '", pattern, "' ", "GROUP BY (oid) HAVING COUNT(oid)=1 ) /* ", filter.toString(), " */ \n"); break; default: throw new IllegalArgumentException("MatchAction: " + matchAction); } return builder; } private String propertyTypesParam(final PropertyName property) { final String propertyTypesParam; final Set<PropertyType> propertyTypes; String propertyName = property.getPropertyName(); propertyTypes = dbMappings.getPropertyTypes(queryType, propertyName); Preconditions.checkState(!propertyTypes.isEmpty(), "Found no mapping for property '" + property + "' of type " + queryType.getName()); List<Integer> propTypeIds = new ArrayList<Integer>(propertyTypes.size()); for (PropertyType pt : propertyTypes) { Integer propertyTypeId = pt.getOid(); propTypeIds.add(propertyTypeId); } propertyTypesParam = newParam("ptype", propTypeIds); return propertyTypesParam; } /** * @param string * @param propertyTypes * */ private String newParam(String paramNamePrefix, Object paramValue) { int sufix = 0; while (true) { String paramName = paramNamePrefix + sufix; if (!namedParams.containsKey(paramName)) { namedParams.put(paramName, paramValue); return paramName; } sufix++; } } /** * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.PropertyIsNotEqualTo, * java.lang.Object) */ @Override public Object visit(PropertyIsNotEqualTo filter, Object extraData) { // equivalent to not(propertyisequalto) FilterFactory ff = Predicates.factory; Not not = ff.not(ff.equal(filter.getExpression1(), filter.getExpression2(), filter.isMatchingCase(), filter.getMatchAction())); visit(not, extraData); return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.And, java.lang.Object) */ @Override public Object visit(And filter, Object extraData) { StringBuilder sql = (StringBuilder) extraData; List<Filter> children = filter.getChildren(); checkArgument(children.size() > 0); sql.append("(\n\t"); for (Iterator<Filter> it = children.iterator(); it.hasNext();) { Filter child = it.next(); sql = (StringBuilder) child.accept(this, sql); if (it.hasNext()) { sql = append(extraData, "\tAND\n\t"); } } sql.append(")"); return sql; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.Or, java.lang.Object) */ @Override public Object visit(Or filter, Object extraData) { StringBuilder sql = (StringBuilder) extraData; List<Filter> children = filter.getChildren(); checkArgument(children.size() > 0); sql.append("("); for (Iterator<Filter> it = children.iterator(); it.hasNext();) { Filter child = it.next(); sql = (StringBuilder) child.accept(this, sql); if (it.hasNext()) { sql = append(extraData, "\tOR\n\t"); } } sql.append(")"); return sql; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.Id, java.lang.Object) */ @Override public Object visit(Id filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.Not, java.lang.Object) */ @Override public Object visit(Not filter, Object extraData) { return filter.getFilter().accept(this, append (extraData, " NOT ")); } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.PropertyIsBetween, * java.lang.Object) */ @Override public Object visit(PropertyIsBetween filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.PropertyIsGreaterThan, * java.lang.Object) */ @Override public Object visit(PropertyIsGreaterThan filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.PropertyIsGreaterThanOrEqualTo, * java.lang.Object) */ @Override public Object visit(PropertyIsGreaterThanOrEqualTo filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.PropertyIsLessThan, * java.lang.Object) */ @Override public Object visit(PropertyIsLessThan filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.PropertyIsLessThanOrEqualTo, * java.lang.Object) */ @Override public Object visit(PropertyIsLessThanOrEqualTo filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.PropertyIsNull, * java.lang.Object) */ @Override public Object visit(PropertyIsNull filter, Object extraData) { final PropertyName propertyName = (PropertyName) filter.getExpression(); final String propertyTypesParam = propertyTypesParam(propertyName); StringBuilder builder = append(extraData, "oid IN (select oid from object_property where property_type in (:", propertyTypesParam, ") and value IS NULL) OR oid NOT in (select oid from object_property where property_type in (:" + propertyTypesParam + ")) /* ", filter.toString(), " */ \n"); return builder; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.PropertyIsNil, * java.lang.Object) */ @Override public Object visit(PropertyIsNil filter, Object extraData) { final PropertyName propertyName = (PropertyName) filter.getExpression(); final String propertyTypesParam = propertyTypesParam(propertyName); StringBuilder builder = append(extraData, "oid IN (select oid from object_property where property_type in (:", propertyTypesParam, ") and value IS NULL) /* ", filter.toString(), " */ \n"); return builder; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.spatial.BBOX, * java.lang.Object) */ @Override public Object visit(BBOX filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.spatial.Beyond, * java.lang.Object) */ @Override public Object visit(Beyond filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.spatial.Contains, * java.lang.Object) */ @Override public Object visit(Contains filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.spatial.Crosses, * java.lang.Object) */ @Override public Object visit(Crosses filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.spatial.Disjoint, * java.lang.Object) */ @Override public Object visit(Disjoint filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.spatial.DWithin, * java.lang.Object) */ @Override public Object visit(DWithin filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.spatial.Equals, * java.lang.Object) */ @Override public Object visit(Equals filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.spatial.Intersects, * java.lang.Object) */ @Override public Object visit(Intersects filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.spatial.Overlaps, * java.lang.Object) */ @Override public Object visit(Overlaps filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.spatial.Touches, * java.lang.Object) */ @Override public Object visit(Touches filter, Object extraData) { return extraData; } /** * @param filter * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.spatial.Within, * java.lang.Object) */ @Override public Object visit(Within filter, Object extraData) { return extraData; } /** * @param after * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.After, * java.lang.Object) */ @Override public Object visit(After after, Object extraData) { return extraData; } /** * @param anyInteracts * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.AnyInteracts, * java.lang.Object) */ @Override public Object visit(AnyInteracts anyInteracts, Object extraData) { return extraData; } /** * @param before * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.Before, * java.lang.Object) */ @Override public Object visit(Before before, Object extraData) { return extraData; } /** * @param begins * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.Begins, * java.lang.Object) */ @Override public Object visit(Begins begins, Object extraData) { return extraData; } /** * @param begunBy * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.BegunBy, * java.lang.Object) */ @Override public Object visit(BegunBy begunBy, Object extraData) { return extraData; } /** * @param during * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.During, * java.lang.Object) */ @Override public Object visit(During during, Object extraData) { return extraData; } /** * @param endedBy * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.EndedBy, * java.lang.Object) */ @Override public Object visit(EndedBy endedBy, Object extraData) { return extraData; } /** * @param ends * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.Ends, * java.lang.Object) */ @Override public Object visit(Ends ends, Object extraData) { return extraData; } /** * @param meets * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.Meets, * java.lang.Object) */ @Override public Object visit(Meets meets, Object extraData) { return extraData; } /** * @param metBy * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.MetBy, * java.lang.Object) */ @Override public Object visit(MetBy metBy, Object extraData) { return extraData; } /** * @param overlappedBy * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.OverlappedBy, * java.lang.Object) */ @Override public Object visit(OverlappedBy overlappedBy, Object extraData) { return extraData; } /** * @param contains * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.TContains, * java.lang.Object) */ @Override public Object visit(TContains contains, Object extraData) { return extraData; } /** * @param equals * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.TEquals, * java.lang.Object) */ @Override public Object visit(TEquals equals, Object extraData) { return extraData; } /** * @param contains * @param extraData * * @see org.opengis.filter.FilterVisitor#visit(org.opengis.filter.temporal.TOverlaps, * java.lang.Object) */ @Override public Object visit(TOverlaps contains, Object extraData) { return extraData; } /** * @param expression * @param extraData * * @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.NilExpression, * java.lang.Object) */ @Override public Object visit(NilExpression expression, Object extraData) { throw new UnsupportedOperationException(); } /** * @param expression * @param extraData * * @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Add, * java.lang.Object) */ @Override public Object visit(Add expression, Object extraData) { throw new UnsupportedOperationException(); } /** * @param expression * @param extraData * * @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Divide, * java.lang.Object) */ @Override public Object visit(Divide expression, Object extraData) { throw new UnsupportedOperationException(); } /** * @param expression * @param extraData * * @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Function, * java.lang.Object) */ @Override public Object visit(Function expression, Object extraData) { throw new UnsupportedOperationException(); } /** * @param expression * @param extraData * * @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Literal, * java.lang.Object) */ @Override public Object visit(Literal expression, Object extraData) { throw new UnsupportedOperationException(); } /** * @param expression * @param extraData * * @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Multiply, * java.lang.Object) */ @Override public Object visit(Multiply expression, Object extraData) { throw new UnsupportedOperationException(); } /** * @param expression * @param extraData * * @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.PropertyName, * java.lang.Object) */ @Override public Object visit(PropertyName expression, Object extraData) { throw new UnsupportedOperationException(); } /** * @param expression * @param extraData * * @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Subtract, * java.lang.Object) */ @Override public Object visit(Subtract expression, Object extraData) { throw new UnsupportedOperationException(); } }