/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2012, Geomatys * * 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.geotoolkit.cql; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.io.WKTWriter; import java.util.Date; import java.util.List; import java.util.SimpleTimeZone; import java.util.TimeZone; import java.util.regex.Pattern; import org.geotoolkit.filter.DefaultPropertyIsLike; import org.geotoolkit.temporal.object.TemporalUtilities; import org.opengis.filter.And; import org.opengis.filter.ExcludeFilter; import org.opengis.filter.Filter; import org.opengis.filter.FilterVisitor; import org.opengis.filter.Id; import org.opengis.filter.IncludeFilter; 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.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; /** * Visitor to convert a Filter in CQL.<br> * Returned object is a StringBuilder containing the CQL text. * * @author Johann Sorel (Geomatys) */ public class FilterToCQLVisitor implements FilterVisitor, ExpressionVisitor { public static final FilterToCQLVisitor INSTANCE = new FilterToCQLVisitor(); private static final TimeZone TZ = new SimpleTimeZone(0, "Out Timezone"); /** * Pattern to check for property name to escape against regExp */ private final Pattern patternPropertyName = Pattern.compile("[,+\\-/*\\t\\n\\r\\d\\s]"); private FilterToCQLVisitor() { } private static StringBuilder toStringBuilder(final Object o){ if(o instanceof StringBuilder){ return (StringBuilder) o; } return new StringBuilder(); } //////////////////////////////////////////////////////////////////////////// // FILTER ////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// @Override public Object visitNullFilter(final Object o) { throw new UnsupportedOperationException("Null filter not supported in CQL."); } @Override public Object visit(final ExcludeFilter filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("1=0"); return sb; } @Override public Object visit(final IncludeFilter filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("1=1"); return sb; } @Override public Object visit(final And filter, final Object o) { final StringBuilder sb = toStringBuilder(o); final List<Filter> filters = filter.getChildren(); if(filters != null && !filters.isEmpty()){ final int size = filters.size(); sb.append('('); for(int i=0,n=size-1;i<n;i++){ filters.get(i).accept(this,sb); sb.append(" AND "); } filters.get(size-1).accept(this,sb); sb.append(')'); } return sb; } @Override public Object visit(final Or filter, final Object o) { final StringBuilder sb = toStringBuilder(o); final List<Filter> filters = filter.getChildren(); if(filters != null && !filters.isEmpty()){ final int size = filters.size(); sb.append('('); for(int i=0,n=size-1;i<n;i++){ filters.get(i).accept(this,sb); sb.append(" OR "); } filters.get(size-1).accept(this,sb); sb.append(')'); } return sb; } @Override public Object visit(final Id filter, final Object o) { throw new UnsupportedOperationException("ID filter not supported in CQL."); } @Override public Object visit(final Not filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("NOT "); filter.getFilter().accept(this,sb); return sb; } @Override public Object visit(final PropertyIsBetween filter, final Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression().accept(this,sb); sb.append(" BETWEEN "); filter.getLowerBoundary().accept(this,sb); sb.append(" AND "); filter.getUpperBoundary().accept(this,sb); return sb; } @Override public Object visit(final PropertyIsEqualTo filter, final Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" = "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(final PropertyIsNotEqualTo filter, final Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" <> "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(final PropertyIsGreaterThan filter, final Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" > "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(final PropertyIsGreaterThanOrEqualTo filter, final Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" >= "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(final PropertyIsLessThan filter, final Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" < "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(final PropertyIsLessThanOrEqualTo filter, final Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" <= "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(final PropertyIsLike filter, final Object o) { final StringBuilder sb = toStringBuilder(o); final char escape = filter.getEscape().charAt(0); final char wildCard = filter.getWildCard().charAt(0); final char singleChar = filter.getSingleChar().charAt(0); final boolean matchingCase = filter.isMatchingCase(); final String literal = filter.getLiteral(); final String pattern = DefaultPropertyIsLike.convertToSQL92(escape, wildCard, singleChar, literal); filter.getExpression().accept(this,sb); if(matchingCase){ sb.append(" LIKE "); }else{ sb.append(" ILIKE "); } sb.append('\''); sb.append(pattern); sb.append('\''); return sb; } @Override public Object visit(final PropertyIsNull filter, final Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression().accept(this,sb); sb.append(" IS NULL"); return sb; } @Override public Object visit(PropertyIsNil filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression().accept(this,sb); sb.append(" IS NIL"); return sb; } //////////////////////////////////////////////////////////////////////////// // GEOMETRY FILTER ///////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// @Override public Object visit(final BBOX filter, final Object o) { final StringBuilder sb = toStringBuilder(o); if(filter.getExpression1() instanceof PropertyName && filter.getExpression2() instanceof Literal){ //use writing : BBOX(att,v1,v2,v3,v4) sb.append("BBOX("); sb.append(filter.getPropertyName()); sb.append(','); sb.append(filter.getMinX()); sb.append(','); sb.append(filter.getMaxX()); sb.append(','); sb.append(filter.getMinY()); sb.append(','); sb.append(filter.getMaxY()); sb.append(')'); }else{ //use writing BBOX(exp1,exp2) sb.append("BBOX("); filter.getExpression1().accept(this,sb); sb.append(','); filter.getExpression2().accept(this,sb); sb.append(')'); } return sb; } @Override public Object visit(final Beyond filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("BEYOND("); filter.getExpression1().accept(this,sb); sb.append(','); filter.getExpression2().accept(this,sb); sb.append(')'); return sb; } @Override public Object visit(final Contains filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("CONTAINS("); filter.getExpression1().accept(this,sb); sb.append(','); filter.getExpression2().accept(this,sb); sb.append(')'); return sb; } @Override public Object visit(final Crosses filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("CROSSES("); filter.getExpression1().accept(this,sb); sb.append(','); filter.getExpression2().accept(this,sb); sb.append(')'); return sb; } @Override public Object visit(final Disjoint filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("DISJOINT("); filter.getExpression1().accept(this,sb); sb.append(','); filter.getExpression2().accept(this,sb); sb.append(')'); return sb; } @Override public Object visit(final DWithin filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("DWITHIN("); filter.getExpression1().accept(this,sb); sb.append(','); filter.getExpression2().accept(this,sb); sb.append(')'); return sb; } @Override public Object visit(final Equals filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("EQUALS("); filter.getExpression1().accept(this,sb); sb.append(','); filter.getExpression2().accept(this,sb); sb.append(')'); return sb; } @Override public Object visit(final Intersects filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("INTERSECTS("); filter.getExpression1().accept(this,sb); sb.append(','); filter.getExpression2().accept(this,sb); sb.append(')'); return sb; } @Override public Object visit(final Overlaps filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("OVERLAPS("); filter.getExpression1().accept(this,sb); sb.append(','); filter.getExpression2().accept(this,sb); sb.append(')'); return sb; } @Override public Object visit(final Touches filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("TOUCHES("); filter.getExpression1().accept(this,sb); sb.append(','); filter.getExpression2().accept(this,sb); sb.append(')'); return sb; } @Override public Object visit(final Within filter, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append("WITHIN("); filter.getExpression1().accept(this,sb); sb.append(','); filter.getExpression2().accept(this,sb); sb.append(')'); return sb; } //////////////////////////////////////////////////////////////////////////// // TEMPORAL FILTER ///////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// @Override public Object visit(After filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" AFTER "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(AnyInteracts filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" ANYINTERACTS "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(Before filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" BEFORE "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(Begins filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" BEGINS "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(BegunBy filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" BEGUNBY "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(During filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" DURING "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(EndedBy filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" ENDEDBY "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(Ends filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" ENDS "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(Meets filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" MEETS "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(MetBy filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" METBY "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(OverlappedBy filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" OVERLAPPEDBY "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(TContains filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" TCONTAINS "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(TEquals filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" TEQUALS "); filter.getExpression2().accept(this,sb); return sb; } @Override public Object visit(TOverlaps filter, Object o) { final StringBuilder sb = toStringBuilder(o); filter.getExpression1().accept(this,sb); sb.append(" TOVERLAPS "); filter.getExpression2().accept(this,sb); return sb; } //////////////////////////////////////////////////////////////////////////// // EXPRESSIONS ///////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// @Override public Object visit(final Literal exp, final Object o) { final StringBuilder sb = toStringBuilder(o); final Object value = exp.getValue(); if(value instanceof Number){ final Number num = (Number) value; sb.append(num.toString()); }else if(value instanceof Date){ final Date date = (Date) value; sb.append(TemporalUtilities.toISO8601Z(date,TZ)); }else if(value instanceof Geometry){ final Geometry geometry = (Geometry) value; final WKTWriter writer = new WKTWriter(); final String wkt = writer.write(geometry); sb.append(wkt); }else{ sb.append('\'').append(value != null ? value.toString() : null).append('\''); } return sb; } @Override public Object visit(final PropertyName exp, final Object o) { final StringBuilder sb = toStringBuilder(o); final String name = exp.getPropertyName(); if(patternPropertyName.matcher(name).find()){ //escape for special chars sb.append('"').append(name).append('"'); }else{ sb.append(name); } return sb; } @Override public Object visit(final Add exp, final Object o) { final StringBuilder sb = toStringBuilder(o); exp.getExpression1().accept(this,sb); sb.append(" + "); exp.getExpression2().accept(this,sb); return sb; } @Override public Object visit(final Divide exp, final Object o) { final StringBuilder sb = toStringBuilder(o); exp.getExpression1().accept(this,sb); sb.append(" / "); exp.getExpression2().accept(this,sb); return sb; } @Override public Object visit(final Multiply exp, final Object o) { final StringBuilder sb = toStringBuilder(o); exp.getExpression1().accept(this,sb); sb.append(" * "); exp.getExpression2().accept(this,sb); return sb; } @Override public Object visit(final Subtract exp, final Object o) { final StringBuilder sb = toStringBuilder(o); exp.getExpression1().accept(this,sb); sb.append(" - "); exp.getExpression2().accept(this,sb); return sb; } @Override public Object visit(final Function exp, final Object o) { final StringBuilder sb = toStringBuilder(o); sb.append(exp.getName()); sb.append('('); final List<Expression> exps = exp.getParameters(); if(exps != null){ final int size = exps.size(); if(size==1){ exps.get(0).accept(this,sb); }else if(size>1){ for(int i=0,n=size-1;i<n;i++){ exps.get(i).accept(this,sb); sb.append(" , "); } exps.get(size-1).accept(this,sb); } } sb.append(')'); return sb; } @Override public Object visit(final NilExpression exp, final Object o) { throw new UnsupportedOperationException("NilExpression not supported in CQL."); } }