com.parse.ParseRelation.java Source code

Java tutorial

Introduction

Here is the source code for com.parse.ParseRelation.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 java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

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

/**
 * A class that is used to access all of the children of a many-to-many relationship. Each instance
 * of Parse.Relation is associated with a particular parent object and key.
 */
public class ParseRelation<T extends ParseObject> {
    private final Object mutex = new Object();

    // The owning object of this ParseRelation.
    private WeakReference<ParseObject> parent;

    // The object Id of the parent.
    private String parentObjectId;

    // The classname of the parent to retrieve the parent ParseObject in case the parent is GC'ed.
    private String parentClassName;

    // The key of the relation in the parent object.
    private String key;

    // The className of the target objects.
    private String targetClass;

    // For offline caching, we keep track of every object we've known to be in the relation.
    private Set<ParseObject> knownObjects = new HashSet<>();

    /* package */ ParseRelation(ParseObject parent, String key) {
        this.parent = new WeakReference<>(parent);
        this.parentObjectId = parent.getObjectId();
        this.parentClassName = parent.getClassName();
        this.key = key;
        this.targetClass = null;
    }

    /* package */ ParseRelation(String targetClass) {
        this.parent = null;
        this.parentObjectId = null;
        this.parentClassName = null;
        this.key = null;
        this.targetClass = targetClass;
    }

    /**
     * Parses a relation from JSON.
     */
    /* package */ ParseRelation(JSONObject jsonObject, ParseDecoder decoder) {
        this.parent = null;
        this.parentObjectId = null;
        this.parentClassName = null;
        this.key = null;
        this.targetClass = jsonObject.optString("className", null);
        JSONArray objectsArray = jsonObject.optJSONArray("objects");
        if (objectsArray != null) {
            for (int i = 0; i < objectsArray.length(); ++i) {
                knownObjects.add((ParseObject) decoder.decode(objectsArray.optJSONObject(i)));
            }
        }
    }

    /* package */ void ensureParentAndKey(ParseObject someParent, String someKey) {
        synchronized (mutex) {
            if (parent == null) {
                parent = new WeakReference<>(someParent);
                parentObjectId = someParent.getObjectId();
                parentClassName = someParent.getClassName();
            }
            if (key == null) {
                key = someKey;
            }
            if (parent.get() != someParent) {
                throw new IllegalStateException(
                        "Internal error. One ParseRelation retrieved from two different ParseObjects.");
            }
            if (!key.equals(someKey)) {
                throw new IllegalStateException(
                        "Internal error. One ParseRelation retrieved from two different keys.");
            }
        }
    }

    /**
     * Adds an object to this relation.
     * 
     * @param object
     *          The object to add to this relation.
     */
    public void add(T object) {
        synchronized (mutex) {
            ParseRelationOperation<T> operation = new ParseRelationOperation<>(Collections.singleton(object), null);
            targetClass = operation.getTargetClass();
            getParent().performOperation(key, operation);

            knownObjects.add(object);
        }
    }

    /**
     * Removes an object from this relation.
     * 
     * @param object
     *          The object to remove from this relation.
     */
    public void remove(T object) {
        synchronized (mutex) {
            ParseRelationOperation<T> operation = new ParseRelationOperation<>(null, Collections.singleton(object));
            targetClass = operation.getTargetClass();
            getParent().performOperation(key, operation);

            knownObjects.remove(object);
        }
    }

    /**
     * Gets a query that can be used to query the objects in this relation.
     * 
     * @return A ParseQuery that restricts the results to objects in this relations.
     */
    public ParseQuery<T> getQuery() {
        synchronized (mutex) {
            ParseQuery.State.Builder<T> builder;
            if (targetClass == null) {
                builder = new ParseQuery.State.Builder<T>(parentClassName).redirectClassNameForKey(key);
            } else {
                builder = new ParseQuery.State.Builder<>(targetClass);
            }
            builder.whereRelatedTo(getParent(), key);
            return new ParseQuery<>(builder);
        }
    }

    /* package */ JSONObject encodeToJSON(ParseEncoder objectEncoder) throws JSONException {
        synchronized (mutex) {
            JSONObject relation = new JSONObject();
            relation.put("__type", "Relation");
            relation.put("className", targetClass);
            JSONArray knownObjectsArray = new JSONArray();
            for (ParseObject knownObject : knownObjects) {
                try {
                    knownObjectsArray.put(objectEncoder.encodeRelatedObject(knownObject));
                } catch (Exception e) {
                    // This is just for caching, so if an object can't be encoded for any reason, drop it.
                }
            }
            relation.put("objects", knownObjectsArray);
            return relation;
        }
    }

    /* package */ String getTargetClass() {
        synchronized (mutex) {
            return targetClass;
        }
    }

    /* package */ void setTargetClass(String className) {
        synchronized (mutex) {
            targetClass = className;
        }
    }

    /**
     * Adds an object that is known to be in the relation. This is used for offline caching.
     */
    /* package */ void addKnownObject(ParseObject object) {
        synchronized (mutex) {
            knownObjects.add(object);
        }
    }

    /**
     * Removes an object that is known to not be in the relation. This is used for offline caching.
     */
    /* package */ void removeKnownObject(ParseObject object) {
        synchronized (mutex) {
            knownObjects.remove(object);
        }
    }

    /**
     * Returns true iff this object was ever known to be in the relation. This is used for offline
     * caching.
     */
    /* package */ boolean hasKnownObject(ParseObject object) {
        synchronized (mutex) {
            return knownObjects.contains(object);
        }
    }

    /* package for tests */ ParseObject getParent() {
        if (parent == null) {
            return null;
        }
        if (parent.get() == null) {
            return ParseObject.createWithoutData(parentClassName, parentObjectId);
        }
        return parent.get();
    }

    /* package for tests */ String getKey() {
        return key;
    }

    /* package for tests */ Set<ParseObject> getKnownObjects() {
        return knownObjects;
    }
}