/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2006-2016, 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.text.commons;
import java.awt.Color;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.regex.Pattern;
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.temporal.Period;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.WKTWriter;
/**
* This class is responsible to convert an expression to a CQL/ECQL valid expression.
*
* <p>
* Warning: This component is not published. It is part of module implementation.
* Client module should not use this feature.
* </p>
*
* @author Mauricio Pazos
*
*
* @source $URL$
*/
public class ExpressionToText implements ExpressionVisitor {
static private StringBuilder asStringBuilder( Object extraData){
if( extraData instanceof StringBuilder){
return (StringBuilder) extraData;
}
return new StringBuilder();
}
/**
* Uses the format <code>yyyy-MM-dd'T'HH:mm:ss'[+|-]##:##'</code> for
* output the provided date.
* @param date
* @param output
* @return output
*/
public StringBuilder dateToText( Date date, StringBuilder output ){
final DateFormat formatter;
// If the Date has millisecond resolution, print the millis.
if (date.getTime() % 1000 == 0) {
formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
} else {
formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSz");
}
formatter.setTimeZone(TimeZone.getTimeZone("GMT+00:00"));
String text = formatter.format(date);
// GMT is not part of CQL syntax so it is removed
text = text.replace("GMT", "");
output.append( text );
return output;
}
/* (non-Javadoc)
* @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.NilExpression, java.lang.Object)
*/
@Override
public Object visit(NilExpression expression, Object extraData) {
StringBuilder output = asStringBuilder(extraData);
output.append("\"\"");
return output;
}
/* (non-Javadoc)
* @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Add, java.lang.Object)
*/
@Override
public Object visit(Add expression, Object extraData) {
StringBuilder output = asStringBuilder(extraData);
expression.getExpression1().accept(this, output );
output.append( " + " );
expression.getExpression2().accept(this, output );
return output;
}
/* (non-Javadoc)
* @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Divide, java.lang.Object)
*/
@Override
public Object visit(Divide expression, Object extraData) {
StringBuilder output = asStringBuilder(extraData);
expression.getExpression1().accept(this, output );
output.append( " / " );
expression.getExpression2().accept(this, output );
return output;
}
/* (non-Javadoc)
* @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Function, java.lang.Object)
*/
@Override
public Object visit(Function function, Object extraData) {
StringBuilder output = asStringBuilder(extraData);
output.append( function.getName() );
output.append( "(" );
List<Expression> parameters = function.getParameters();
if( parameters != null ){
for( Iterator<Expression> i=parameters.iterator(); i.hasNext(); ){
Expression argument = i.next();
argument.accept(this, output );
if( i.hasNext() ){
output.append(",");
}
}
}
output.append( ")" );
return output;
}
/* (non-Javadoc)
* @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Literal, java.lang.Object)
*/
@Override
public Object visit(Literal expression, Object extraData) {
StringBuilder output = asStringBuilder(extraData);
Object literal = expression.getValue();
if (literal instanceof Geometry) {
Geometry geometry = (Geometry) literal;
WKTWriter writer = new WKTWriter();
String wkt = writer.write( geometry );
output.append( wkt );
}
else if( literal instanceof Number ){
// don't convert to string
output.append( literal );
}
else if (literal instanceof Date ){
return dateToText( (Date) literal, output );
}
else if (literal instanceof Period){
Period period = (Period) literal;
output = dateToText(period.getBeginning().getPosition().getDate(), output);
output.append("/");
output = dateToText(period.getEnding().getPosition().getDate(), output);
return output;
} else if (literal instanceof Color) {
Color color = (Color) literal;
String redCode = Integer.toHexString(color.getRed());
String greenCode = Integer.toHexString(color.getGreen());
String blueCode = Integer.toHexString(color.getBlue());
output.append("'#");
if (redCode.length() == 1) {
output.append("0");
}
output.append(redCode.toUpperCase());
if (greenCode.length() == 1) {
output.append("0");
}
output.append(greenCode.toUpperCase());
if (blueCode.length() == 1) {
output.append("0");
}
output.append(blueCode.toUpperCase());
output.append("'");
}
else {
String escaped = literal.toString().replaceAll("'", "''");
output.append("'" + escaped + "'");
}
return output;
}
/* (non-Javadoc)
* @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Multiply, java.lang.Object)
*/
@Override
public Object visit(Multiply expression, Object extraData) {
StringBuilder output = asStringBuilder(extraData);
expression.getExpression1().accept(this, output );
output.append( " * " );
expression.getExpression2().accept(this, output );
return output;
}
/* (non-Javadoc)
* @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.PropertyName, java.lang.Object)
*/
@Override
public Object visit(PropertyName expression, Object extraData) {
StringBuilder output = asStringBuilder(extraData);
if(propertyNeedsDelimiters(expression)) {
output.append('"');
output.append( expression.getPropertyName().replace("\"", "\"\"") );
output.append('"');
} else {
output.append( expression.getPropertyName() );
}
return output;
}
// Pattern for an identifier which does not require delimiting unless it's
// a reserved word
private static final Pattern SIMPLE_IDENTIFIER =
Pattern.compile("[a-zA-Z][_a-zA-Z0-9]*");
// Words reserved by the language which can not be used as identifiers
// unless delimited
private static final Set<String> RESERVED_WORDS;
static {
Set<String> reservedWords =
new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
reservedWords.addAll(Arrays.asList("NOT", "AND", "OR", "LIKE", "IS",
"NULL", "EXISTS", "DOES-NOT-EXIST", "DURING", "AFTER", "BEFORE",
"ID", // deprecated but accepted by the parser
"IN", "INCLUDE", "EXCLUDE", "TRUE", "FALSE", "EQUALS",
"DISJOINT", "INTERSECTS", "TOUCHES", "CROSSES", "WITHIN",
"CONTAINS", "OVERLAPS", "RELATE", "DWITHIN", "BEYOND", "POINT",
"LINESTRING", "POLYGON", "MULTIPOINT", "MULTILINESTRING",
"MULTIPOLYGON", "GEOMETRYCOLLECTION"));
RESERVED_WORDS = Collections.unmodifiableSet(reservedWords);
}
protected boolean propertyNeedsDelimiters(PropertyName name) {
if(!SIMPLE_IDENTIFIER.matcher(name.getPropertyName()).matches()) {
return true;
}
return RESERVED_WORDS.contains(name.getPropertyName());
}
/* (non-Javadoc)
* @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Subtract, java.lang.Object)
*/
@Override
public Object visit(Subtract expression, Object extraData) {
StringBuilder output = asStringBuilder(extraData);
expression.getExpression1().accept(this, output );
output.append( " - " );
expression.getExpression2().accept(this, output );
return output;
}
}