com.parse.ParseQuery.java Source code

Java tutorial

Introduction

Here is the source code for com.parse.ParseQuery.java

Source

/*
 * Copyright (c) 2015-present, Parse, LLC.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
package com.parse;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;

import bolts.Continuation;
import bolts.Task;

/**
 * The {@code ParseQuery} class defines a query that is used to fetch {@link ParseObject}s. The most
 * common use case is finding all objects that match a query through the {@link #findInBackground()}
 * method, using a {@link FindCallback}. For example, this sample code fetches all objects of class
 * {@code "MyClass"}. It calls a different function depending on whether the fetch succeeded or not.
 * <p/>
 * <pre>
 * ParseQuery&lt;ParseObject&gt; query = ParseQuery.getQuery("MyClass");
 * query.findInBackground(new FindCallback&lt;ParseObject&gt;() {
 *     public void done(List&lt;ParseObject&gt; objects, ParseException e) {
 *         if (e == null) {
 *             objectsWereRetrievedSuccessfully(objects);
 *         } else {
 *             objectRetrievalFailed();
 *         }
 *     }
 * }
 * </pre>
 * <p/>
 * A {@code ParseQuery} can also be used to retrieve a single object whose id is known, through the
 * {@link #getInBackground(String)} method, using a {@link GetCallback}. For example, this
 * sample code fetches an object of class {@code "MyClass"} and id {@code myId}. It calls
 * a different function depending on whether the fetch succeeded or not.
 * <p/>
 * <pre>
 * ParseQuery&lt;ParseObject&gt; query = ParseQuery.getQuery("MyClass");
 * query.getInBackground(myId, new GetCallback&lt;ParseObject&gt;() {
 *     public void done(ParseObject object, ParseException e) {
 *         if (e == null) {
 *             objectWasRetrievedSuccessfully(object);
 *         } else {
 *             objectRetrievalFailed();
 *         }
 *     }
 * }
 * </pre>
 * <p/>
 * A {@code ParseQuery} can also be used to count the number of objects that match the query without
 * retrieving all of those objects. For example, this sample code counts the number of objects of
 * the class {@code "MyClass"}.
 * <p/>
 * <pre>
 * ParseQuery&lt;ParseObject&gt; query = ParseQuery.getQuery("MyClass");
 * query.countInBackground(new CountCallback() {
 *     public void done(int count, ParseException e) {
 *         if (e == null) {
 *             objectsWereCounted(count);
 *         } else {
 *             objectCountFailed();
 *         }
 *     }
 * }
 * </pre>
 * <p/>
 * Using the callback methods is usually preferred because the network operation will not block the
 * calling thread. However, in some cases it may be easier to use the {@link #find()},
 * {@link #get(String)} or {@link #count()} calls, which do block the calling thread. For example,
 * if your application has already spawned a background task to perform work, that background task
 * could use the blocking calls and avoid the code complexity of callbacks.
 */
public class ParseQuery<T extends ParseObject> {

    private static ParseQueryController getQueryController() {
        return ParseCorePlugins.getInstance().getQueryController();
    }

    /**
     * Constraints for a {@code ParseQuery}'s where clause. A map of field names to constraints. The
     * values can either be actual values to compare with for equality, or instances of
     * {@link KeyConstraints}.
     */
    @SuppressWarnings("serial")
    /* package */ static class QueryConstraints extends HashMap<String, Object> {

        public QueryConstraints() {
            super();
        }

        public QueryConstraints(Map<? extends String, ?> map) {
            super(map);
        }
    }

    /**
     * Constraints for a particular field in a query. If this is used, it's a may where the keys are
     * special operators, such as $greaterThan or $nin. The values are the actual values to compare
     * against.
     */
    @SuppressWarnings("serial")
    /* package */ static class KeyConstraints extends HashMap<String, Object> {
    }

    /**
     * Constraint for a $relatedTo query.
     */
    /* package */ static class RelationConstraint {
        private String key;
        private ParseObject object;

        public RelationConstraint(String key, ParseObject object) {
            if (key == null || object == null) {
                throw new IllegalArgumentException("Arguments must not be null.");
            }
            this.key = key;
            this.object = object;
        }

        public String getKey() {
            return key;
        }

        public ParseObject getObject() {
            return object;
        }

        public ParseRelation<ParseObject> getRelation() {
            return object.getRelation(key);
        }

        /**
         * Encodes the constraint in a format appropriate for including in the query.
         */
        public JSONObject encode(ParseEncoder objectEncoder) {
            JSONObject json = new JSONObject();
            try {
                json.put("key", key);
                json.put("object", objectEncoder.encodeRelatedObject(object));
            } catch (JSONException e) {
                // This can never happen.
                throw new RuntimeException(e);
            }
            return json;
        }
    }

    /**
     * Constructs a query that is the {@code or} of the given queries.
     *
     * @param queries
     *          The list of {@code ParseQuery}s to 'or' together
     * @return A {@code ParseQuery} that is the 'or' of the passed in queries
     */
    public static <T extends ParseObject> ParseQuery<T> or(List<ParseQuery<T>> queries) {
        if (queries.isEmpty()) {
            throw new IllegalArgumentException("Can't take an or of an empty list of queries");
        }

        List<State.Builder<T>> builders = new ArrayList<>();
        for (ParseQuery<T> query : queries) {
            builders.add(query.getBuilder());
        }
        return new ParseQuery<>(State.Builder.or(builders));
    }

    /**
     * Creates a new query for the given {@link ParseObject} subclass type. A default query with no
     * further parameters will retrieve all {@link ParseObject}s of the provided class.
     *
     * @param subclass
     *          The {@link ParseObject} subclass type to retrieve.
     * @return A new {@code ParseQuery}.
     */
    public static <T extends ParseObject> ParseQuery<T> getQuery(Class<T> subclass) {
        return new ParseQuery<>(subclass);
    }

    /**
     * Creates a new query for the given class name. A default query with no further parameters will
     * retrieve all {@link ParseObject}s of the provided class name.
     *
     * @param className
     *          The name of the class to retrieve {@link ParseObject}s for.
     * @return A new {@code ParseQuery}.
     */
    public static <T extends ParseObject> ParseQuery<T> getQuery(String className) {
        return new ParseQuery<>(className);
    }

    /**
     * Constructs a query for {@link ParseUser}s.
     *
     * @deprecated Please use {@link ParseUser#getQuery()} instead.
     */
    @Deprecated
    public static ParseQuery<ParseUser> getUserQuery() {
        return ParseUser.getQuery();
    }

    /**
     * {@code CachePolicy} specifies different caching policies that could be used with
     * {@link ParseQuery}.
     * <p/>
     * This lets you show data when the user's device is offline, or when the app has just started and
     * network requests have not yet had time to complete. Parse takes care of automatically flushing
     * the cache when it takes up too much space.
     * <p/>
     * <strong>Note:</strong> Cache policy can only be set when Local Datastore is not enabled.
     *
     * @see com.parse.ParseQuery
     */
    public enum CachePolicy {
        /**
         * The query does not load from the cache or save results to the cache.
         * <p/>
         * This is the default cache policy.
         */
        IGNORE_CACHE,

        /**
         * The query only loads from the cache, ignoring the network.
         * <p/>
         * If there are no cached results, this causes a {@link ParseException#CACHE_MISS}.
         */
        CACHE_ONLY,

