/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-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;
// Java Topology Suite dependencies
import java.util.logging.Logger;
import org.geotools.gml.GMLHandlerJTS;
import org.opengis.feature.simple.SimpleFeatureType;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.XMLFilterImpl;
import com.vividsolutions.jts.geom.Geometry;
/**
* Creates an OGC filter using a SAX filter.
*
* <p>
* Possibly the worst-named class of all time, <code>FilterFilter</code>
* extracts an OGC filter object from an XML stream and passes it to its
* parent as a fully instantiated OGC filter object.
* </p>
*
* @author Rob Hranac, Vision for New York
* @source $URL$
* @version $Id$
*/
public class FilterFilter extends XMLFilterImpl implements GMLHandlerJTS {
/** The logger for the filter module. */
private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.filter");
/** For handling and creating logic filters that come in. */
private LogicSAXParser logicFactory;
/** For handling and creating non-logic filters that come in */
private FilterSAXParser filterFactory;
/** For handling and creating expressions that come in. */
private ExpressionSAXParser expressionFactory;
/** Parent of the filter: must implement FilterHandler */
private FilterHandler parent;
/** The FeatureType to create attribute expressions against. */
private SimpleFeatureType schema;
/** Whether we are currently processing a logic filter. */
private boolean isLogicFilter = false;
/** Whether we are currently processing an fid filter. */
private boolean isFidFilter = false;
/** Whether Whether we are currently processing a filter. */
protected boolean insideFilter = false;
/** Whether we are inside a distance element or not. */
private boolean insideDistance = false;
/** units for a distance element attribute. somewhere else? */
private String units;
/** collects element content on each call to {@link #characters(char[], int, int)}
* to be processed by endElement */
private StringBuffer characters;
private boolean convertLiteralToNumber = true;
/**
* Constructor with parent, which must implement GMLHandlerJTS.
*
* @param parent The parent of this filter, to recieve the filters created.
* @param schema The schema that the filter will be used against.
*/
public FilterFilter(FilterHandler parent, SimpleFeatureType schema) {
super();
this.parent = parent;
this.schema = schema;
expressionFactory = new ExpressionSAXParser(schema);
filterFactory = new FilterSAXParser();
logicFactory = new LogicSAXParser();
characters = new StringBuffer();
}
/**
* Constructor with parent, which must implement GMLHandlerJTS.
*
* @param parent The parent of this filter, to recieve the filters created.
* @param schema The schema that the filter will be used against.
*/
public FilterFilter(FilterHandler parent, SimpleFeatureType schema,boolean convertLiteralToNumber ) {
this(parent,schema);
this.convertLiteralToNumber = convertLiteralToNumber;
}
/**
* Checks the name of the element, and sends to the appropriate filter
* creation factory.
*
* @param namespaceURI The namespace of the element.
* @param localName The local name of the element.
* @param qName The full name of the element, including namespace prefix.
* @param atts The element attributes.
*
* @throws SAXException Some parsing error occured while reading filter.
*/
public void startElement(String namespaceURI, String localName,
String qName, Attributes atts) throws SAXException {
LOGGER.finer("found start element: " + localName);
characters.setLength(0);
if (localName.equals("Filter")) {
//Should we check to make sure namespace is correct?
//perhaps let users set namespace aware...
insideFilter = true;
expressionFactory=new ExpressionSAXParser(schema);
} else if (insideFilter) {
short filterType = convertType(localName);
LOGGER.finest("types: (xml): " + localName + "; " + "(internal): "
+ filterType);
//DJB: if you use "<AND>" instead of "<And>" the filter appears to work
// but actually is completely screwed. This should throw an error.
// I'm not 100% sure if this is correct, but I dont think you can
// have any tag here that's not in "convertType()".
//DJB: Found "UpperBoundary" and "LowerBoundary" - these are completely ignored by this parser!!!
// More checking shows its handled when the tag ENDs, so this isnt a problem.
//DJB: <Distance> also looks like its hacked in this function (see bottom)
//DJB: Also found <gml:pointMember>-like things from the CITE tests.
//in all these cases, the correct (well, defined) thing to do is to ignore the tag - its never used anywhere!
if ( (filterType == -1) && !( (localName.equals("UpperBoundary")) || (localName.equals("LowerBoundary")) || (localName.equals("Distance")) ) )
{
if (!(localName.endsWith("Member"))) //from CITE tests
throw new SAXException("Attempted to construct illegal filter - I dont understand the tag: "+qName+". HINT: tags are case-sensitive!");
}
try {
if (isFidFilter) {
if (filterType == AbstractFilter.FID) {
LOGGER.finer(
"sending attributes to existing FID filter");
filterFactory.setAttributes(atts);
} else {
isFidFilter = false;
LOGGER.finer("is fid (1): " + isFidFilter);
// if the filter is done, pass along to the parent
if (isLogicFilter) {
addFilterToLogicFactory();
} else {
addFilterToParent();
}
}
}
if (!isFidFilter) {
// if at a complex filter start, add it to the logic stack
LOGGER.finest("is logic?");
if (AbstractFilter.isLogicFilter(filterType)) {
LOGGER.finer("found a logic filter start");
isLogicFilter = true;
logicFactory.start(filterType);
} else if (AbstractFilter.isSimpleFilter(filterType)) {
// if at a simple filter start, tell the factory
LOGGER.finer("found a simple filter start");
filterFactory.start(filterType);
if (filterType == AbstractFilter.LIKE) {
LOGGER.finer("sending attributes for like filter");
filterFactory.setAttributes(atts);
} else if (filterType == AbstractFilter.FID) {
LOGGER.finer("sending attributes to new FID filter");
filterFactory.setAttributes(atts);
isFidFilter = true;
LOGGER.finer("is fid (3): " + isFidFilter);
}
} else if (DefaultExpression.isExpression(filterType)) {
// if at an expression start, tell the factory
LOGGER.finest("found an expression filter start");
expressionFactory.start(localName,atts);
} else if (localName.equals("Distance")) { //DJB: this looks like hack
LOGGER.finest("inside distance");
//Not too sure what to do here, as units should be
//required element, so an error would be nice.
//But geotools is also not supporting units at all,
//so I feel like it doesn't matter so much...
if (("units").equals(atts.getLocalName(0))) {
units = atts.getValue(0);
LOGGER.finest("units = " + units);
}
insideDistance = true;
}
}
} catch (IllegalFilterException ife) {
throw new SAXException("Attempted to construct illegal "
+ "filter: " + ife.getMessage(), ife);
}
} else {
parent.startElement(namespaceURI, localName, qName, atts);
}
}
private void addFilterToParent() throws IllegalFilterException {
parent.filter(filterFactory.create());
expressionFactory = new ExpressionSAXParser(schema);
}
private void addFilterToLogicFactory() throws IllegalFilterException {
logicFactory.add(filterFactory.create());
expressionFactory = new ExpressionSAXParser(schema);
}
/**
* Reads the only internal characters read by filters. If we are in a
* distance filter than the distance is set in the filter factory, if not
* we forward directly along to the expression factory.
*
* @param chars Raw coordinate string from the filter document.
* @param start Beginning character position of raw string.
* @param length Length of the character string.
*
* @throws SAXException Some parsing error occurred while reading
* coordinates.
*/
public void characters(char[] chars, int start, int length)
throws SAXException {
//accumulate partial strings in the instance StringBuffer
//so we make sure the message is collected as a whole before
//passing it to expression factory
this.characters.append(chars, start, length);
}
/**
* Calling this method should be the first thing done by {@link #endElement(String, String, String)},
* to ensure the message passed to the expression factory contains the whole
* string accumulated by the potentially many calls to {@link #characters(char[], int, int)}
* done by the parser.
*
* @throws SAXException
*/
private void processCharacters() throws SAXException {
if (insideFilter) {
String message = this.characters.toString();
try {
if (insideDistance) {
LOGGER.finest("calling set distance on " + message + ", "
+ units);
filterFactory.setDistance(message, units);
} else {
LOGGER.finest("sending to expression factory: " + message);
expressionFactory.message(message,this.convertLiteralToNumber);
}
} catch (IllegalFilterException ife) {
throw new SAXException(ife);
}
}else if(characters.length() > 0){
LOGGER.finer("delegating characters to parent: " + characters.toString());
int len = this.characters.length();
char []chars = new char[this.characters.length()];
this.characters.getChars(0, len, chars, 0);
parent.characters(chars, 0, len);
}
}
/**
* Checks for filter element end and - if not a Filter then sends it
* directly to the appropriate filter factory.
*
* @param namespaceURI Namespace of the element.
* @param localName Local name of the element.
* @param qName Full name of the element, including namespace prefix.
*
* @throws SAXException Parsing error occurred while reading coordinates.
*/
public void endElement(String namespaceURI, String localName, String qName)
throws SAXException {
LOGGER.finer("found end element: " + localName);
processCharacters();
if (localName.equals("Filter")) {
//moved by cholmes, bug fix for fid.
if (isFidFilter && !localName.equals("FeatureId")) {
isFidFilter = false;
LOGGER.finer("is fid (2): " + isFidFilter);
// if the filter is done, pass along to the parent
try {
if (isLogicFilter) {
addFilterToLogicFactory();
} else {
addFilterToParent();
}
} catch (IllegalFilterException e) {
throw new SAXException(
"Attempted to construct illegal filter: "
+ e.getMessage());
}
}
insideFilter = false;
} else if (insideFilter) {
short filterType = convertType(localName);
try {
// if at the end of a complex filter, simplify the logic stack
// appropriately
if (AbstractFilter.isLogicFilter(filterType)) {
LOGGER.finest("found a logic filter end");
if (isFidFilter) {
addFilterToLogicFactory();
isFidFilter = false;
}
logicFactory.end(filterType);
// if the filter is done, pass along to the parent
if (logicFactory.isComplete()) {
LOGGER.finer("creating logic factory");
parent.filter(logicFactory.create());
}
//isFidFilter = false;
} else if (AbstractFilter.isSimpleFilter(filterType)
&& !isFidFilter) {
// if at the end of a simple filter, create it and push it
// on top of the logic stack
LOGGER.finest("found a simple filter end");
// if the filter is done, pass along to the parent
if (isLogicFilter) {
addFilterToLogicFactory();
} else {
addFilterToParent();
}
}
// if at the end of an expression, two cases:
// 1. at the end of an outer expression, create it and pass
// to filter
// 2. at end of an inner expression, pass the message along to
// current outer expression
else if (DefaultExpression.isExpression(filterType)) {
LOGGER.finer("found an expression filter end");
expressionFactory.end(localName);
if (expressionFactory.isReady()) {
LOGGER.finer("expression factory is ready");
filterFactory.expression(expressionFactory.create());
}
} else if (localName.equals("Distance")) {
insideDistance = false;
}
} catch (IllegalFilterException e) {
throw new SAXException(
"Attempted to construct illegal filter: " + e.getMessage());
}
} else {
parent.endElement(namespaceURI, localName, qName);
}
}
/**
* Recieves a geometry from its child filter.
*
* @param geometry The geometry from the filter.
*
* @throws RuntimeException if the filterFactory can't handle the geometry
*
* @task REVISIT: can we throw another exception?
*/
public void geometry(Geometry geometry) throws RuntimeException {
// Sends the geometry to the expression
try {
LOGGER.finer("got geometry: " + geometry);
expressionFactory.geometry(geometry);
if (expressionFactory.isReady()) {
LOGGER.finer("expression factory made expression and sent "
+ "to filter factory");
filterFactory.expression(expressionFactory.create());
}
} catch (IllegalFilterException ife) {
LOGGER.finer("Had problems adding geometry: " + geometry.toString());
throw new RuntimeException("problem adding geometry to filter ", ife);
}
}
/**
* Converts the string representation of the expression to the
* AbstractFilter or DefaultExpression short type.
*
* @param filterType Type of filter for check.
*
* @return the short representation of the filter.
*/
protected static short convertType(String filterType) {
// matches all filter types to the default logic type
if (filterType.equals("Or")) {
return Filter.LOGIC_OR;
} else if (filterType.equals("And")) {
return Filter.LOGIC_AND;
} else if (filterType.equals("Not")) {
return Filter.LOGIC_NOT;
} else if (filterType.equals("Equals")) {
return Filter.GEOMETRY_EQUALS;
} else if (filterType.equals("Disjoint")) {
return Filter.GEOMETRY_DISJOINT;
} else if (filterType.equals("DWithin")) {
return Filter.GEOMETRY_DWITHIN;
} else if (filterType.equals("Intersects")) {
return Filter.GEOMETRY_INTERSECTS;
} else if (filterType.equals("Touches")) {
return Filter.GEOMETRY_TOUCHES;
} else if (filterType.equals("Crosses")) {
return Filter.GEOMETRY_CROSSES;
} else if (filterType.equals("Within")) {
return Filter.GEOMETRY_WITHIN;
} else if (filterType.equals("Contains")) {
return Filter.GEOMETRY_CONTAINS;
} else if (filterType.equals("Overlaps")) {
return Filter.GEOMETRY_OVERLAPS;
} else if (filterType.equals("Beyond")) {
return Filter.GEOMETRY_BEYOND;
} else if (filterType.equals("BBOX")) {
return Filter.GEOMETRY_BBOX;
} else if (filterType.equals("PropertyIsEqualTo")) {
return FilterType.COMPARE_EQUALS;
} else if (filterType.equals("PropertyIsNotEqualTo")) {
return FilterType.COMPARE_NOT_EQUALS;
} else if (filterType.equals("PropertyIsLessThan")) {
return FilterType.COMPARE_LESS_THAN;
} else if (filterType.equals("PropertyIsGreaterThan")) {
return FilterType.COMPARE_GREATER_THAN;
} else if (filterType.equals("PropertyIsLessThanOrEqualTo")) {
return FilterType.COMPARE_LESS_THAN_EQUAL;
} else if (filterType.equals("PropertyIsGreaterThanOrEqualTo")) {
return FilterType.COMPARE_GREATER_THAN_EQUAL;
} else if (filterType.equals("PropertyIsBetween")) {
return AbstractFilter.BETWEEN;
} else if (filterType.equals("PropertyIsLike")) {
return AbstractFilter.LIKE;
} else if (filterType.equals("PropertyIsNull")) {
return AbstractFilter.NULL;
} else if (filterType.equals("FeatureId")) {
return AbstractFilter.FID;
} else if (filterType.equals("Add")) {
return DefaultExpression.MATH_ADD;
} else if (filterType.equals("Sub")) {
return DefaultExpression.MATH_SUBTRACT;
} else if (filterType.equals("Mul")) {
return DefaultExpression.MATH_MULTIPLY;
} else if (filterType.equals("Div")) {
return DefaultExpression.MATH_DIVIDE;
} else if (filterType.equals("PropertyName")) {
return DefaultExpression.LITERAL_DOUBLE;
} else if (filterType.equals("Literal")) {
return DefaultExpression.ATTRIBUTE_DOUBLE;
} else if (filterType.equals("Function")) {
return DefaultExpression.FUNCTION;
} else {
return -1;
}
}
}