/** Make a new Query object that is exactly like the old. Too bad Query isn't Cloneable. */
  protected com.google.appengine.api.datastore.Query cloneRawQuery(
      com.google.appengine.api.datastore.Query orig) {
    com.google.appengine.api.datastore.Query copy =
        new com.google.appengine.api.datastore.Query(orig.getKind(), orig.getAncestor());

    for (FilterPredicate filter : orig.getFilterPredicates())
      copy.addFilter(filter.getPropertyName(), filter.getOperator(), filter.getValue());

    for (SortPredicate sort : orig.getSortPredicates())
      copy.addSort(sort.getPropertyName(), sort.getDirection());

    // This should be impossible but who knows what might happen in the future
    if (orig.isKeysOnly()) copy.setKeysOnly();

    return copy;
  }
  /* (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  public String toString() {
    StringBuilder bld = new StringBuilder(this.getClass().getName());
    bld.append("{kind=");
    bld.append(this.actual.getKind());

    bld.append(",ancestor=");
    if (this.actual.getAncestor() != null)
      bld.append(KeyFactory.keyToString(this.actual.getAncestor()));

    // We need to sort filters to make a stable string value
    FilterPredicate[] filters =
        this.actual
            .getFilterPredicates()
            .toArray(new FilterPredicate[this.actual.getFilterPredicates().size()]);
    Arrays.sort(
        filters,
        new Comparator<FilterPredicate>() {
          @Override
          public int compare(FilterPredicate o1, FilterPredicate o2) {
            int result = o1.getPropertyName().compareTo(o2.getPropertyName());
            if (result != 0) return result;

            result = o1.getOperator().compareTo(o2.getOperator());
            if (result != 0) return result;

            if (o1.getValue() == null) return o2.getValue() == null ? 0 : -1;
            else if (o2.getValue() == null) return 1;
            else
              return o1.getValue()
                  .toString()
                  .compareTo(
                      o2.getValue().toString()); // not perfect, but probably as good as we can do
          }
        });
    for (FilterPredicate filter : filters) {
      bld.append(",filter=");
      bld.append(filter.getPropertyName());
      bld.append(filter.getOperator().name());
      bld.append(filter.getValue());
    }

    // We need to sort sorts to make a stable string value
    SortPredicate[] sorts =
        this.actual
            .getSortPredicates()
            .toArray(new SortPredicate[this.actual.getSortPredicates().size()]);
    Arrays.sort(
        sorts,
        new Comparator<SortPredicate>() {
          @Override
          public int compare(SortPredicate o1, SortPredicate o2) {
            int result = o1.getPropertyName().compareTo(o2.getPropertyName());
            if (result != 0) return result;

            // Actually, it should be impossible to have the same prop with multiple directions
            return o1.getDirection().compareTo(o2.getDirection());
          }
        });
    for (SortPredicate sort : this.actual.getSortPredicates()) {
      bld.append(",sort=");
      bld.append(sort.getPropertyName());
      bld.append(sort.getDirection().name());
    }

    if (this.limit > 0) bld.append(",limit=").append(this.limit);

    if (this.offset > 0) bld.append(",offset=").append(this.offset);

    if (this.cursor != null) bld.append(",cursor=").append(this.cursor.toWebSafeString());

    bld.append('}');

    return bld.toString();
  }