        /**
         * The query does not load from the cache, but it will save results to the cache.
         */
        NETWORK_ONLY,

        /**
         * The query first tries to load from the cache, but if that fails, it loads results from the
         * network.
         * <p/>
         * If there are no cached results, this causes a {@link ParseException#CACHE_MISS}.
         */
        CACHE_ELSE_NETWORK,

        /**
         * The query first tries to load from the network, but if that fails, it loads results from the
         * cache.
         * <p/>
         * If there are no cached results, this causes a {@link ParseException#CACHE_MISS}.
         */
        NETWORK_ELSE_CACHE,

        /**
         * The query first loads from the cache, then loads from the network.
         * The callback will be called twice - first with the cached results, then with the network
         * results. Since it returns two results at different times, this cache policy cannot be used
         * with synchronous or task methods.
         */
        // TODO(grantland): Remove this and come up with a different solution, since it breaks our
        // "callbacks get called at most once" paradigm. (v2)
        CACHE_THEN_NETWORK
    }

    private static void throwIfLDSEnabled() {
        throwIfLDSEnabled(false);
    }

    private static void throwIfLDSDisabled() {
        throwIfLDSEnabled(true);
    }

    private static void throwIfLDSEnabled(boolean enabled) {
        boolean ldsEnabled = Parse.isLocalDatastoreEnabled();
        if (enabled && !ldsEnabled) {
            throw new IllegalStateException(
                    "Method requires Local Datastore. " + "Please refer to `Parse#enableLocalDatastore(Context)`.");
        }
        if (!enabled && ldsEnabled) {
            throw new IllegalStateException("Unsupported method when Local Datastore is enabled.");
        }
    }

