/* * 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; // J2SE dependencies import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.logging.Logger; /** * Processes messages from clients to create Logic Filters. Handles nested * logic filters. Filters should call start and end when they reach logic * filters, and create when the filter is complete. * * This documenation provided by Dave Blasby April 1/2005 after fixing GEOS-328: * DJB: okay, there's no where near enough comments in here to understand whats going on. Hopefully I'm correct. * I've looked at this for a bit, and this is what I can figure out. * * This is called by the FilterFilter class (nice name...NOT) which is also a sax parser-like class. * Basically, the FilterFilter does most of the Filter parsing - but it hands most of the work off to the * appropriate classes. For NOT, AND, OR clauses, this class is used. * * As a simple example, <filter> <OR> [STATE_NAME = 'NY'] [STATE_NAME = 'WA'] </OR></FILTER> * Or, in long form: * <Filter> * <Or> * <PropertyIsEqualTo> * <PropertyName>STATE_NAME</PropertyName> * <Literal>NY</Literal> * </PropertyIsEqualTo> * <PropertyIsEqualTo> * <PropertyName>STATE_NAME</PropertyName> * <Literal>WA</Literal> * </PropertyIsEqualTo> * </Or> * </Filter> * * The "PropertyIsEqualTo" is handled by another parser, so we dont have to worry about it here for the moment. * * So, the order of events are like this: * * * start( "OR" ) * add([STATE_NAME = 'NY']) // these are handled by another class * add([STATE_NAME = 'WA']) // these are handled by another class * end ("OR") * create() // this creates an actual Filter [[ STATE_NAME = NY ] OR [ STATE_NAME = WA ]] * * This is pretty simple, but it gets more complex when you have nested structures. * * * <Filter> * <And> * <Or> * <PropertyIsEqualTo> * <PropertyName>STATE_NAME</PropertyName> * <Literal>NY</Literal> * </PropertyIsEqualTo> * <PropertyIsEqualTo> * <PropertyName>STATE_NAME</PropertyName> * <Literal>WA</Literal> * </PropertyIsEqualTo> * </Or> * <PropertyIsEqualTo> * <PropertyName>STATE_NAME</PropertyName> * <Literal>BC</Literal> * </PropertyIsEqualTo> * </And> * </Filter> * * Again, we're going to ignore the "PropertyIsEqualTo" stuff since its handled elsewhere. * * The main idea is that there will be a LogicSAXParser for the top-level "AND" and another one * for the nested "OR". It gets a bit harder to describe because the classes start passing events * to each other. * * start("AND") -- the parent LogicSAXParser starts to construct an "AND" filter * start("OR") -- the "AND" parser sees that its sub-element is another logic operator. * It makes another LogicSAXParser that will handle the "OR" SAX events. * add([STATE_NAME = 'NY']) -- this is sent to the "AND" parser. It then sends it to the "OR" parser. * + "OR" parser remembers this component * add([STATE_NAME = 'WA']) -- this is sent to the "AND" parser. It then sends it to the "OR" parser. * + "OR" parser remembers this component * end("OR") -- this is sent to the "AND" parser. It then sends it to the "OR" parser. * + The "OR" parser marks itself as complete * + The "AND" parser notices that its child is completed parsing * + The "AND" parser calls create() on the "OR" parser to make a filter (see next step) * + Since "OR" is finished, "AND" stop passing events to it. * "OR".create() -- makes a "[[ STATE_NAME = NY ] OR [ STATE_NAME = WA ]]" and "AND" remembers it as a component * add ([ STATE_NAME = BC ]) --This is added as a component to the "AND" filter. * end ("AND") -- the "AND" parser marks itself as complete * create() -- the "AND" parser creates a FILTER [[[ STATE_NAME = NY ] OR [ STATE_NAME = WA ]] AND [ STATE_NAME = BC ]] * * * Higher levels of nesting work the same way - each level will send the event down to the next level. * * If logicFilter == null then this object is the one doing the processing. If its non-null, then * the sub-object is doing the processing - event are sent to it. * * @author Rob Hranac, Vision for New York * @author Chris Holmes, TOPP * * @source $URL$ * @version $Id$ */ public class LogicSAXParser { /** The logger for the filter module. */ private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.filter"); /** factory for creating filters. */ private FilterFactory ff; /** AbstractFilter filte type. */ private short logicType = -1; /** The array of filters to be logically joined. */ private List subFilters = new ArrayList(); /** An instance of this class for nested logic filter structures. */ private LogicSAXParser logicFactory = null; /** if this logic filter is ready to be created. */ private boolean isComplete = false; /** * Constructor which flags the operator as between. */ public LogicSAXParser() { this( FilterFactoryFinder.createFilterFactory() ); } /** Constructor injection */ public LogicSAXParser( FilterFactory factory ){ ff = factory; LOGGER.finer("made new logic factory"); } /** Setter injection */ public void setFilterFactory( FilterFactory factory ){ ff = factory; } /** * To be called by a parser to start the creation of a logic filter. Can * start a nested or a base logic filter. * * @param logicType OR, or AND abstract filter type. * * @throws IllegalFilterException if filter type does not match declared * type. */ // logic types are AND=2, OR=1, NOT=3 public void start(short logicType) throws IllegalFilterException { LOGGER.finest("got a start element: " + logicType); if (this.logicType != -1) { //DJB: for GEOS-328 we need to keep the old parser around to handle multiple nestings of logic operators. if (logicFactory == null) { logicFactory = new LogicSAXParser(); } logicFactory.start(logicType); } else if (!AbstractFilter.isLogicFilter(logicType)) { throw new IllegalFilterException( "Add logic filter type does not match declared type."); } else { this.logicType = logicType; } } /** * To be called when the sax parser reaches the end of a logic filter. * Tells this class to complete. * * @param logicType the Filter type. * * @throws IllegalFilterException If the end message can't be processed in * this state. */ // logic types are AND=2, OR=1, NOT=3 public void end(short logicType) throws IllegalFilterException { LOGGER.finer("got an end element: " + logicType); if (logicFactory != null) { LOGGER.finer("sending end element to nested logic filter: " + logicType); logicFactory.end(logicType); if (logicFactory.isComplete()) { subFilters.add(logicFactory.create()); logicFactory = null; } } else if (this.logicType == logicType) { LOGGER.finer("end element matched internal type: " + this.logicType); isComplete = true; } else { throw new IllegalFilterException( "Logic Factory got an end message that it can't process."); } } /** * Adds a filter to the current logic list. * * @param filter The filter to be added. */ public void add(Filter filter) { LOGGER.finer("added a filter: " + filter.toString()); if (logicFactory != null) { LOGGER.finer("adding to nested logic filter: " + filter.toString()); logicFactory.add(filter); } else { LOGGER.finer("added to sub filters: " + filter.toString()); subFilters.add(filter); } } /** * Creates the the logic filter if in a complete state. * * @return The created logic filter. * * @throws IllegalFilterException if the filter is not complete. */ public Filter create() throws IllegalFilterException { LogicFilter filter = null; LOGGER.finer("creating a logic filter"); if (isComplete()) { LOGGER.finer("filter is complete, with type: " + this.logicType); if (logicType == AbstractFilter.LOGIC_NOT) { filter = ff.createLogicFilter((Filter) subFilters.get( 0), this.logicType); } else { filter = ff.createLogicFilter(this.logicType); Iterator iterator = subFilters.iterator(); while (iterator.hasNext()) { filter.addFilter((org.opengis.filter.Filter) iterator.next()); } } //reset the variables so it works right if called again. subFilters = new ArrayList(); this.logicType = -1; isComplete = false; return filter; } else { throw new IllegalFilterException( "Attempted to generate incomplete logic filter."); } } /** * indicates if the logic filter is complete. * * @return <tt>true</tt> if this holds a complete logic filter to be * created, <tt>false</tt> otherwise. */ public boolean isComplete() { return isComplete; } }