/* * 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; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import org.xml.sax.Attributes; /** * Creates filters from FilterFilter, which reads in a SAX stream and passes * the appropriate messages here. * * @author Rob Hranac, Vision for New York<br> * @author Chris Holmes, TOPP * * @source $URL$ * @version $Id$ */ public class FilterSAXParser { /** The logger for the filter module. */ private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.filter"); /** The number of attributes to be found in a like filter */ private static final int NUM_LIKE_ATTS = 3; /** The filter being currently constructed */ private Filter curFilter = null; /** The current completion state of the filter */ private String curState = "uninitialized"; /** The short representation of this type of filter */ private short filterType; /** factory for creating filters. */ private FilterFactory ff; /** * the Attributes of the filter (only applicable to LIKE filters, I think) */ private Map attributes = new HashMap(); /** * Constructor which flags the operator as between. */ public FilterSAXParser() { this( FilterFactoryFinder.createFilterFactory() ); } /** Constructor injdection */ public FilterSAXParser( FilterFactory factory ){ ff = factory; } /** Setter injection */ public void setFilterFactory( FilterFactory factory ){ ff = factory; } /** * Handles all incoming generic string 'messages,' including a message to * create the filter, based on the XML tag that represents the start of * the filter. * * @param filterType The string from the SAX filter. * * @throws IllegalFilterException Filter is illegal. */ public void start(short filterType) throws IllegalFilterException { LOGGER.finest("starting filter type " + filterType); if ((filterType == AbstractFilter.FID) && !curState.equals("fid")) { LOGGER.finer("creating the FID filter"); curFilter = ff.createFidFilter(); } else if (AbstractFilter.isGeometryDistanceFilter(filterType)) { curFilter = ff.createGeometryDistanceFilter(filterType); } else if (AbstractFilter.isGeometryFilter(filterType)) { curFilter = ff.createGeometryFilter(filterType); } else if (filterType == AbstractFilter.BETWEEN) { curFilter = ff.createBetweenFilter(); } else if (filterType == AbstractFilter.NULL) { curFilter = ff.createNullFilter(); } else if (filterType == AbstractFilter.LIKE) { curFilter = ff.createLikeFilter(); } else if (AbstractFilter.isCompareFilter(filterType)) { curFilter = ff.createCompareFilter(filterType); } else { throw new IllegalFilterException( "Attempted to start a new filter with invalid type: " + filterType); } curState = setInitialState(filterType); this.filterType = filterType; attributes = new HashMap(); } /** * Handles all incoming generic string 'messages,' including a message to * create the filter, based on the XML tag that represents the start of * the filter. * * @param message The string from the SAX filter. * * @throws IllegalFilterException Filter is illegal. */ public void value(String message) throws IllegalFilterException { } /** * Adds the passed in expression to the current filter. Generally created * by the ExpressionSAXParser. * * @param expression The value of the attribute for comparison. * * @throws IllegalFilterException if the expression does not match what the * current filter is expecting. * * @task REVISIT: split this method up. */ public void expression(Expression expression) throws IllegalFilterException { // Handle all filter compare states and expressions if (filterType == AbstractFilter.BETWEEN) { if (curState.equals("attribute")) { ((BetweenFilter) curFilter).addMiddleValue(expression); curState = "LowerBoundary"; } else if (curState.equals("LowerBoundary")) { ((BetweenFilter) curFilter).addLeftValue(expression); curState = "UpperBoundary"; } else if (curState.equals("UpperBoundary")) { ((BetweenFilter) curFilter).addRightValue(expression); curState = "complete"; } else { throw new IllegalFilterException( "Got expression for Between Filter in illegal state: " + curState); } } else if (AbstractFilter.isCompareFilter(filterType)) { if (curState.equals("leftValue")) { ((CompareFilter) curFilter).addLeftValue(expression); curState = "rightValue"; } else if (curState.equals("rightValue")) { ((CompareFilter) curFilter).addRightValue(expression); curState = "complete"; } else { throw new IllegalFilterException( "Got expression for Compare Filter in illegal state: " + curState); } } else if (filterType == AbstractFilter.NULL) { if (curState.equals("attribute")) { ((NullFilter) curFilter).nullCheckValue(expression); curState = "complete"; } else { throw new IllegalFilterException( "Got expression for Null Filter in illegal state: " + curState); } } else if (AbstractFilter.isGeometryFilter(filterType)) { if (curState.equals("leftValue")) { ((GeometryFilter) curFilter).addLeftGeometry(expression); curState = "rightValue"; } else if (curState.equals("rightValue")) { ((GeometryFilter) curFilter).addRightGeometry(expression); if (AbstractFilter.isGeometryDistanceFilter(filterType)) { curState = "distance"; } else { curState = "complete"; } LOGGER.finer("expression called on geometry, curState = " + curState); } else { throw new IllegalFilterException( "Got expression for Geometry Filter in illegal state: " + curState); } } else if (filterType == AbstractFilter.LIKE) { if (curState.equals("attribute")) { ((LikeFilter) curFilter).setValue(expression); curState = "pattern"; } else if (curState.equals("pattern")) { if (attributes.size() != NUM_LIKE_ATTS) { throw new IllegalFilterException( "Got wrong number of attributes (expecting 3): " + attributes.size() + "\n" + attributes); } String wildcard = (String) attributes.get("wildCard"); if ( (wildcard == null) || (wildcard.length() != 1) ) { throw new IllegalFilterException("like filter -- required attribute 'wildCard' missing or not exactly 1 char long. Capitalization?"); } String singleChar = (String) attributes.get("singleChar"); if ( (singleChar == null)|| (singleChar.length() != 1) ) { throw new IllegalFilterException("like filter -- required attribute 'singleChar' missing or not exactly 1 char long. Capitalization?"); } String escapeChar = (String) attributes.get("escape"); if (escapeChar == null) //totally against spec, but... escapeChar = (String) attributes.get("escapeChar"); if ( (escapeChar == null)|| (escapeChar.length() != 1) ) { throw new IllegalFilterException("like filter -- required attribute 'escape' missing or not exactly 1 char long. Capitalization?"); } LOGGER.fine("escape char is " + escapeChar); ((LikeFilter) curFilter).setPattern(expression, wildcard, singleChar, escapeChar); curState = "complete"; } else { throw new IllegalFilterException( "Got expression for Like Filter in illegal state: " + curState); } } LOGGER.finer("current state (end): " + curState); } /** * Creates the filter held in the parser. * * @return The current filter to be created by this parser. * * @throws IllegalFilterException If called before the filter is in a * complete state. */ public Filter create() throws IllegalFilterException { if (isComplete()) { LOGGER.finer("complete called, state = " + curState); curState = "complete"; //added by cholmes fid bug. return curFilter; } else { throw new IllegalFilterException( "Got to the end state of an incomplete filter, current" + " state is " + curState); } } /** * Sets the state that shall be expected next based on the fitlerType. So * if a between, null or like is the currentFilter then attribute should * be next, if an fid filter then fid should be next. If it's a * comparison, geometry or not, then leftValue should be next. * * @param filterType An AbstractFilter short of the filter type. * * @return the string of what state should come next. * * @throws IllegalFilterException if the filter type is not recognized. */ private static String setInitialState(short filterType) throws IllegalFilterException { if ((filterType == AbstractFilter.BETWEEN) || (filterType == AbstractFilter.NULL) || (filterType == AbstractFilter.LIKE)) { return "attribute"; } else if ((filterType == AbstractFilter.FID)) { return "fid"; } else if ((AbstractFilter.isCompareFilter(filterType)) || (AbstractFilter.isGeometryFilter(filterType))) { return "leftValue"; } else { throw new IllegalFilterException("Filter type: " + filterType + " is not recognized"); } } /** * This sets the distance for a GeometryDistanceFilter. It currently * ignores the units, and attempts to convert the distance to a double. * * @param distance the distance - should be a string of a double. * @param units a reference to a units dictionary. * * @throws IllegalFilterException if the distance string can not be * converted to a double. * * @task TODO: Implement units, probably with org.geotools.units package * and a special distance class in the filter package. It would be * nice if the distance class could get any type of units, like it * would handle the conversion. */ public void setDistance(String distance, String units) throws IllegalFilterException { LOGGER.finer("set distance called, current state is " + curState); if (curState.equals("distance")) { try { double distDouble = Double.parseDouble(distance); ((GeometryDistanceFilter) curFilter).setDistance(distDouble); curState = "complete"; } catch (NumberFormatException nfe) { throw new IllegalFilterException("could not parse distance: " + distance + " to a double"); } } else { throw new IllegalFilterException( "Got distance for Geometry Distance Filter in illegal state: " + curState + ", geometry and property should be set first"); } } /** * Sets the filter attributes. Called when attributes are encountered by * the filter filter. Puts them in a hash map by thier name and value. * * @param atts the attributes to set. */ public void setAttributes(Attributes atts) { LOGGER.finer("got attribute: " + atts.getLocalName(0) + ", " + atts.getValue(0)); LOGGER.finer("current state: " + curState); if (curState.equals("fid")) { LOGGER.finer("is a fid"); ((FidFilter) curFilter).addFid(atts.getValue(0)); LOGGER.finer("added fid"); } else { for (int i = 0; i < atts.getLength(); i++) { this.attributes.put(atts.getLocalName(i), atts.getValue(i)); } } } /** * Indicates that the filter is in a complete state (ready to be created.) * * @return <tt>true</tt> if the current state is either complete or fid */ private boolean isComplete() { return (curState.equals("complete") || curState.equals("fid")); } }