    /* package */ static class State<T extends ParseObject> {

        /* package */ static class Builder<T extends ParseObject> {

            // TODO(grantland): Convert mutable parameter to immutable t6941155
            public static <T extends ParseObject> Builder<T> or(List<Builder<T>> builders) {
                if (builders.isEmpty()) {
                    throw new IllegalArgumentException("Can't take an or of an empty list of queries");
                }

                String className = null;
                List<QueryConstraints> constraints = new ArrayList<>();
                for (Builder<T> builder : builders) {
                    if (className != null && !builder.className.equals(className)) {
                        throw new IllegalArgumentException(
                                "All of the queries in an or query must be on the same class ");
                    }
                    if (builder.limit >= 0) {
                        throw new IllegalArgumentException("Cannot have limits in sub queries of an 'OR' query");
                    }
                    if (builder.skip > 0) {
                        throw new IllegalArgumentException("Cannot have skips in sub queries of an 'OR' query");
                    }
                    if (!builder.order.isEmpty()) {
                        throw new IllegalArgumentException("Cannot have an order in sub queries of an 'OR' query");
                    }
                    if (!builder.includes.isEmpty()) {
                        throw new IllegalArgumentException(
                                "Cannot have an include in sub queries of an 'OR' query");
                    }
                    if (builder.selectedKeys != null) {
                        throw new IllegalArgumentException(
                                "Cannot have an selectKeys in sub queries of an 'OR' query");
                    }

                    className = builder.className;
                    constraints.add(builder.where);
                }

                return new State.Builder<T>(className).whereSatifiesAnyOf(constraints);
            }

            private final String className;
            private final QueryConstraints where = new QueryConstraints();
            private final Set<String> includes = new HashSet<>();
            // This is nullable since we allow unset selectedKeys as well as no selectedKeys
            private Set<String> selectedKeys;
            private int limit = -1; // negative limits mean, do not send a limit
            private int skip = 0; // negative skip means do not send a skip
            private List<String> order = new ArrayList<>();
            private final Map<String, Object> extraOptions = new HashMap<>();

            // TODO(grantland): Move out of State
            private boolean trace;

            // Query Caching
            private CachePolicy cachePolicy = CachePolicy.IGNORE_CACHE;
            private long maxCacheAge = Long.MAX_VALUE; // 292 million years should be enough not to cause issues

            // LDS
            private boolean isFromLocalDatastore = false;
            private String pinName;
            private boolean ignoreACLs;

            public Builder(String className) {
                this.className = className;
            }

            public Builder(Class<T> subclass) {
                this(ParseObject.getClassName(subclass));
            }

            public Builder(State state) {
                className = state.className();
                where.putAll(state.constraints());
                includes.addAll(state.includes());
                selectedKeys = state.selectedKeys() != null ? new HashSet(state.selectedKeys()) : null;
                limit = state.limit();
                skip = state.skip();
                order.addAll(state.order());
                extraOptions.putAll(state.extraOptions());
                trace = state.isTracingEnabled();
                cachePolicy = state.cachePolicy();
                maxCacheAge = state.maxCacheAge();
                isFromLocalDatastore = state.isFromLocalDatastore();
                pinName = state.pinName();
                ignoreACLs = state.ignoreACLs();
            }

            public Builder(Builder<T> builder) {
                className = builder.className;
                where.putAll(builder.where);
                includes.addAll(builder.includes);
                selectedKeys = builder.selectedKeys != null ? new HashSet(builder.selectedKeys) : null;
                limit = builder.limit;
                skip = builder.skip;
                order.addAll(builder.order);
                extraOptions.putAll(builder.extraOptions);
                trace = builder.trace;
                cachePolicy = builder.cachePolicy;
                maxCacheAge = builder.maxCacheAge;
                isFromLocalDatastore = builder.isFromLocalDatastore;
                pinName = builder.pinName;
                ignoreACLs = builder.ignoreACLs;
            }

            public String getClassName() {
                return className;
            }

            //region Where Constraints

            /**
             * Add a constraint to the query that requires a particular key's value to be equal to the
             * provided value.
             *
             * @param key
             *          The key to check.
             * @param value
             *          The value that the {@link ParseObject} must contain.
             * @return this, so you can chain this call.
             */
            // TODO(grantland): Add typing
            public Builder<T> whereEqualTo(String key, Object value) {
                where.put(key, value);
                return this;
            }

            // TODO(grantland): Convert mutable parameter to immutable t6941155
            public Builder<T> whereDoesNotMatchKeyInQuery(String key, String keyInQuery, Builder<?> builder) {
                Map<String, Object> condition = new HashMap<>();
                condition.put("key", keyInQuery);
                condition.put("query", builder);
                return addConditionInternal(key, "$dontSelect", Collections.unmodifiableMap(condition));
            }

            // TODO(grantland): Convert mutable parameter to immutable t6941155
            public Builder<T> whereMatchesKeyInQuery(String key, String keyInQuery, Builder<?> builder) {
                Map<String, Object> condition = new HashMap<>();
                condition.put("key", keyInQuery);
                condition.put("query", builder);
                return addConditionInternal(key, "$select", Collections.unmodifiableMap(new HashMap<>(condition)));
            }

            // TODO(grantland): Convert mutable parameter to immutable t6941155
            public Builder<T> whereDoesNotMatchQuery(String key, Builder<?> builder) {
                return addConditionInternal(key, "$notInQuery", builder);
            }

            // TODO(grantland): Convert mutable parameter to immutable t6941155
            public Builder<T> whereMatchesQuery(String key, Builder<?> builder) {
                return addConditionInternal(key, "$inQuery", builder);
            }

            public Builder<T> whereNear(String key, ParseGeoPoint point) {
                return addCondition(key, "$nearSphere", point);
            }

            public Builder<T> maxDistance(String key, double maxDistance) {
                return addCondition(key, "$maxDistance", maxDistance);
            }

            public Builder<T> whereWithin(String key, ParseGeoPoint southwest, ParseGeoPoint northeast) {
                List<Object> array = new ArrayList<>();
                array.add(southwest);
                array.add(northeast);
                Map<String, List<Object>> dictionary = new HashMap<>();
                dictionary.put("$box", array);
                return addCondition(key, "$within", dictionary);
            }

            public Builder<T> addCondition(String key, String condition, Collection<? extends Object> value) {
                return addConditionInternal(key, condition, Collections.unmodifiableCollection(value));
            }

            // TODO(grantland): Add typing
            public Builder<T> addCondition(String key, String condition, Object value) {
                return addConditionInternal(key, condition, value);
            }

            // Helper for condition queries.
            private Builder<T> addConditionInternal(String key, String condition, Object value) {
                KeyConstraints whereValue = null;

                // Check if we already have some of a condition
                if (where.containsKey(key)) {
                    Object existingValue = where.get(key);
                    if (existingValue instanceof KeyConstraints) {
                        whereValue = (KeyConstraints) existingValue;
                    }
                }
                if (whereValue == null) {
                    whereValue = new KeyConstraints();
                }

                whereValue.put(condition, value);

                where.put(key, whereValue);
                return this;
            }

            // Used by ParseRelation
            /* package */ Builder<T> whereRelatedTo(ParseObject parent, String key) {
                where.put("$relatedTo", new RelationConstraint(key, parent));
                return this;
            }

            /**
             * Add a constraint that a require matches any one of an array of {@code ParseQuery}s.
             * <p/>
             * The {@code ParseQuery}s passed cannot have any orders, skips, or limits set.
             *
             * @param constraints
             *          The array of queries to or
             *
             * @return this, so you can chain this call.
             */
            private Builder<T> whereSatifiesAnyOf(List<QueryConstraints> constraints) {
                where.put("$or", constraints);
                return this;
            }

            // Used by getInBackground
            /* package */ Builder<T> whereObjectIdEquals(String objectId) {
                where.clear();
                where.put("objectId", objectId);
                return this;
            }

            //endregion

            //region Order

            private Builder<T> setOrder(String key) {
                order.clear();
                order.add(key);
                return this;
            }

            private Builder<T> addOrder(String key) {
                order.add(key);
                return this;
            }

            /**
             * Sorts the results in ascending order by the given key.
             *
             * @param key
             *          The key to order by.
             * @return this, so you can chain this call.
             */
            public Builder<T> orderByAscending(String key) {
                return setOrder(key);
            }

            /**
             * Also sorts the results in ascending order by the given key.
             * <p/>
             * The previous sort keys have precedence over this key.
             *
             * @param key
             *          The key to order by
             * @return this, so you can chain this call.
             */
            public Builder<T> addAscendingOrder(String key) {
                return addOrder(key);
            }

            /**
             * Sorts the results in descending order by the given key.
             *
             * @param key
             *          The key to order by.
             * @return this, so you can chain this call.
             */
            public Builder<T> orderByDescending(String key) {
                return setOrder(String.format("-%s", key));
            }

            /**
             * Also sorts the results in descending order by the given key.
             * <p/>
             * The previous sort keys have precedence over this key.
             *
             * @param key
             *          The key to order by
             * @return this, so you can chain this call.
             */
            public Builder<T> addDescendingOrder(String key) {
                return addOrder(String.format("-%s", key));
            }

            //endregion

            //region Includes

            /**
             * Include nested {@link ParseObject}s for the provided key.
             * <p/>
             * You can use dot notation to specify which fields in the included object that are also fetched.
             *
             * @param key
             *          The key that should be included.
             * @return this, so you can chain this call.
             */
            public Builder<T> include(String key) {
                includes.add(key);
                return this;
            }

            //endregion

            /**
             * Restrict the fields of returned {@link ParseObject}s to only include the provided keys.
             * <p/>
             * If this is called multiple times, then all of the keys specified in each of the calls will be
             * included.
             * <p/>
             * <strong>Note:</strong> This option will be ignored when querying from the local datastore. This
             * is done since all the keys will be in memory anyway and there will be no performance gain from
             * removing them.
             *
             * @param keys
             *          The set of keys to include in the result.
             * @return this, so you can chain this call.
             */
            public Builder<T> selectKeys(Collection<String> keys) {
                if (selectedKeys == null) {
                    selectedKeys = new HashSet<>();
                }
                selectedKeys.addAll(keys);
                return this;
            }

            public int getLimit() {
                return limit;
            }

            public Builder<T> setLimit(int limit) {
                this.limit = limit;
                return this;
            }

            public int getSkip() {
                return skip;
            }

            public Builder<T> setSkip(int skip) {
                this.skip = skip;
                return this;
            }

            // Used by ParseRelation
            /* package */ Builder<T> redirectClassNameForKey(String key) {
                extraOptions.put("redirectClassNameForKey", key);
                return this;
            }

            public Builder<T> setTracingEnabled(boolean trace) {
                this.trace = trace;
                return this;
            }

            public CachePolicy getCachePolicy() {
                throwIfLDSEnabled();
                return cachePolicy;
            }

            public Builder<T> setCachePolicy(CachePolicy cachePolicy) {
                throwIfLDSEnabled();
                this.cachePolicy = cachePolicy;
                return this;
            }

            public long getMaxCacheAge() {
                throwIfLDSEnabled();
                return maxCacheAge;
            }

            public Builder<T> setMaxCacheAge(long maxCacheAge) {
                throwIfLDSEnabled();
                this.maxCacheAge = maxCacheAge;
                return this;
            }

            public boolean isFromNetwork() {
                throwIfLDSDisabled();
                return !isFromLocalDatastore;
            }

            public Builder<T> fromNetwork() {
                throwIfLDSDisabled();
                isFromLocalDatastore = false;
                pinName = null;
                return this;
            }

            public Builder<T> fromLocalDatastore() {
                return fromPin(null);
            }

            public boolean isFromLocalDatstore() {
                return isFromLocalDatastore;
            }

            public Builder<T> fromPin() {
                return fromPin(ParseObject.DEFAULT_PIN);
            }

            public Builder<T> fromPin(String pinName) {
                throwIfLDSDisabled();
                isFromLocalDatastore = true;
                this.pinName = pinName;
                return this;
            }

            public Builder<T> ignoreACLs() {
                throwIfLDSDisabled();
                ignoreACLs = true;
                return this;
            }

            public State<T> build() {
                if (!isFromLocalDatastore && ignoreACLs) {
                    throw new IllegalStateException("`ignoreACLs` cannot be combined with network queries");
                }
                return new State<>(this);
            }
        }

        private final String className;
        private final QueryConstraints where;
        private final Set<String> include;
        private final Set<String> selectedKeys;
        private final int limit;
        private final int skip;
        private final List<String> order;
        private final Map<String, Object> extraOptions;

        // TODO(grantland): Move out of State
        private final boolean trace;

        // Query Caching
        private final CachePolicy cachePolicy;
        private final long maxCacheAge;

        // LDS
        private final boolean isFromLocalDatastore;
        private final String pinName;
        private final boolean ignoreACLs;

        private State(Builder<T> builder) {
            className = builder.className;
            where = new QueryConstraints(builder.where);
            include = Collections.unmodifiableSet(new HashSet<>(builder.includes));
            selectedKeys = builder.selectedKeys != null
                    ? Collections.unmodifiableSet(new HashSet<>(builder.selectedKeys))
                    : null;
            limit = builder.limit;
            skip = builder.skip;
            order = Collections.unmodifiableList(new ArrayList<>(builder.order));
            extraOptions = Collections.unmodifiableMap(new HashMap<>(builder.extraOptions));

            trace = builder.trace;

            cachePolicy = builder.cachePolicy;
            maxCacheAge = builder.maxCacheAge;

            isFromLocalDatastore = builder.isFromLocalDatastore;
            pinName = builder.pinName;
            ignoreACLs = builder.ignoreACLs;
        }

        public String className() {
            return className;
        }

        public QueryConstraints constraints() {
            return where;
        }

        public Set<String> includes() {
            return include;
        }

        public Set<String> selectedKeys() {
            return selectedKeys;
        }

        public int limit() {
            return limit;
        }

        public int skip() {
            return skip;
        }

        public List<String> order() {
            return order;
        }

        public Map<String, Object> extraOptions() {
            return extraOptions;
        }

        public boolean isTracingEnabled() {
            return trace;
        }

        public CachePolicy cachePolicy() {
            return cachePolicy;
        }

        public long maxCacheAge() {
            return maxCacheAge;
        }

        public boolean isFromLocalDatastore() {
            return isFromLocalDatastore;
        }

        public String pinName() {
            return pinName;
        }

        public boolean ignoreACLs() {
            return ignoreACLs;
        }

        // Returns the query in JSON REST format for subqueries
        /* package */ JSONObject toJSON(ParseEncoder encoder) {
            JSONObject params = new JSONObject();

            try {
                params.put("className", className);
                params.put("where", encoder.encode(where));

                if (limit >= 0) {
                    params.put("limit", limit);
                }
                if (skip > 0) {
                    params.put("skip", skip);
                }
                if (!order.isEmpty()) {
                    params.put("order", ParseTextUtils.join(",", order));
                }
                if (!include.isEmpty()) {
                    params.put("include", ParseTextUtils.join(",", include));
                }
                if (selectedKeys != null) {
                    params.put("fields", ParseTextUtils.join(",", selectedKeys));
                }
                if (trace) {
                    params.put("trace", 1);
                }

                for (String key : extraOptions.keySet()) {
                    params.put(key, encoder.encode(extraOptions.get(key)));
                }
            } catch (JSONException e) {
                throw new RuntimeException(e);
            }

            return params;
        }

        @Override
        public String toString() {
            return String.format(Locale.US,
                    "%s[className=%s, where=%s, include=%s, "
                            + "selectedKeys=%s, limit=%s, skip=%s, order=%s, extraOptions=%s, "
                            + "cachePolicy=%s, maxCacheAge=%s, " + "trace=%s]",
                    getClass().getName(), className, where, include, selectedKeys, limit, skip, order, extraOptions,
                    cachePolicy, maxCacheAge, trace);
        }
    }

