/*
* 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.Iterator;
import org.geotools.factory.CommonFactoryFinder;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
/**
* Determines which parts of a Filter can be turned into valid SQL statements.
* Given a filter it constructs two filters, one of the supported parts of the
* filter passed in, one of the unsupported. If one of the constructed
* filters is null (ie the whole filter is supported or unsupported), it is
* the clients responsibility to deal with that. The SQLUnpacker should be
* tightly coordinated with the SQLEncoder. The SQLEncoder passes its
* Capabilities (ie which filters it can encode and which it can't) to the
* Unpacker, and the Unpacker returns a supported filter, which should be
* passed to the Encoder as the Encoder Capabilities claimed to fully support
* everything in the supported filter. The unsupported filter should be used
* after the SQL statement is executed, testing each feature in the result set
* with the contains method.
*
* <p>
* This Unpacker can likely be easily used with any Encoder that has a
* FilterCapabilities of the actions it can perform. May want to rename it
* FilterUnpacker, as it is likely generic enough, but for now this name
* should be fine, to emphasize that the SQLEncoder needs to be closely linked
* to it to work properly.
* </p>
*
* @author Chris Holmes, TOPP
*
* @task REVISIT: The getSupported getUnsupported is clunky and dangerous, as
* clients could be using this and do an unpack, get the unsupported
* filter, and then do another unpack, and want to get the first
* supported filter, and would get the second instead. This is likely
* in a getFeatures when the unpacker is held by the class. So for now
* clients should construct an unpacker whenever they want to use it.
* This is obviously less than ideal. So this should be revisited. One
* way is fir the unpack methods to return FilterPairs, for the clients
* to deal with themselves. I'm not sure that this is the best
* semantic, and it exposes an inner class that really has no other use,
* so it could be nice to do it a better way. Another option is to have
* static methods SQLUnpacker.getSupported(Filter, splitType,
* capabilities), or something to that effect. Or non static, but pass
* the filter in each time. If anyone is looking at this class and has
* suggestions email the list, as we should think this through more, but
* I've other pressing tasks.
* @deprecated please use PostPreProcessFilterSplittingVisitor
*
* @source $URL$
*
* @deprecated scheduled for removal in 2.7, use classes in org.geotools.jdbc
*/
public class SQLUnpacker {
/**
* FilterPair is an inner class, for holding the unsupported and supported
* filters
*/
private FilterPair pair;
/** The types of Filters that should be part of the supported Filter */
private FilterCapabilities capabilities;
private FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
/**
* Constructor with FilterCapabilities from the Encoder used in conjunction
* with this Unpacker.
*
* @param capabilities what FilterTypes should be supported.
*/
public SQLUnpacker(FilterCapabilities capabilities) {
this.capabilities = capabilities;
}
/**
* Performs the unpacking of a filter, for the cases when ANDs can be split
* and ORs can not. To get the results of the unpacking getUnsupported
* and getSupported must be called before another unpacking is done.
*
* @param filter to be unpacked, split on ANDs
*/
public void unPackAND(org.opengis.filter.Filter filter) {
pair = doUnPack(filter, AbstractFilter.LOGIC_AND);
}
/**
* Performs the unpacking of a filter, for the cases when ORs can be split
* and ANDs can not. To get the results of the unpacking getUnsupported
* and getSupported must be called before another unpacking is done.
*
* @param filter to be unpacked, split on ANDs
*/
public void unPackOR(Filter filter) {
pair = doUnPack(filter, AbstractFilter.LOGIC_OR);
}
/**
* After an unPack has been called, returns the resulting Filter of the
* unsupported parts of the unPacked filter. If the unPacked filter is
* fully supported this returns a null, it is the client's responsibility
* to deal with it. If there are multiple unsupported subfilters they are
* ANDed together.
*
* @return A filter of the unsupported parts of the unPacked filter.
*/
public Filter getUnSupported() {
return pair.getUnSupported();
}
/**
* After an unPack has been called, returns the resulting Filter of the
* supported parts of the unPacked filter. If the unPacked filter is not
* supported at all this returns a null, it is the client's responsibility
* to deal with it. If there are multiple supported subfilters they are
* ANDed together.
*
* @return A filter of the supported parts of the unPacked filter.
*/
public Filter getSupported() {
return pair.getSupported();
}
/**
* Performs the actual recursive unpacking of the filter. Can do the
* unpacking on either AND or OR filters.
*
* @param filter the filter to be split
* @param splitType the short representation of the logic filter to
* recursively unpack.
*
* @return A filter of the unsupported parts of the unPacked filter.
*/
private FilterPair doUnPack(org.opengis.filter.Filter filter, short splitType) {
/*
* Implementation notes: This is recursive, so it's worth explaining.
* The base cases are either the filter is fully supported, ie all of
* its subFilters are supported, and thus it can be totally encoded,
* or it is not supported. The recursive cases are when the filter is
* not fully supported and it is an AND or a NOT filter. In these
* cases the filter can be split up, and each subfilter can return
* some supported filters and some unsupported filters. If it is an
* OR filter and not fully supported we can descend no further, as
* each part of the OR needs to be tested, we can't put part in the
* SQL statement and part in the filter. So if it is an AND filter,
* we get teh subFilters and call doUnPack on each subFilter,
* combining the Unsupported and Supported FilterPairs of each
* subFilter into a single filter pair, which is the pair we will
* return. If a subfilter in turn is an AND with its own subfilters,
* they return their own unSupported and Supported filters, because it
* will eventually hit the base case. The base cases return null for
* half of the filter pair, and return the filter for the other half,
* depending on if it's unsupported or supported. For the NOT filter,
* it just descends further, unpacking the filter inside the NOT, and
* then tacking NOTs on the supported and unsupported sub filters.
* ---addition: No longer just ANDs supported. ORs can be split,
* same as previous paragraph, but switch ORs with ANDs, there are
* cases, such as the delete statement, where we have to split on ORs
* and can't on ANDs (opposite of get statement). Should work the
* same, just a different logic filter.
*/
FilterPair retPair;
FilterPair subPair;
Filter subSup = null; //for logic iteration
Filter subUnSup = null; //for logic iteration
Filter retSup = null; //for return pair
Filter retUnSup = null; //for return pair
if (filter == null) {
return new FilterPair(null, null);
}
if (capabilities.fullySupports(filter)) {
retSup = filter;
} else {
short type = Filters.getFilterType(filter);
if ((type == splitType) && capabilities.supports(splitType)) {
//REVISIT: one special case not covered, when capabilities
//does not support AND and it perfectly splits the filter
//into unsupported and supported
Iterator filters = ((LogicFilter) filter).getFilterIterator();
while (filters.hasNext()) {
Filter next = (Filter) filters.next();
subPair = doUnPack(next, splitType);
subSup = subPair.getSupported();
subUnSup = subPair.getUnSupported();
retSup = combineFilters(retSup, subSup, splitType);
retUnSup = combineFilters(retUnSup, subUnSup, splitType);
}
} else if ((type == AbstractFilter.LOGIC_NOT)
&& capabilities.supports(AbstractFilter.LOGIC_NOT)) {
Iterator filters = ((LogicFilter) filter).getFilterIterator();
//NOT only has one, so just get filters.next()
subPair = doUnPack((Filter) filters.next(), splitType);
subSup = subPair.getSupported();
subUnSup = subPair.getUnSupported();
if (subSup != null) {
retSup = ff.not( subSup );
}
if (subUnSup != null) {
retUnSup = ff.not( subUnSup );
}
} else { //it's not supported and has no logic subfilters to be split.
retUnSup = filter;
}
}
retPair = new FilterPair(retSup, retUnSup);
return retPair;
}
/**
* Combines two filters, which may be null, into one. If one is null and
* the other not, it returns the one that's not. If both are null returns
* null.
*
* @param filter1 one filter to be combined.
* @param filter2 the other filter to be combined.
* @param splitType the short representation of the logic filter to
* recursively unpack.
*
* @return the resulting combined filter.
*/
private Filter combineFilters(Filter filter1, Filter filter2,
short splitType) {
Filter retFilter;
if (filter1 != null) {
if (filter2 != null) {
if (splitType == AbstractFilter.LOGIC_AND) {
retFilter = Filters.and( ff, filter1, filter2 );
} else { //OR and AND only split types, this must be or.
retFilter = Filters.or( ff, filter1, filter2 );
}
} else {
retFilter = filter1;
}
} else {
if (filter2 != null) {
retFilter = filter2;
} else {
retFilter = null;
}
}
return retFilter;
}
/**
* An inner class to hold a pair of Filters. Reasoning behind inner class
* is that if made public it would clutter up the filter folder with a
* FilterPair, which seems like it might be widely used, but is only
* necessary for one class. To return filter pairs would be slightly
* cleaner, in terms of writing code, but this way is cleaner for the
* geotools filter module.
*/
private class FilterPair {
/** half of the filter pair */
private Filter supported;
/** the other half */
private Filter unSupported;
/**
* Constructor takes the two filters of the pair.
*
* @param supported The filter that can be encoded.
* @param unSupported the filter that can't be encoded.
*/
public FilterPair(Filter supported, Filter unSupported) {
this.supported = supported;
this.unSupported = unSupported;
}
/**
* Accessor method.
*
* @return the supported Filter.
*/
public Filter getSupported() {
return supported;
}
/**
* Accessor method.
*
* @return the unSupported Filter.
*/
public Filter getUnSupported() {
return unSupported;
}
}
}