/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2006-2008, 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.cql2;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.List;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.Hints;
import org.geotools.filter.IllegalFilterException;
import org.geotools.filter.text.commons.ICompiler;
import org.geotools.filter.text.commons.IToken;
import org.geotools.filter.text.commons.Result;
import org.geotools.filter.text.commons.TokenAdapter;
import org.geotools.filter.text.generated.parsers.CQLParser;
import org.geotools.filter.text.generated.parsers.Node;
import org.geotools.filter.text.generated.parsers.ParseException;
import org.geotools.filter.text.generated.parsers.Token;
import org.geotools.filter.text.generated.parsers.TokenMgrError;
import org.opengis.filter.BinaryComparisonOperator;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.Not;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.spatial.BinarySpatialOperator;
import org.opengis.filter.spatial.DistanceBufferOperator;
/**
* CQL Compiler. This class extend the CQLParser generated by javacc with
* semantic actions.
* <p>
* The "build..." methods implement that semantic actions making a filter for
* each syntax rules recognized.
* </p>
*
* <p>
* Warning: This component is not published. It is part of module implementation.
* Client module should not use this feature.
* </p>
*
* TODO This module should use the new geometry API, more info in http://docs.codehaus.org/display/GEOTOOLS/GeomeryFactoryFinder+Proposal
*
*
* @author Mauricio Pazos (Axios Engineering)
*
*
* @source $URL$
* @version $Id$
* @since 2.5
*/
public class CQLCompiler extends CQLParser implements ICompiler{
private static final String ATTRIBUTE_PATH_SEPARATOR = "/";
/** cql expression to compile */
private final String source;
private CQLFilterBuilder builder;
/**
* new instance of CQL Compiler
* @param cqlSource
* @param filterFactory
*/
public CQLCompiler(final String cqlSource, final FilterFactory filterFactory) {
super(new StringReader(cqlSource));
assert cqlSource != null: "cqlSource cannot be null";
assert filterFactory != null: "filterFactory cannot be null";
this.source = cqlSource;
this.builder = new CQLFilterBuilder(cqlSource, filterFactory);
}
/**
* compile source to produce a Filter. The filter
* result must be retrieved with {@link #getFilter()}.
*/
public void compileFilter() throws CQLException{
try {
super.FilterCompilationUnit();
} catch (TokenMgrError tokenError){
throw new CQLException(tokenError.getMessage(),getTokenInPosition(0), this.source);
} catch (CQLException e) {
throw e;
} catch (ParseException e) {
throw new CQLException(e.getMessage(), getTokenInPosition(0), e.getCause(), this.source);
}
}
/**
* compile source to produce a Expression
*/
public void compileExpression() throws CQLException{
try {
super.ExpressionCompilationUnit();
} catch (TokenMgrError tokenError){
throw new CQLException(tokenError.getMessage(),getTokenInPosition(0), this.source);
} catch (CQLException e) {
throw e;
} catch (ParseException e) {
throw new CQLException(e.getMessage(), getTokenInPosition(0), e.getCause(), this.source);
}
}
public void compileFilterList() throws CQLException{
try {
super.FilterListCompilationUnit();
} catch (TokenMgrError tokenError){
throw new CQLException(tokenError.getMessage(),getTokenInPosition(0), this.source);
} catch (CQLException e) {
throw e;
} catch (ParseException e) {
throw new CQLException(e.getMessage(), getTokenInPosition(0), e.getCause(), this.source);
}
}
/**
* @return the cql source
*/
public final String getSource(){
return this.source;
}
/**
* Return the filter resultant of compiling process
* @return Filter
* @throws CQLException
*/
public final Filter getFilter() throws CQLException {
return this.builder.getFilter();
}
/**
* Return the expression resultant of compiling process
* @return Expression
* @throws CQLException
*/
public final Expression getExpression() throws CQLException {
return this.builder.getExpression();
}
/**
* Returns the list of Filters built as the result of calling
* {@link #MultipleCompilationUnit()}
*
* @return List<Filter>
* @throws CQLException
* if a ClassCastException occurs while casting a built item to
* a Filter.
*/
public final List<Filter> getFilterList() throws CQLException {
return this.builder.getFilterList();
}
public IToken getTokenInPosition(int index ){
return TokenAdapter.newAdapterFor( super.getToken(index));
}
public final void jjtreeOpenNodeScope(Node n) {
}
public final void jjtreeCloseNodeScope(Node n) throws ParseException {
try {
Object built = build(n);
IToken tokenAdapter = TokenAdapter.newAdapterFor(token);
Result r = new Result(built, tokenAdapter, n.getType());
this.builder.pushResult(r );
} finally {
n.dispose();
}
}
/**
* This method is called when the parser close a node. Here is built the
* filters an expressions recognized in the parsing process.
*
* @param cqlNode a Node instance
* @return Filter or Expression
* @throws CQLException
*/
private Object build(Node cqlNode) throws CQLException {
switch (cqlNode.getType()) {
// Literals
// note, these should never throw because the parser grammar
// constrains input before we ever reach here!
case JJTINTEGERNODE:
return this.builder.buildLiteralInteger(getToken(0).image);
case JJTFLOATINGNODE:
return this.builder.buildLiteralDouble(getToken(0).image);
case JJTSTRINGNODE:
return this.builder.buildLiteralString(getToken(0).image);
// ----------------------------------------
// Identifier
// ----------------------------------------
case JJTIDENTIFIER_NODE:
return this.builder.buildIdentifier(JJTIDENTIFIER_PART_NODE);
case JJTIDENTIFIER_PART_NODE:
return this.builder.buildIdentifierPart(getTokenInPosition(0));
// ----------------------------------------
// attribute
// ----------------------------------------
case JJTSIMPLE_ATTRIBUTE_NODE:
return this.builder.buildSimpleAttribute();
case JJTCOMPOUND_ATTRIBUTE_NODE:
return this.builder.buildCompoundAttribute(JJTSIMPLE_ATTRIBUTE_NODE, ATTRIBUTE_PATH_SEPARATOR);
// ----------------------------------------
// function
// ----------------------------------------
case JJTFUNCTION_NODE:
return this.builder.buildFunction(JJTFUNCTIONNAME_NODE);
case JJTFUNCTIONNAME_NODE:
return cqlNode; // used as mark of function name in stack
case JJTFUNCTIONARG_NODE:
return cqlNode; // used as mark of args in stack
// Math Nodes
case JJTADDNODE:
case JJTSUBTRACTNODE:
case JJTMULNODE:
case JJTDIVNODE:
return buildBinaryExpression(cqlNode.getType());
// Boolean expression
case JJTBOOLEAN_AND_NODE:
return buildLogicFilter(JJTBOOLEAN_AND_NODE);
case JJTBOOLEAN_OR_NODE:
return buildLogicFilter(JJTBOOLEAN_OR_NODE);
case JJTBOOLEAN_NOT_NODE:
return buildLogicFilter(JJTBOOLEAN_NOT_NODE);
// ----------------------------------------
// between predicate actions
// ----------------------------------------
case JJTBETWEEN_NODE:
return this.builder.buildBetween();
case JJTNOT_BETWEEN_NODE:
return this.builder.buildNotBetween();
// ----------------------------------------
// Compare predicate actions
// ----------------------------------------
case JJTCOMPARISSONPREDICATE_EQ_NODE:
case JJTCOMPARISSONPREDICATE_GT_NODE:
case JJTCOMPARISSONPREDICATE_LT_NODE:
case JJTCOMPARISSONPREDICATE_GTE_NODE:
case JJTCOMPARISSONPREDICATE_LTE_NODE:
return buildBinaryComparasionOperator(cqlNode.getType());
case JJTCOMPARISSONPREDICATE_NOT_EQUAL_NODE:
Filter eq = buildBinaryComparasionOperator(JJTCOMPARISSONPREDICATE_EQ_NODE);
Not notFilter = this.builder.buildNotFilter(eq);
return notFilter;
// ----------------------------------------
// Text predicate (Like)
// ----------------------------------------
case JJTLIKE_NODE:
return this.builder.buildLikeFilter();
case JJTNOT_LIKE_NODE:
return this.builder.buildNotLikeFilter();
// ----------------------------------------
// Null predicate
// ----------------------------------------
case JJTNULLPREDICATENODE:
return this.builder.buildPropertyIsNull();
case JJTNOTNULLPREDICATENODE:
return this.builder.buildPorpertyNotIsNull();
// ----------------------------------------
// temporal predicate actions
// ----------------------------------------
case JJTDATETIME_NODE:
return this.builder.buildDateTimeExpression(getTokenInPosition(0));
case JJTDURATION_DATE_NODE:
return this.builder.buildDurationExpression(getTokenInPosition(0));
case JJTPERIOD_BETWEEN_DATES_NODE:
return this.builder.buildPeriodBetweenDates();
case JJTPERIOD_WITH_DATE_DURATION_NODE:
return this.builder.buildPeriodDateAndDuration();
case JJTPERIOD_WITH_DURATION_DATE_NODE:
return this.builder.buildPeriodDurationAndDate();
case JJTTPBEFORE_DATETIME_NODE:
return buildTemporalPredicateBefore();
case JJTTPAFTER_DATETIME_NODE:
return buildTemporalPredicateAfter();
case JJTTPDURING_PERIOD_NODE:
return buildTemporalPredicateDuring();
case JJTTPBEFORE_OR_DURING_PERIOD_NODE:
return buildTemporalPredicateBeforeOrDuring();
case JJTTPDURING_OR_AFTER_PERIOD_NODE:
return buildTemporalPredicateDuringOrAfter();
// ----------------------------------------
// existence predicate actions
// ----------------------------------------
case JJTEXISTENCE_PREDICATE_EXISTS_NODE:
return this.builder.buildPropertyExists();
case JJTEXISTENCE_PREDICATE_DOESNOTEXIST_NODE:
Filter filter = this.builder.buildPropertyExists();
Filter filterPropNotExist = this.builder.buildNotFilter(filter);
return filterPropNotExist;
// ----------------------------------------
// routine invocation Geo Operation
// ----------------------------------------
case JJTROUTINEINVOCATION_GEOOP_EQUAL_NODE:
case JJTROUTINEINVOCATION_GEOOP_DISJOINT_NODE:
case JJTROUTINEINVOCATION_GEOOP_INTERSECT_NODE:
case JJTROUTINEINVOCATION_GEOOP_TOUCH_NODE:
case JJTROUTINEINVOCATION_GEOOP_CROSS_NODE:
case JJTROUTINEINVOCATION_GEOOP_WITHIN_NODE:
case JJTROUTINEINVOCATION_GEOOP_CONTAIN_NODE:
case JJTROUTINEINVOCATION_GEOOP_OVERLAP_NODE:
return buildBinarySpatialOperator(cqlNode.getType());
case JJTROUTINEINVOCATION_GEOOP_BBOX_NODE:
case JJTROUTINEINVOCATION_GEOOP_BBOX_SRS_NODE:
return buildBBox(cqlNode.getType());
case JJTROUTINEINVOCATION_GEOOP_RELATE_NODE:
throw new CQLException(
"Unsupported geooperation RELATE (is not implemented by GeoTools)",
getTokenInPosition(0), this.source);
// ----------------------------------------
// routine invocation RelGeo Operation
// ----------------------------------------
case JJTTOLERANCE_NODE:
return this.builder.buildTolerance();
case JJTDISTANCEUNITS_NODE:
return this.builder.buildDistanceUnit(getTokenInPosition(0));
case JJTROUTINEINVOCATION_RELOP_BEYOND_NODE:
case JJTROUTINEINVOCATION_RELOP_DWITHIN_NODE:
return buildDistanceBufferOperator(cqlNode.getType());
// ----------------------------------------
// Geometries:
// ----------------------------------------
case JJTWKTNODE:
return this.builder.buildGeometry(TokenAdapter.newAdapterFor(cqlNode.getToken()));
case JJTENVELOPETAGGEDTEXT_NODE:
return this.builder.buildEnvelop(TokenAdapter.newAdapterFor(cqlNode.getToken()));
case JJTINCLUDE_NODE:
return Filter.INCLUDE;
case JJTEXCLUDE_NODE:
return Filter.EXCLUDE;
case JJTTRUENODE:
return this.builder.buildTrueLiteral();
case JJTFALSENODE:
return this.builder.buildFalseLiteral();
}
return null;
}
private org.opengis.filter.expression.BinaryExpression buildBinaryExpression(
int nodeType) throws CQLException {
org.opengis.filter.expression.BinaryExpression expr = null;
switch (nodeType) {
case JJTADDNODE:
expr = this.builder.buildAddExpression();
break;
case JJTSUBTRACTNODE:
expr = this.builder.buildSubtractExression();
break;
case JJTMULNODE:
expr = this.builder.buildMultiplyExpression();
break;
case JJTDIVNODE:
expr = this.builder.buildDivideExpression();
break;
default:
break;
}
return expr;
}
private org.opengis.filter.Filter buildLogicFilter(int nodeType)
throws CQLException {
try {
org.opengis.filter.Filter logicFilter;
switch (nodeType) {
case JJTBOOLEAN_AND_NODE:
logicFilter = this.builder.buildAndFilter();
break;
case JJTBOOLEAN_OR_NODE:
logicFilter = this.builder.buildOrFilter();
break;
case JJTBOOLEAN_NOT_NODE:
logicFilter = this.builder.buildNotFilter();
break;
default:
throw new CQLException(
"Expression not supported. And, Or, Not is required",
getTokenInPosition(0), this.source);
}
return logicFilter;
} catch (IllegalFilterException ife) {
throw new CQLException("Exception building LogicFilter",
getTokenInPosition(0), ife, this.source);
}
}
/**
* Creates Binary Spatial Operator
*
* @param tipeNode
*
* @return BinarySpatialOperator
* @throws CQLException
*/
private BinarySpatialOperator buildBinarySpatialOperator(final int nodeType)
throws CQLException {
BinarySpatialOperator filter = null;
switch (nodeType) {
case JJTROUTINEINVOCATION_GEOOP_EQUAL_NODE:
filter = this.builder.buildSpatialEqualFilter();
break;
case JJTROUTINEINVOCATION_GEOOP_DISJOINT_NODE:
filter = this.builder.buildSpatialDisjointFilter();
break;
case JJTROUTINEINVOCATION_GEOOP_INTERSECT_NODE:
filter = this.builder.buildSpatialIntersectsFilter();
break;
case JJTROUTINEINVOCATION_GEOOP_TOUCH_NODE:
filter = this.builder.buildSpatialTouchesFilter();
break;
case JJTROUTINEINVOCATION_GEOOP_CROSS_NODE:
filter = this.builder.buildSpatialCrossesFilter();
break;
case JJTROUTINEINVOCATION_GEOOP_WITHIN_NODE:
filter = this.builder.buildSpatialWithinFilter();
break;
case JJTROUTINEINVOCATION_GEOOP_CONTAIN_NODE:
filter = this.builder.buildSpatialContainsFilter();
break;
case JJTROUTINEINVOCATION_GEOOP_OVERLAP_NODE:
filter = this.builder.buildSpatialOverlapsFilter();
break;
default:
throw new CQLException("Binary spatial operator unexpected");
}
return filter;
}
private org.opengis.filter.spatial.BBOX buildBBox(int nodeType)
throws CQLException {
if (nodeType == JJTROUTINEINVOCATION_GEOOP_BBOX_SRS_NODE) {
return this.builder.buildBBoxWithCRS();
} else {
return this.builder.buildBBox();
}
}
/**
* Builds Distance Buffer Operator
*
* @param nodeType
* @return DistanceBufferOperator dwithin and beyond filters
* @throws CQLException
*/
private DistanceBufferOperator buildDistanceBufferOperator(
final int nodeType) throws CQLException {
DistanceBufferOperator filter = null;
switch (nodeType) {
case JJTROUTINEINVOCATION_RELOP_DWITHIN_NODE:
filter = this.builder.buildSpatialDWithinFilter();
break;
case JJTROUTINEINVOCATION_RELOP_BEYOND_NODE:
filter = this.builder.buildSpatialBeyondFilter();
break;
default:
throw new CQLException("Binary spatial operator unexpected");
}
return filter;
}
private org.opengis.filter.Filter buildTemporalPredicateBeforeOrDuring()
throws CQLException {
org.opengis.filter.Filter filter = null;
Result node = this.builder.peekResult();
switch (node.getNodeType()) {
case JJTPERIOD_BETWEEN_DATES_NODE:
case JJTPERIOD_WITH_DATE_DURATION_NODE:
case JJTPERIOD_WITH_DURATION_DATE_NODE:
filter = this.builder.buildPropertyIsLTELastDate();
break;
default:
throw new CQLException(
"unexpeted date time expression in temporal predicate.",
node.getToken(), this.source);
}
return filter;
}
private org.opengis.filter.Filter buildTemporalPredicateDuringOrAfter()
throws CQLException {
org.opengis.filter.Filter filter = null;
Result node = this.builder.peekResult();
switch (node.getNodeType()) {
case JJTPERIOD_BETWEEN_DATES_NODE:
case JJTPERIOD_WITH_DATE_DURATION_NODE:
case JJTPERIOD_WITH_DURATION_DATE_NODE:
filter = this.builder.buildPropertyIsGTEFirstDate();
break;
default:
throw new CQLException(
"unexpeted date time expression in temporal predicate.",
node.getToken(), this.source);
}
return filter;
}
/**
* Build the convenient filter for before date and before period filters
*
* @param nodeType
*
* @return Filter
* @throws CQLException
*/
private org.opengis.filter.Filter buildTemporalPredicateBefore()
throws CQLException {
org.opengis.filter.Filter filter = null;
// analyzes if the last build is period or date
Result node = this.builder.peekResult();
switch (node.getNodeType()) {
case JJTDATETIME_NODE:
filter = buildBinaryComparasionOperator(JJTCOMPARISSONPREDICATE_LT_NODE);
break;
case JJTPERIOD_BETWEEN_DATES_NODE:
case JJTPERIOD_WITH_DATE_DURATION_NODE:
case JJTPERIOD_WITH_DURATION_DATE_NODE:
filter = this.builder.buildPropertyIsLTFirsDate();
break;
default:
throw new CQLException(
"unexpeted date time expression in temporal predicate.",
node.getToken(), this.source);
}
return filter;
}
/**
* Build the convenient filter for during period filters
*
* @return Filter
* @throws CQLException
*/
private Object buildTemporalPredicateDuring() throws CQLException {
org.opengis.filter.Filter filter = null;
// determines if the node is period or date
Result node = this.builder.peekResult();
switch (node.getNodeType()) {
case JJTPERIOD_BETWEEN_DATES_NODE:
case JJTPERIOD_WITH_DATE_DURATION_NODE:
case JJTPERIOD_WITH_DURATION_DATE_NODE:
filter = this.builder.buildPropertyBetweenDates();
break;
default:
throw new CQLException(
"unexpeted period expression in temporal predicate.", node
.getToken(), this.source);
}
return filter;
}
/**
* build filter for after date and after period
*
* @return a filter
* @throws CQLException
*/
private org.opengis.filter.Filter buildTemporalPredicateAfter()
throws CQLException {
org.opengis.filter.Filter filter = null;
// determines if the node is period or date
Result result = this.builder.peekResult();
switch (result.getNodeType()) {
case JJTDATETIME_NODE:
filter = buildBinaryComparasionOperator(JJTCOMPARISSONPREDICATE_GT_NODE);
break;
case JJTPERIOD_BETWEEN_DATES_NODE:
case JJTPERIOD_WITH_DURATION_DATE_NODE:
case JJTPERIOD_WITH_DATE_DURATION_NODE:
filter = this.builder.buildPropertyIsGTLastDate();
break;
default:
throw new CQLException(
"unexpeted date time expression in temporal predicate.",
result.getToken(), this.source);
}
return filter;
}
/**
* Builds a compare filter
*
* @param filterTipa
*
* @return BinaryComparisonOperator
* @throws CQLException
*/
private BinaryComparisonOperator buildBinaryComparasionOperator(
int filterType) throws CQLException {
switch (filterType) {
case JJTCOMPARISSONPREDICATE_EQ_NODE:
return this.builder.buildEquals();
case JJTCOMPARISSONPREDICATE_GT_NODE:
return this.builder.buildGreater();
case JJTCOMPARISSONPREDICATE_LT_NODE:
return this.builder.buildLess();
case JJTCOMPARISSONPREDICATE_GTE_NODE:
return this.builder.buildGreaterOrEqual();
case JJTCOMPARISSONPREDICATE_LTE_NODE:
return this.builder.buildLessOrEqual();
default:
throw new CQLException("unexpeted filter type.");
}
}
/**
* On line cql interpreter
* @param args
* @throws ParseException
*/
public static void main(String args[]) throws ParseException {
System.out.println("Expecting a predicate (q - quit).");
while (true) {
try {
InputStreamReader reader = new InputStreamReader(System.in);
BufferedReader buf = new BufferedReader(reader);
String source = buf.readLine();
if ("q".equals(source)) {
System.out.println("bye.");
break;
}
FilterFactory ff = CommonFactoryFinder
.getFilterFactory((Hints) null);
CQLCompiler compiler = new CQLCompiler(source, ff);
compiler.compileFilter();
Filter filter = compiler.getFilter();
System.out.println("Result: " + filter);
} catch (CQLException e) {
System.out.println("Parsing error.");
System.out.println(e.getSyntaxError());
//e.printStackTrace();
} catch (IOException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
}