    private final State.Builder<T> builder;
    private ParseUser user;

    private final Object lock = new Object();
    private boolean isRunning = false;
    private Task<Void>.TaskCompletionSource cts;

    /**
     * Constructs a query for a {@link ParseObject} subclass type. A default query with no further
     * parameters will retrieve all {@link ParseObject}s of the provided class.
     *
     * @param subclass
     *          The {@link ParseObject} subclass type to retrieve.
     */
    public ParseQuery(Class<T> subclass) {
        this(ParseObject.getClassName(subclass));
    }

    /**
     * Constructs a query. A default query with no further parameters will retrieve all
     * {@link ParseObject}s of the provided class.
     *
     * @param theClassName
     *          The name of the class to retrieve {@link ParseObject}s for.
     */
    public ParseQuery(String theClassName) {
        this(new State.Builder<T>(theClassName));
    }

    /**
     * Constructs a copy of {@code query};
     *
     * @param query
     *          The query to copy.
     */
    public ParseQuery(ParseQuery<T> query) {
        this(new State.Builder<>(query.getBuilder()));
        user = query.user;
    }

    /* package */ ParseQuery(State.Builder<T> builder) {
        this.builder = builder;
    }

    /* package */ State.Builder<T> getBuilder() {
        return builder;
    }

    /**
     * Sets the user to be used for this query.
     *
     *
     * The query will use the user if set, otherwise it will read the current user.
     */
    /* package for tests */ ParseQuery<T> setUser(ParseUser user) {
        this.user = user;
        return this;
    }

    /**
     * Returns the user used for the query. This user is used to filter results based on ACLs on the
     * target objects. Can be {@code null} if the there is no current user or {@link #ignoreACLs} is
     * enabled.
     */
    /* package for tests */ Task<ParseUser> getUserAsync(State<T> state) {
        if (state.ignoreACLs()) {
            return Task.forResult(null);
        }
        if (user != null) {
            return Task.forResult(user);
        }
        return ParseUser.getCurrentUserAsync();
    }

    private void checkIfRunning() {
        checkIfRunning(false);
    }

    private void checkIfRunning(boolean grabLock) {
        synchronized (lock) {
            if (isRunning) {
                throw new RuntimeException(
                        "This query has an outstanding network connection. You have to wait until it's done.");
            } else if (grabLock) {
                isRunning = true;
                cts = Task.create();
            }
        }
    }

    /**
     * Cancels the current network request (if one is running).
     */
    //TODO (grantland): Deprecate and replace with CancellationTokens
    public void cancel() {
        synchronized (lock) {
            if (cts != null) {
                cts.trySetCancelled();
                cts = null;
            }
            isRunning = false;
        }
    }

    /**
     * Retrieves a list of {@link ParseObject}s that satisfy this query.
     * <p/>
     * @return A list of all {@link ParseObject}s obeying the conditions set in this query.
     * @throws ParseException
     *           Throws a {@link ParseException} if no object is found.
     *
     * @see ParseException#OBJECT_NOT_FOUND
     */
    public List<T> find() throws ParseException {
        return ParseTaskUtils.wait(findInBackground());
    }

    /**
     * Retrieves at most one {@link ParseObject} that satisfies this query.
     * <p/>
     * <strong>Note:</strong>This mutates the {@code ParseQuery}.
     *
     * @return A {@link ParseObject} obeying the conditions set in this query.
     * @throws ParseException
     *           Throws a {@link ParseException} if no object is found.
     *
     * @see ParseException#OBJECT_NOT_FOUND
     */
    public T getFirst() throws ParseException {
        return ParseTaskUtils.wait(getFirstInBackground());
    }

    /**
     * Change the caching policy of this query.
     * <p/>
     * Unsupported when Local Datastore is enabled.
     *
     * @return this, so you can chain this call.
     *
     * @see ParseQuery#fromLocalDatastore()
     * @see ParseQuery#fromPin()
     * @see ParseQuery#fromPin(String)
     */
    public ParseQuery<T> setCachePolicy(CachePolicy newCachePolicy) {
        checkIfRunning();

        builder.setCachePolicy(newCachePolicy);
        return this;
    }

    /**
     * @return the caching policy.
     */
    public CachePolicy getCachePolicy() {
        return builder.getCachePolicy();
    }

    /**
     * Change the source of this query to the server.
     * <p/>
     * Requires Local Datastore to be enabled.
     *
     * @return this, so you can chain this call.
     *
     * @see ParseQuery#setCachePolicy(CachePolicy)
     */
    /* package */ ParseQuery<T> fromNetwork() {
        checkIfRunning();

        builder.fromNetwork();
        return this;
    }

