private boolean match0(Map properties) {
    switch (op) {
      case AND:
        {
          FilterImpl[] filters = (FilterImpl[]) value;
          for (int i = 0, size = filters.length; i < size; i++) {
            if (!filters[i].match0(properties)) {
              return false;
            }
          }

          return true;
        }

      case OR:
        {
          FilterImpl[] filters = (FilterImpl[]) value;
          for (int i = 0, size = filters.length; i < size; i++) {
            if (filters[i].match0(properties)) {
              return true;
            }
          }

          return false;
        }

      case NOT:
        {
          FilterImpl filter = (FilterImpl) value;

          return !filter.match0(properties);
        }

      case SUBSTRING:
      case EQUAL:
      case GREATER:
      case LESS:
      case APPROX:
      case SUBSET:
      case SUPERSET:
        {
          Object prop = (properties == null) ? null : properties.get(attr);

          return compare(op, prop, value);
        }

      case PRESENT:
        {
          Object prop = (properties == null) ? null : properties.get(attr);

          return prop != null;
        }
    }

    return false;
  }
  /**
   * Returns this <code>Filter</code>'s normalized filter string.
   *
   * <p>The filter string is normalized by removing whitespace which does not affect the meaning of
   * the filter.
   *
   * @return This <code>Filter</code>'s filter string.
   */
  private String normalize() {
    StringBuffer sb = new StringBuffer();
    sb.append('(');

    switch (op) {
      case AND:
        {
          sb.append('&');

          FilterImpl[] filters = (FilterImpl[]) value;
          for (int i = 0, size = filters.length; i < size; i++) {
            sb.append(filters[i].normalize());
          }

          break;
        }

      case OR:
        {
          sb.append('|');

          FilterImpl[] filters = (FilterImpl[]) value;
          for (int i = 0, size = filters.length; i < size; i++) {
            sb.append(filters[i].normalize());
          }

          break;
        }

      case NOT:
        {
          sb.append('!');
          FilterImpl filter = (FilterImpl) value;
          sb.append(filter.normalize());

          break;
        }

      case SUBSTRING:
        {
          sb.append(attr);
          sb.append('=');

          String[] substrings = (String[]) value;

          for (int i = 0, size = substrings.length; i < size; i++) {
            String substr = substrings[i];

            if (substr == null) /* * */ {
              sb.append('*');
            } else /* xxx */ {
              sb.append(encodeValue(substr));
            }
          }

          break;
        }
      case EQUAL:
        {
          sb.append(attr);
          sb.append('=');
          sb.append(encodeValue((String) value));

          break;
        }
      case GREATER:
        {
          sb.append(attr);
          sb.append(">=");
          sb.append(encodeValue((String) value));

          break;
        }
      case LESS:
        {
          sb.append(attr);
          sb.append("<=");
          sb.append(encodeValue((String) value));

          break;
        }
      case APPROX:
        {
          sb.append(attr);
          sb.append("~=");
          sb.append(encodeValue(approxString((String) value)));

          break;
        }
      case PRESENT:
        {
          sb.append(attr);
          sb.append("=*");

          break;
        }
      case SUBSET:
        {
          sb.append(attr);
          sb.append("<*");
          sb.append(encodeValue(approxString((String) value)));

          break;
        }
      case SUPERSET:
        {
          sb.append(attr);
          sb.append("*>");
          sb.append(encodeValue(approxString((String) value)));

          break;
        }
    }

    sb.append(')');

    return sb.toString();
  }