public Object visit(BBOX filter, Object notUsed) {
    if (filter instanceof BBOX3D && !fcs.supports(BBOX3D.class)) {
      postStack.push(filter);
    } else if (!fcs.supports(BBOX.class)) {
      postStack.push(filter);
    } else {
      preStack.push(filter);
    }

    return null;
  }
  /** @see org.geotools.filter.FilterVisitor#visit(org.geotools.filter.FunctionExpression) */
  public Object visit(Function expression, Object notUsed) {
    if (!fcs.supports(expression.getClass())) {
      postStack.push(expression);
      return null;
    }

    if (expression.getName() == null) {
      postStack.push(expression);
      return null;
    }

    int i = postStack.size();
    int j = preStack.size();

    for (int k = 0; k < expression.getParameters().size(); k++) {
      ((Expression) expression.getParameters().get(i)).accept(this, null);

      if (i < postStack.size()) {
        while (j < preStack.size()) preStack.pop();
        postStack.pop();
        postStack.push(expression);

        return null;
      }
    }
    while (j < preStack.size()) preStack.pop();
    preStack.push(expression);
    return null;
  }
 /**
  * @see FilterVisitor#visit(ExcludeFilter, Object)
  * @param filter the {@link Filter} to visit
  */
 public void visit(ExcludeFilter filter) {
   if (fcs.supports(Filter.EXCLUDE)) {
     preStack.push(filter);
   } else {
     postStack.push(filter);
   }
 }
 public Object visit(ExcludeFilter filter, Object notUsed) {
   if (fcs.supports(Filter.EXCLUDE)) {
     preStack.push(filter);
   } else {
     postStack.push(filter);
   }
   return null;
 }
  public Object visit(Id filter, Object notUsed) {
    if (original == null) original = filter;

    if (!fcs.supports(filter)) {
      postStack.push(filter);
    } else {
      preStack.push(filter);
    }

    return null;
  }
  private void visitMathExpression(BinaryExpression expression) {
    if (!fcs.supports(Add.class)
        && !fcs.supports(Subtract.class)
        && !fcs.supports(Multiply.class)
        && !fcs.supports(Divide.class)) {
      postStack.push(expression);
      return;
    }

    int i = postStack.size();
    Expression leftValue = expression.getExpression1();
    Expression rightValue = expression.getExpression2();
    if (leftValue == null || rightValue == null) {
      postStack.push(expression);
      return;
    }
    leftValue.accept(this, null);

    if (i < postStack.size()) {
      postStack.pop();
      postStack.push(expression);

      return;
    }

    rightValue.accept(this, null);

    if (i < postStack.size()) {
      preStack.pop(); // left
      postStack.pop();
      postStack.push(expression);

      return;
    }

    preStack.pop(); // left side
    preStack.pop(); // right side
    preStack.push(expression);
  }
  protected Object visit(BinaryTemporalOperator filter, Object data) {
    if (original == null) original = filter;

    // supports it as a group -- no need to check the type
    if (!fcs.supports(filter)) {
      postStack.push(filter);
      return null;
    }

    Expression leftValue = filter.getExpression1();
    Expression rightValue = filter.getExpression2();

    int i = postStack.size();
    if (leftValue == null || rightValue == null) {
      postStack.push(filter);
      return null;
    }

    leftValue.accept(this, null);

    if (i < postStack.size()) {
      postStack.pop();
      postStack.push(filter);

      return null;
    }

    rightValue.accept(this, null);

    if (i < postStack.size()) {
      preStack.pop(); // left
      postStack.pop();
      postStack.push(filter);

      return null;
    }

    preStack.pop(); // left side
    preStack.pop(); // right side
    preStack.push(filter);
    return null;
  }
  private void visitBinaryComparisonOperator(BinaryComparisonOperator filter) {
    if (original == null) original = filter;

    // supports it as a group -- no need to check the type
    if (!fcs.supports(FilterCapabilities.SIMPLE_COMPARISONS_OPENGIS)) {
      postStack.push(filter);
      return;
    }

    int i = postStack.size();
    Expression leftValue = filter.getExpression1();
    Expression rightValue = filter.getExpression2();
    if (leftValue == null || rightValue == null) {
      postStack.push(filter);
      return;
    }

    leftValue.accept(this, null);

    if (i < postStack.size()) {
      postStack.pop();
      postStack.push(filter);

      return;
    }

    rightValue.accept(this, null);

    if (i < postStack.size()) {
      preStack.pop(); // left
      postStack.pop();
      postStack.push(filter);

      return;
    }

    preStack.pop(); // left side
    preStack.pop(); // right side
    preStack.push(filter);
  }
  public Object visit(PropertyIsNull filter, Object notUsed) {
    if (original == null) original = filter;

    if (!fcs.supports(PropertyIsNull.class)) {
      postStack.push(filter);

      return null;
    }

    int i = postStack.size();
    ((PropertyIsNull) filter).getExpression().accept(this, null);

    if (i < postStack.size()) {
      postStack.pop();
      postStack.push(filter);
    }

    preStack.pop(); // null
    preStack.push(filter);

    return null;
  }
  Object visitNullNil(Filter filter, Expression e) {
    if (original == null) original = filter;

    if (!fcs.supports(PropertyIsNull.class)) {
      postStack.push(filter);

      return null;
    }

    int i = postStack.size();
    e.accept(this, null);

    if (i < postStack.size()) {
      postStack.pop();
      postStack.push(filter);
      return null;
    }

    preStack.pop(); // null
    preStack.push(filter);

    return null;
  }
  @Override
  protected FilterCapabilities createFilterCapabilities() {
    FilterCapabilities caps = new FilterCapabilities();
    caps.addAll(SQLDialect.BASE_DBMS_CAPABILITIES);

    // adding the spatial filters support
    caps.addType(BBOX.class);
    caps.addType(Contains.class);
    caps.addType(Crosses.class);
    caps.addType(Disjoint.class);
    caps.addType(Equals.class);
    caps.addType(Intersects.class);
    caps.addType(Overlaps.class);
    caps.addType(Touches.class);
    caps.addType(Within.class);
    caps.addType(DWithin.class);
    caps.addType(Beyond.class);

    // temporal filters
    caps.addType(After.class);
    caps.addType(Before.class);
    caps.addType(Begins.class);
    caps.addType(BegunBy.class);
    caps.addType(During.class);
    caps.addType(TOverlaps.class);
    caps.addType(Ends.class);
    caps.addType(EndedBy.class);
    caps.addType(TEquals.class);

    return caps;
  }
  private void visitLogicOperator(Filter filter) {
    if (original == null) original = filter;

    if (!fcs.supports(Not.class) && !fcs.supports(And.class) && !fcs.supports(Or.class)) {
      postStack.push(filter);
      return;
    }

    int i = postStack.size();
    int j = preStack.size();
    if (filter instanceof Not) {

      if (((Not) filter).getFilter() != null) {
        Filter next = ((Not) filter).getFilter();
        next.accept(this, null);

        if (i < postStack.size()) {
          // since and can split filter into both pre and post parts
          // the parts have to be combined since ~(A^B) == ~A | ~B
          // combining is easy since filter==combined result however both post and pre stacks
          // must be cleared since both may have components of the filter
          popToSize(postStack, i);
          popToSize(preStack, j);
          postStack.push(filter);
        } else {
          popToSize(preStack, j);
          preStack.push(filter);
        }
      }
    } else {
      if (filter instanceof Or) {
        Filter orReplacement;

        try {
          orReplacement = translateOr((Or) filter);
          orReplacement.accept(this, null);
        } catch (IllegalFilterException e) {
          popToSize(preStack, j);
          postStack.push(filter);
          return;
        }
        if (postStack.size() > i) {
          popToSize(postStack, i);
          postStack.push(filter);

          return;
        }

        preStack.pop();
        preStack.push(filter);
      } else {
        // it's an AND
        Iterator it = ((And) filter).getChildren().iterator();

        while (it.hasNext()) {
          Filter next = (Filter) it.next();
          next.accept(this, null);
        }

        // combine the unsupported and add to the top
        if (i < postStack.size()) {
          if (filter instanceof And) {
            Filter f = (Filter) postStack.pop();

            while (postStack.size() > i) f = ff.and(f, (Filter) postStack.pop());

            postStack.push(f);

            if (j < preStack.size()) {
              f = (Filter) preStack.pop();

              while (preStack.size() > j) f = ff.and(f, (Filter) preStack.pop());
              preStack.push(f);
            }
          } else {
            logger.warning("LogicFilter found which is not 'and, or, not");

            popToSize(postStack, i);
            popToSize(preStack, j);

            postStack.push(filter);
          }
        } else {
          popToSize(preStack, j);
          preStack.push(filter);
        }
      }
    }
  }
  private void visitBinarySpatialOperator(BinarySpatialOperator filter) {
    if (original == null) original = filter;

    Class[] spatialOps =
        new Class[] {
          Beyond.class,
          Contains.class,
          Crosses.class,
          Disjoint.class,
          DWithin.class,
          Equals.class,
          Intersects.class,
          Overlaps.class,
          Touches.class,
          Within.class
        };

    for (int i = 0; i < spatialOps.length; i++) {
      if (spatialOps[i].isAssignableFrom(filter.getClass())) {
        if (!fcs.supports(spatialOps[i])) {
          postStack.push(filter);
          return;
        } else {
          // fcs supports this filter, no need to check the rest
          break;
        }
      }
    }

    // TODO check against tranasaction ?

    int i = postStack.size();

    Expression leftGeometry, rightGeometry;
    leftGeometry = ((BinarySpatialOperator) filter).getExpression1();
    rightGeometry = ((BinarySpatialOperator) filter).getExpression2();

    if (leftGeometry == null || rightGeometry == null) {
      postStack.push(filter);
      return;
    }
    leftGeometry.accept(this, null);

    if (i < postStack.size()) {
      postStack.pop();
      postStack.push(filter);

      return;
    }

    rightGeometry.accept(this, null);

    if (i < postStack.size()) {
      preStack.pop(); // left
      postStack.pop();
      postStack.push(filter);

      return;
    }

    preStack.pop(); // left side
    preStack.pop(); // right side
    preStack.push(filter);
  }
  /**
   * @see FilterVisitor#visit(PropertyIsBetween, Object)
   *     <p>NOTE: This method is extra documented as an example of how all the other methods are
   *     implemented. If you want to know how this class works read this method first!
   * @param filter the {@link Filter} to visit
   */
  public Object visit(PropertyIsBetween filter, Object extradata) {
    if (original == null) original = filter;

    // Do we support this filter type at all?
    if (fcs.supports(PropertyIsBetween.class)) {
      // Yes, we do.  Now, can we support the sub-filters?

      // first, remember how big the current list of "I can't support these"
      // filters is.
      int i = postStack.size();

      Expression lowerBound = filter.getLowerBoundary();
      Expression expr = filter.getExpression();
      Expression upperBound = filter.getUpperBoundary();
      if (lowerBound == null || upperBound == null || expr == null) {
        // Well, one of the boundaries is null, so I guess
        // we're saying that *no* datastore could support this.
        postStack.push(filter);
        return null;
      }

      // Ok, here's the magic.  We know how big our list of "can't support"
      // filters is.  Now we send off the lowerBound Expression to see if
      // it can be supported.
      lowerBound.accept(this, null);

      // Now we're back, and we check.  Did the postStack get bigger?
      if (i < postStack.size()) {
        // Yes, it did.  Well, that means we can't support
        // this particular filter.  Let's back out anything that was
        // added by the lowerBound.accept() and add ourselves.
        postStack.pop(); // lowerBound.accept()'s bum filter
        postStack.push(filter);

        return null;
      }

      // Aha!  The postStack didn't get any bigger, so we're still
      // all good.  Now try again with the middle expression itself...

      expr.accept(this, null);

      // Did postStack get bigger?
      if (i < postStack.size()) {
        // Yes, it did.  So that means we can't support
        // this particular filter.  We need to back out what we've
        // done, which is BOTH the lowerbounds filter *and* the
        // thing that was added by expr.accept() when it failed.
        preStack.pop(); // lowerBound.accept()'s success
        postStack.pop(); // expr.accept()'s bum filter
        postStack.push(filter);

        return null;
      }

      // Same deal again...
      upperBound.accept(this, null);

      if (i < postStack.size()) {
        // post process it
        postStack.pop(); // upperBound.accept()'s bum filter
        preStack.pop(); // expr.accept()'s success
        preStack.pop(); // lowerBound.accept()'s success
        postStack.push(filter);

        return null;
      }

      // Well, by getting here it means that postStack didn't get
      // taller, even after accepting all three middle filters.  This
      // means that this whole filter is totally pre-filterable.

      // Let's clean up the pre-stack (which got one added to it
      // for the success at each of the three above .accept() calls)
      // and add us to the stack.

      preStack.pop(); // upperBounds.accept()'s success
      preStack.pop(); // expr.accept()'s success
      preStack.pop(); // lowerBounds.accept()'s success

      // finally we add ourselves to the "can be pre-proccessed" filter
      // stack.  Now when we return we've added exactly one thing to
      // the preStack...namely, the given filter.
      preStack.push(filter);
    } else {
      // No, we don't support this filter.
      // So we push it onto the postStack, saying
      // "Hey, here's one more filter that we don't support.
      // Someone who called us may look at this and say,
      // "Hmm, I called accept() on this filter and now
      // the postStack is taller than it was...I guess this
      // filter wasn't accepted.
      postStack.push(filter);
    }
    return null;
  }
  /**
   * Sets the capabilities of this filter.
   *
   * @return FilterCapabilities for this Filter
   */
  protected FilterCapabilities createFilterCapabilities() {
    capabilities = new FilterCapabilities();
    capabilities.addAll(FilterCapabilities.LOGICAL_OPENGIS);
    capabilities.addAll(FilterCapabilities.SIMPLE_COMPARISONS_OPENGIS);
    capabilities.addType(FilterCapabilities.FID);
    capabilities.addType(FilterCapabilities.BETWEEN);
    capabilities.addType(FilterCapabilities.LIKE);
    capabilities.addType(FilterCapabilities.NULL_CHECK);
    capabilities.addType(FilterCapabilities.SPATIAL_BBOX);
    capabilities.addType(FilterCapabilities.SPATIAL_DISJOINT);
    capabilities.addType(FilterCapabilities.SPATIAL_WITHIN);
    capabilities.addType(FilterCapabilities.SPATIAL_INTERSECT);
    capabilities.addType(FilterCapabilities.SPATIAL_CONTAINS);
    // temporal filters
    capabilities.addType(After.class);
    capabilities.addType(Before.class);
    capabilities.addType(Begins.class);
    capabilities.addType(BegunBy.class);
    capabilities.addType(During.class);
    capabilities.addType(Ends.class);
    capabilities.addType(EndedBy.class);
    capabilities.addType(TContains.class);
    capabilities.addType(TEquals.class);

    return capabilities;
  }
  /**
   * Sets the DB2 filter capabilities.
   *
   * @return FilterCapabilities for DB2
   */
  protected FilterCapabilities createFilterCapabilities() {
    FilterCapabilities caps = new FilterCapabilities();
    caps.addAll(SQLDialect.BASE_DBMS_CAPABILITIES);

    // adding the spatial filters support
    caps.addType(BBOX.class);
    caps.addType(Contains.class);
    caps.addType(Crosses.class);
    caps.addType(Disjoint.class);
    caps.addType(Equals.class);
    caps.addType(Intersects.class);
    caps.addType(Overlaps.class);
    caps.addType(Touches.class);
    caps.addType(Within.class);
    caps.addType(DWithin.class);
    caps.addType(Beyond.class);

    // temporal filters
    caps.addType(After.class);
    caps.addType(Before.class);
    caps.addType(Begins.class);
    caps.addType(BegunBy.class);
    caps.addType(During.class);
    caps.addType(TOverlaps.class);
    caps.addType(Ends.class);
    caps.addType(EndedBy.class);
    caps.addType(TEquals.class);

    if (isFunctionEncodingEnabled()) {
      // add support for string functions
      caps.addType(FilterFunction_strConcat.class);
      caps.addType(FilterFunction_strEndsWith.class);
      caps.addType(FilterFunction_strStartsWith.class);
      caps.addType(FilterFunction_strEqualsIgnoreCase.class);
      caps.addType(FilterFunction_strIndexOf.class);
      caps.addType(FilterFunction_strLength.class);
      caps.addType(FilterFunction_strToLowerCase.class);
      caps.addType(FilterFunction_strToUpperCase.class);
      caps.addType(FilterFunction_strReplace.class);
      caps.addType(FilterFunction_strSubstring.class);
      caps.addType(FilterFunction_strSubstringStart.class);
      caps.addType(FilterFunction_strTrim.class);
      caps.addType(FilterFunction_strTrim2.class);

      // add support for math functions
      caps.addType(FilterFunction_abs.class);
      caps.addType(FilterFunction_abs_2.class);
      caps.addType(FilterFunction_abs_3.class);
      caps.addType(FilterFunction_abs_4.class);
      caps.addType(FilterFunction_ceil.class);
      caps.addType(FilterFunction_floor.class);
    }

    return caps;
  }