    /* package */ boolean isFromNetwork() {
        return builder.isFromNetwork();
    }

    /**
     * Change the source of this query to all pinned objects.
     * <p/>
     * Requires Local Datastore to be enabled.
     *
     * @return this, so you can chain this call.
     *
     * @see ParseQuery#setCachePolicy(CachePolicy)
     */
    public ParseQuery<T> fromLocalDatastore() {
        builder.fromLocalDatastore();
        return this;
    }

    /**
     * Change the source of this query to the default group of pinned objects.
     * <p/>
     * Requires Local Datastore to be enabled.
     *
     * @return this, so you can chain this call.
     *
     * @see ParseObject#DEFAULT_PIN
     * @see ParseQuery#setCachePolicy(CachePolicy)
     */
    public ParseQuery<T> fromPin() {
        checkIfRunning();
        builder.fromPin();
        return this;
    }

    /**
     * Change the source of this query to a specific group of pinned objects.
     * <p/>
     * Requires Local Datastore to be enabled.
     *
     * @param name
     *          the pinned group
     * @return this, so you can chain this call.
     *
     * @see ParseQuery#setCachePolicy(CachePolicy)
     */
    public ParseQuery<T> fromPin(String name) {
        checkIfRunning();
        builder.fromPin(name);
        return this;
    }

    /**
     * Ignore ACLs when querying from the Local Datastore.
     * <p/>
     * This is particularly useful when querying for objects with Role based ACLs set on them.
     *
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> ignoreACLs() {
        checkIfRunning();

        builder.ignoreACLs();
        return this;
    }

    /**
     * Sets the maximum age of cached data that will be considered in this query.
     *
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> setMaxCacheAge(long maxAgeInMilliseconds) {
        checkIfRunning();

        builder.setMaxCacheAge(maxAgeInMilliseconds);
        return this;
    }

    /**
     * Gets the maximum age of cached data that will be considered in this query. The returned value
     * is in milliseconds
     */
    public long getMaxCacheAge() {
        return builder.getMaxCacheAge();
    }

    /**
     * Wraps a callable with checking that only one of these is running.
     */
    private <TResult> Task<TResult> doWithRunningCheck(Callable<Task<TResult>> runnable) {
        checkIfRunning(true);
        Task<TResult> task;
        try {
            task = runnable.call();
        } catch (Exception e) {
            task = Task.forError(e);
        }
        return task.continueWithTask(new Continuation<TResult, Task<TResult>>() {
            @Override
            public Task<TResult> then(Task<TResult> task) throws Exception {
                synchronized (lock) {
                    isRunning = false;
                    if (cts != null) {
                        cts.trySetResult(null);
                    }
                    cts = null;
                }
                return task;
            }
        });
    }

    /**
     * Retrieves a list of {@link ParseObject}s that satisfy this query from the source in a
     * background thread.
     * <p/>
     * This is preferable to using {@link #find()}, unless your code is already running in a
     * background thread.
     *
     * @return A {@link Task} that will be resolved when the find has completed.
     */
    public Task<List<T>> findInBackground() {
        return findAsync(builder.build());
    }

    /**
     * Retrieves a list of {@link ParseObject}s that satisfy this query from the source in a
     * background thread.
     * <p/>
     * This is preferable to using {@link #find()}, unless your code is already running in a
     * background thread.
     *
     * @param callback
     *          callback.done(objectList, e) is called when the find completes.
     */
    public void findInBackground(final FindCallback<T> callback) {
        final State<T> state = builder.build();

        final Task<List<T>> task;
        if (state.cachePolicy() != CachePolicy.CACHE_THEN_NETWORK || state.isFromLocalDatastore()) {
            task = findAsync(state);
        } else {
            task = doCacheThenNetwork(state, callback, new CacheThenNetworkCallable<T, Task<List<T>>>() {
                @Override
                public Task<List<T>> call(State<T> state, ParseUser user, Task<Void> cancellationToken) {
                    return findAsync(state, user, cancellationToken);
                }
            });
        }
        ParseTaskUtils.callbackOnMainThreadAsync(task, callback);
    }

    private Task<List<T>> findAsync(final State<T> state) {
        return doWithRunningCheck(new Callable<Task<List<T>>>() {
            @Override
            public Task<List<T>> call() throws Exception {
                return getUserAsync(state).onSuccessTask(new Continuation<ParseUser, Task<List<T>>>() {
                    @Override
                    public Task<List<T>> then(Task<ParseUser> task) throws Exception {
                        final ParseUser user = task.getResult();
                        return findAsync(state, user, cts.getTask());
                    }
                });
            }
        });
    }

    /* package */ Task<List<T>> findAsync(State<T> state, ParseUser user, Task<Void> cancellationToken) {
        return ParseQuery.getQueryController().findAsync(state, user, cancellationToken);
    }

    /**
     * Retrieves at most one {@link ParseObject} that satisfies this query from the source in a
     * background thread.
     * <p/>
     * This is preferable to using {@link #getFirst()}, unless your code is already running in a
     * background thread.
     * <p/>
     * <strong>Note:</strong>This mutates the {@code ParseQuery}.
     *
     * @return A {@link Task} that will be resolved when the get has completed.
     */
    public Task<T> getFirstInBackground() {
        final State<T> state = builder.setLimit(1).build();
        return getFirstAsync(state);
    }

    /**
     * Retrieves at most one {@link ParseObject} that satisfies this query from the source in a
     * background thread.
     * <p/>
     * This is preferable to using {@link #getFirst()}, unless your code is already running in a
     * background thread.
     * <p/>
     * <strong>Note:</strong>This mutates the {@code ParseQuery}.
     *
     * @param callback
     *          callback.done(object, e) is called when the find completes.
     */
    public void getFirstInBackground(final GetCallback<T> callback) {
        final State<T> state = builder.setLimit(1).build();

        final Task<T> task;
        if (state.cachePolicy() != CachePolicy.CACHE_THEN_NETWORK || state.isFromLocalDatastore()) {
            task = getFirstAsync(state);
        } else {
            task = doCacheThenNetwork(state, callback, new CacheThenNetworkCallable<T, Task<T>>() {
                @Override
                public Task<T> call(State<T> state, ParseUser user, Task<Void> cancellationToken) {
                    return getFirstAsync(state, user, cancellationToken);
                }
            });
        }
        ParseTaskUtils.callbackOnMainThreadAsync(task, callback);
    }

    private Task<T> getFirstAsync(final State<T> state) {
        return doWithRunningCheck(new Callable<Task<T>>() {
            @Override
            public Task<T> call() throws Exception {
                return getUserAsync(state).onSuccessTask(new Continuation<ParseUser, Task<T>>() {
                    @Override
                    public Task<T> then(Task<ParseUser> task) throws Exception {
                        final ParseUser user = task.getResult();
                        return getFirstAsync(state, user, cts.getTask());
                    }
                });
            }
        });
    }

    private Task<T> getFirstAsync(State<T> state, ParseUser user, Task<Void> cancellationToken) {
        return ParseQuery.getQueryController().getFirstAsync(state, user, cancellationToken);
    }

    /**
     * Counts the number of objects that match this query. This does not use caching.
     *
     * @throws ParseException
     *           Throws an exception when the network connection fails or when the query is invalid.
     */
    public int count() throws ParseException {
        return ParseTaskUtils.wait(countInBackground());
    }

    /**
     * Counts the number of objects that match this query in a background thread. This does not use
     * caching.
     *
     * @return A {@link Task} that will be resolved when the count has completed.
     */
    public Task<Integer> countInBackground() {
        State.Builder<T> copy = new State.Builder<T>(builder);
        final State<T> state = copy.setLimit(0).build();
        return countAsync(state);
    }

    /**
     * Counts the number of objects that match this query in a background thread. This does not use
     * caching.
     *
     * @param callback
     *          callback.done(count, e) will be called when the count completes.
     */
    public void countInBackground(final CountCallback callback) {
        State.Builder<T> copy = new State.Builder<T>(builder);
        final State<T> state = copy.setLimit(0).build();

        // Hack to workaround CountCallback's non-uniform signature.
        final ParseCallback2<Integer, ParseException> c = callback != null
                ? new ParseCallback2<Integer, ParseException>() {
                    @Override
                    public void done(Integer integer, ParseException e) {
                        callback.done(e == null ? integer : -1, e);
                    }
                }
                : null;

        final Task<Integer> task;
        if (state.cachePolicy() != CachePolicy.CACHE_THEN_NETWORK || state.isFromLocalDatastore()) {
            task = countAsync(state);
        } else {
            task = doCacheThenNetwork(state, c, new CacheThenNetworkCallable<T, Task<Integer>>() {
                @Override
                public Task<Integer> call(State<T> state, ParseUser user, Task<Void> cancellationToken) {
                    return countAsync(state, user, cancellationToken);
                }
            });
        }
        ParseTaskUtils.callbackOnMainThreadAsync(task, c);
    }

    private Task<Integer> countAsync(final State<T> state) {
        return doWithRunningCheck(new Callable<Task<Integer>>() {
            @Override
            public Task<Integer> call() throws Exception {
                return getUserAsync(state).onSuccessTask(new Continuation<ParseUser, Task<Integer>>() {
                    @Override
                    public Task<Integer> then(Task<ParseUser> task) throws Exception {
                        final ParseUser user = task.getResult();
                        return countAsync(state, user, cts.getTask());
                    }
                });
            }
        });
    }

    private Task<Integer> countAsync(State<T> state, ParseUser user, Task<Void> cancellationToken) {
        return ParseQuery.getQueryController().countAsync(state, user, cancellationToken);
    }

    /**
     * Constructs a {@link ParseObject} whose id is already known by fetching data from the source.
     * <p/>
     * <strong>Note:</strong>This mutates the {@code ParseQuery}.
     *
     * @param objectId
     *          Object id of the {@link ParseObject} to fetch.
     * @throws ParseException
     *           Throws an exception when there is no such object or when the network connection
     *           fails.
     *
     * @see ParseException#OBJECT_NOT_FOUND
     */
    public T get(final String objectId) throws ParseException {
        return ParseTaskUtils.wait(getInBackground(objectId));
    }

    /**
     * Returns whether or not this query has a cached result.
     */
    //TODO (grantland): should be done Async since it does disk i/o & calls through to current user
    public boolean hasCachedResult() {
        throwIfLDSEnabled();

        // TODO(grantland): Is there a more efficient way to accomplish this rather than building a
        // new state just to check it's cacheKey?
        State<T> state = builder.build();

        ParseUser user = null;
        try {
            user = ParseTaskUtils.wait(getUserAsync(state));
        } catch (ParseException e) {
            // do nothing
        }
        String sessionToken = user != null ? user.getSessionToken() : null;

        /*
         * TODO: Once the count queries are cached, only return false when both queries miss in the
         * cache.
         */
        String raw = ParseKeyValueCache.loadFromKeyValueCache(
                ParseRESTQueryCommand.findCommand(state, sessionToken).getCacheKey(), state.maxCacheAge());
        return raw != null;
    }

    /**
     * Removes the previously cached result for this query, forcing the next find() to hit the
     * network. If there is no cached result for this query, then this is a no-op.
     */
    //TODO (grantland): should be done Async since it does disk i/o & calls through to current user
    public void clearCachedResult() {
        throwIfLDSEnabled();

        // TODO(grantland): Is there a more efficient way to accomplish this rather than building a
        // new state just to check it's cacheKey?
        State<T> state = builder.build();

        ParseUser user = null;
        try {
            user = ParseTaskUtils.wait(getUserAsync(state));
        } catch (ParseException e) {
            // do nothing
        }
        String sessionToken = user != null ? user.getSessionToken() : null;

        // TODO: Once the count queries are cached, handle the cached results of the count query.
        ParseKeyValueCache
                .clearFromKeyValueCache(ParseRESTQueryCommand.findCommand(state, sessionToken).getCacheKey());
    }

    /**
     * Clears the cached result for all queries.
     */
    public static void clearAllCachedResults() {
        throwIfLDSEnabled();

        ParseKeyValueCache.clearKeyValueCacheDir();
    }

    /**
     * Constructs a {@link ParseObject} whose id is already known by fetching data from the source in a
     * background thread. This does not use caching.
     * <p/>
     * This is preferable to using the {@link ParseObject#createWithoutData(String, String)}, unless
     * your code is already running in a background thread.
     *
     * @param objectId
     *          Object id of the {@link ParseObject} to fetch.
     *
     * @return A {@link Task} that is resolved when the fetch completes.
     */
    // TODO(grantland): Why is this an instance method? Shouldn't this just be a static method since
    // other parameters don't even make sense here?
    // We'll need to add a version with CancellationToken if we do.
    public Task<T> getInBackground(final String objectId) {
        final State<T> state = builder.setSkip(-1).whereObjectIdEquals(objectId).build();
        return getFirstAsync(state);
    }

    /**
     * Constructs a {@link ParseObject} whose id is already known by fetching data from the source in
     * a background thread. This does not use caching.
     * <p/>
     * This is preferable to using the {@link ParseObject#createWithoutData(String, String)}, unless
     * your code is already running in a background thread.
     *
     * @param objectId
     *          Object id of the {@link ParseObject} to fetch.
     * @param callback
     *          callback.done(object, e) will be called when the fetch completes.
     */
    // TODO(grantland): Why is this an instance method? Shouldn't this just be a static method since
    // other parameters don't even make sense here?
    // We'll need to add a version with CancellationToken if we do.
    public void getInBackground(final String objectId, final GetCallback<T> callback) {
        final State<T> state = builder.setSkip(-1).whereObjectIdEquals(objectId).build();

        final Task<T> task;
        if (state.cachePolicy() != CachePolicy.CACHE_THEN_NETWORK || state.isFromLocalDatastore()) {
            task = getFirstAsync(state);
        } else {
            task = doCacheThenNetwork(state, callback, new CacheThenNetworkCallable<T, Task<T>>() {
                @Override
                public Task<T> call(State<T> state, ParseUser user, Task<Void> cancellationToken) {
                    return getFirstAsync(state, user, cancellationToken);
                }
            });
        }
        ParseTaskUtils.callbackOnMainThreadAsync(task, callback);
    }

    //region CACHE_THEN_NETWORK

    /**
     * Helper method for CACHE_THEN_NETWORK.
     *
     * Serially executes the {@code delegate} once in cache with the {@code} callback and then returns
     * a task for the execution of the second {@code delegate} execution on the network for the caller
     * to callback on.
     */
    private <TResult> Task<TResult> doCacheThenNetwork(final ParseQuery.State<T> state,
            final ParseCallback2<TResult, ParseException> callback,
            final CacheThenNetworkCallable<T, Task<TResult>> delegate) {
        return doWithRunningCheck(new Callable<Task<TResult>>() {
            @Override
            public Task<TResult> call() throws Exception {
                return getUserAsync(state).onSuccessTask(new Continuation<ParseUser, Task<TResult>>() {
                    @Override
                    public Task<TResult> then(Task<ParseUser> task) throws Exception {
                        final ParseUser user = task.getResult();
                        final State<T> cacheState = new State.Builder<T>(state)
                                .setCachePolicy(CachePolicy.CACHE_ONLY).build();
                        final State<T> networkState = new State.Builder<T>(state)
                                .setCachePolicy(CachePolicy.NETWORK_ONLY).build();

                        Task<TResult> executionTask = delegate.call(cacheState, user, cts.getTask());
                        executionTask = ParseTaskUtils.callbackOnMainThreadAsync(executionTask, callback);
                        return executionTask.continueWithTask(new Continuation<TResult, Task<TResult>>() {
                            @Override
                            public Task<TResult> then(Task<TResult> task) throws Exception {
                                if (task.isCancelled()) {
                                    return task;
                                }
                                return delegate.call(networkState, user, cts.getTask());
                            }
                        });
                    }
                });
            }
        });
    }

    private interface CacheThenNetworkCallable<T extends ParseObject, TResult> {
        TResult call(ParseQuery.State<T> state, ParseUser user, Task<Void> cancellationToken);
    }

    //endregion

    /**
     * Add a constraint to the query that requires a particular key's value to be equal to the
     * provided value.
     *
     * @param key
     *          The key to check.
     * @param value
     *          The value that the {@link ParseObject} must contain.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereEqualTo(String key, Object value) {
        checkIfRunning();
        builder.whereEqualTo(key, value);
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's value to be less than the
     * provided value.
     *
     * @param key
     *          The key to check.
     * @param value
     *          The value that provides an upper bound.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereLessThan(String key, Object value) {
        checkIfRunning();
        builder.addCondition(key, "$lt", value);
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's value to be not equal to the
     * provided value.
     *
     * @param key
     *          The key to check.
     * @param value
     *          The value that must not be equalled.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereNotEqualTo(String key, Object value) {
        checkIfRunning();
        builder.addCondition(key, "$ne", value);
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's value to be greater than the
     * provided value.
     *
     * @param key
     *          The key to check.
     * @param value
     *          The value that provides an lower bound.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereGreaterThan(String key, Object value) {
        checkIfRunning();
        builder.addCondition(key, "$gt", value);
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's value to be less than or equal
     * to the provided value.
     *
     * @param key
     *          The key to check.
     * @param value
     *          The value that provides an upper bound.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereLessThanOrEqualTo(String key, Object value) {
        checkIfRunning();
        builder.addCondition(key, "$lte", value);
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's value to be greater than or
     * equal to the provided value.
     *
     * @param key
     *          The key to check.
     * @param value
     *          The value that provides an lower bound.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereGreaterThanOrEqualTo(String key, Object value) {
        checkIfRunning();
        builder.addCondition(key, "$gte", value);
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's value to be contained in the
     * provided list of values.
     *
     * @param key
     *          The key to check.
     * @param values
     *          The values that will match.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereContainedIn(String key, Collection<? extends Object> values) {
        checkIfRunning();
        builder.addCondition(key, "$in", values);
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's value match another
     * {@code ParseQuery}.
     * <p/>
     * This only works on keys whose values are {@link ParseObject}s or lists of {@link ParseObject}s.
     * Add a constraint to the query that requires a particular key's value to contain every one of
     * the provided list of values.
     *
     * @param key
     *          The key to check. This key's value must be an array.
     * @param values
     *          The values that will match.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereContainsAll(String key, Collection<?> values) {
        checkIfRunning();
        builder.addCondition(key, "$all", values);
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's value match another
     * {@code ParseQuery}.
     * <p/>
     * This only works on keys whose values are {@link ParseObject}s or lists of {@link ParseObject}s.
     *
     * @param key
     *          The key to check.
     * @param query
     *          The query that the value should match
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereMatchesQuery(String key, ParseQuery<?> query) {
        checkIfRunning();
        builder.whereMatchesQuery(key, query.getBuilder());
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's value does not match another
     * {@code ParseQuery}.
     * <p/>
     * This only works on keys whose values are {@link ParseObject}s or lists of {@link ParseObject}s.
     *
     * @param key
     *          The key to check.
     * @param query
     *          The query that the value should not match
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereDoesNotMatchQuery(String key, ParseQuery<?> query) {
        checkIfRunning();
        builder.whereDoesNotMatchQuery(key, query.getBuilder());
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's value matches a value for a key
     * in the results of another {@code ParseQuery}.
     *
     * @param key
     *          The key whose value is being checked
     * @param keyInQuery
     *          The key in the objects from the sub query to look in
     * @param query
     *          The sub query to run
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereMatchesKeyInQuery(String key, String keyInQuery, ParseQuery<?> query) {
        checkIfRunning();
        builder.whereMatchesKeyInQuery(key, keyInQuery, query.getBuilder());
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's value does not match any value
     * for a key in the results of another {@code ParseQuery}.
     *
     * @param key
     *          The key whose value is being checked and excluded
     * @param keyInQuery
     *          The key in the objects from the sub query to look in
     * @param query
     *          The sub query to run
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereDoesNotMatchKeyInQuery(String key, String keyInQuery, ParseQuery<?> query) {
        checkIfRunning();
        builder.whereDoesNotMatchKeyInQuery(key, keyInQuery, query.getBuilder());
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's value not be contained in the
     * provided list of values.
     *
     * @param key
     *          The key to check.
     * @param values
     *          The values that will not match.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereNotContainedIn(String key, Collection<? extends Object> values) {
        checkIfRunning();
        builder.addCondition(key, "$nin", values);
        return this;
    }

    /**
     * Add a proximity based constraint for finding objects with key point values near the point
     * given.
     *
     * @param key
     *          The key that the {@link ParseGeoPoint} is stored in.
     * @param point
     *          The reference {@link ParseGeoPoint} that is used.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereNear(String key, ParseGeoPoint point) {
        checkIfRunning();
        builder.whereNear(key, point);
        return this;
    }

    /**
     * Add a proximity based constraint for finding objects with key point values near the point given
     * and within the maximum distance given.
     * <p/>
     * Radius of earth used is {@code 3958.8} miles.
     *
     * @param key
     *          The key that the {@link ParseGeoPoint} is stored in.
     * @param point
     *          The reference {@link ParseGeoPoint} that is used.
     * @param maxDistance
     *          Maximum distance (in miles) of results to return.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereWithinMiles(String key, ParseGeoPoint point, double maxDistance) {
        checkIfRunning();
        return whereWithinRadians(key, point, maxDistance / ParseGeoPoint.EARTH_MEAN_RADIUS_MILE);
    }

    /**
     * Add a proximity based constraint for finding objects with key point values near the point given
     * and within the maximum distance given.
     * <p/>
     * Radius of earth used is {@code 6371.0} kilometers.
     *
     * @param key
     *          The key that the {@link ParseGeoPoint} is stored in.
     * @param point
     *          The reference {@link ParseGeoPoint} that is used.
     * @param maxDistance
     *          Maximum distance (in kilometers) of results to return.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereWithinKilometers(String key, ParseGeoPoint point, double maxDistance) {
        checkIfRunning();
        return whereWithinRadians(key, point, maxDistance / ParseGeoPoint.EARTH_MEAN_RADIUS_KM);
    }

    /**
     * Add a proximity based constraint for finding objects with key point values near the point given
     * and within the maximum distance given.
     *
     * @param key
     *          The key that the {@link ParseGeoPoint} is stored in.
     * @param point
     *          The reference {@link ParseGeoPoint} that is used.
     * @param maxDistance
     *          Maximum distance (in radians) of results to return.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereWithinRadians(String key, ParseGeoPoint point, double maxDistance) {
        checkIfRunning();
        builder.whereNear(key, point).maxDistance(key, maxDistance);
        return this;
    }

    /**
     * Add a constraint to the query that requires a particular key's coordinates be contained within
     * a given rectangular geographic bounding box.
     *
     * @param key
     *          The key to be constrained.
     * @param southwest
     *          The lower-left inclusive corner of the box.
     * @param northeast
     *          The upper-right inclusive corner of the box.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereWithinGeoBox(String key, ParseGeoPoint southwest, ParseGeoPoint northeast) {
        checkIfRunning();
        builder.whereWithin(key, southwest, northeast);
        return this;
    }

    /**
     * Add a regular expression constraint for finding string values that match the provided regular
     * expression.
     * <p/>
     * This may be slow for large datasets.
     *
     * @param key
     *          The key that the string to match is stored in.
     * @param regex
     *          The regular expression pattern to match.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereMatches(String key, String regex) {
        checkIfRunning();
        builder.addCondition(key, "$regex", regex);
        return this;
    }

    /**
     * Add a regular expression constraint for finding string values that match the provided regular
     * expression.
     * <p/>
     * This may be slow for large datasets.
     *
     * @param key
     *          The key that the string to match is stored in.
     * @param regex
     *          The regular expression pattern to match.
     * @param modifiers
     *          Any of the following supported PCRE modifiers:<br>
     *          <code>i</code> - Case insensitive search<br>
     *          <code>m</code> - Search across multiple lines of input<br>
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereMatches(String key, String regex, String modifiers) {
        checkIfRunning();
        builder.addCondition(key, "$regex", regex);
        if (modifiers.length() != 0) {
            builder.addCondition(key, "$options", modifiers);
        }
        return this;
    }

    /**
     * Add a constraint for finding string values that contain a provided string.
     * <p/>
     * This will be slow for large datasets.
     *
     * @param key
     *          The key that the string to match is stored in.
     * @param substring
     *          The substring that the value must contain.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereContains(String key, String substring) {
        String regex = Pattern.quote(substring);
        whereMatches(key, regex);
        return this;
    }

    /**
     * Add a constraint for finding string values that start with a provided string.
     * <p/>
     * This query will use the backend index, so it will be fast even for large datasets.
     *
     * @param key
     *          The key that the string to match is stored in.
     * @param prefix
     *          The substring that the value must start with.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereStartsWith(String key, String prefix) {
        String regex = "^" + Pattern.quote(prefix);
        whereMatches(key, regex);
        return this;
    }

    /**
     * Add a constraint for finding string values that end with a provided string.
     * <p/>
     * This will be slow for large datasets.
     *
     * @param key
     *          The key that the string to match is stored in.
     * @param suffix
     *          The substring that the value must end with.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereEndsWith(String key, String suffix) {
        String regex = Pattern.quote(suffix) + "$";
        whereMatches(key, regex);
        return this;
    }

    /**
     * Include nested {@link ParseObject}s for the provided key.
     * <p/>
     * You can use dot notation to specify which fields in the included object that are also fetched.
     *
     * @param key
     *          The key that should be included.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> include(String key) {
        checkIfRunning();
        builder.include(key);
        return this;
    }

    /**
     * Restrict the fields of returned {@link ParseObject}s to only include the provided keys.
     * <p/>
     * If this is called multiple times, then all of the keys specified in each of the calls will be
     * included.
     * <p/>
     * <strong>Note:</strong> This option will be ignored when querying from the local datastore. This
     * is done since all the keys will be in memory anyway and there will be no performance gain from
     * removing them.
     *
     * @param keys
     *          The set of keys to include in the result.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> selectKeys(Collection<String> keys) {
        checkIfRunning();
        builder.selectKeys(keys);
        return this;
    }

    /**
     * Add a constraint for finding objects that contain the given key.
     *
     * @param key
     *          The key that should exist.
     *
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereExists(String key) {
        checkIfRunning();
        builder.addCondition(key, "$exists", true);
        return this;
    }

    /**
     * Add a constraint for finding objects that do not contain a given key.
     *
     * @param key
     *          The key that should not exist
     *
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> whereDoesNotExist(String key) {
        checkIfRunning();
        builder.addCondition(key, "$exists", false);
        return this;
    }

    /**
     * Sorts the results in ascending order by the given key.
     *
     * @param key
     *          The key to order by.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> orderByAscending(String key) {
        checkIfRunning();
        builder.orderByAscending(key);
        return this;
    }

    /**
     * Also sorts the results in ascending order by the given key.
     * <p/>
     * The previous sort keys have precedence over this key.
     *
     * @param key
     *          The key to order by
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> addAscendingOrder(String key) {
        checkIfRunning();
        builder.addAscendingOrder(key);
        return this;
    }

    /**
     * Sorts the results in descending order by the given key.
     *
     * @param key
     *          The key to order by.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> orderByDescending(String key) {
        checkIfRunning();
        builder.orderByDescending(key);
        return this;
    }

    /**
     * Also sorts the results in descending order by the given key.
     * <p/>
     * The previous sort keys have precedence over this key.
     *
     * @param key
     *          The key to order by
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> addDescendingOrder(String key) {
        checkIfRunning();
        builder.addDescendingOrder(key);
        return this;
    }

    /**
     * Controls the maximum number of results that are returned.
     * <p/>
     * Setting a negative limit denotes retrieval without a limit. The default limit is {@code 100},
     * with a maximum of {@code 1000} results being returned at a time.
     *
     * @param newLimit The new limit.
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> setLimit(int newLimit) {
        checkIfRunning();
        builder.setLimit(newLimit);
        return this;
    }

    /**
     * Accessor for the limit.
     */
    public int getLimit() {
        return builder.getLimit();
    }

    /**
     * Controls the number of results to skip before returning any results.
     * <p/>
     * This is useful for pagination. Default is to skip zero results.
     *
     * @param newSkip The new skip
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> setSkip(int newSkip) {
        checkIfRunning();
        builder.setSkip(newSkip);
        return this;
    }

    /**
     * Accessor for the skip value.
     */
    public int getSkip() {
        return builder.getSkip();
    }

    /**
     * Accessor for the class name.
     */
    public String getClassName() {
        return builder.getClassName();
    }

    /**
     * Turn on performance tracing of finds.
     * <p/>
     * If performance tracing is already turned on this does nothing. In general you don't need to call trace.
     *
     * @return this, so you can chain this call.
     */
    public ParseQuery<T> setTrace(boolean shouldTrace) {
        checkIfRunning();
        builder.setTracingEnabled(shouldTrace);
        return this;
    }
}