· 4 years ago · May 03, 2021, 08:00 PM
1/*
2 * Copyright (c) 2015-present, Parse, LLC.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
8 */
9package com.parse;
10
11import android.os.Bundle;
12import android.os.Parcel;
13import android.os.Parcelable;
14import android.support.annotation.NonNull;
15import android.support.annotation.Nullable;
16
17import org.json.JSONArray;
18import org.json.JSONException;
19import org.json.JSONObject;
20
21import java.util.ArrayList;
22import java.util.Arrays;
23import java.util.Collection;
24import java.util.Collections;
25import java.util.Date;
26import java.util.HashMap;
27import java.util.HashSet;
28import java.util.Iterator;
29import java.util.LinkedList;
30import java.util.List;
31import java.util.ListIterator;
32import java.util.Locale;
33import java.util.Map;
34import java.util.Set;
35import java.util.concurrent.Callable;
36import java.util.concurrent.atomic.AtomicBoolean;
37import java.util.concurrent.locks.Lock;
38
39import bolts.Capture;
40import bolts.Continuation;
41import bolts.Task;
42import bolts.TaskCompletionSource;
43
44/**
45 * The {@code ParseObject} is a local representation of data that can be saved and retrieved from
46 * the Parse cloud.
47 * <p/>
48 * The basic workflow for creating new data is to construct a new {@code ParseObject}, use
49 * {@link #put(String, Object)} to fill it with data, and then use {@link #saveInBackground()} to
50 * persist to the cloud.
51 * <p/>
52 * The basic workflow for accessing existing data is to use a {@link ParseQuery} to specify which
53 * existing data to retrieve.
54 */
55@SuppressWarnings({"unused", "WeakerAccess"})
56public class ParseObject implements Parcelable {
57 /**
58 * Default name for pinning if not specified.
59 *
60 * @see #pin()
61 * @see #unpin()
62 */
63 public static final String DEFAULT_PIN = "_default";
64 static final String KEY_IS_DELETING_EVENTUALLY = "__isDeletingEventually";
65 private static final String AUTO_CLASS_NAME = "_Automatic";
66 private static final String TAG = "ParseObject";
67 /*
68 REST JSON Keys
69 */
70 private static final String KEY_OBJECT_ID = "objectId";
71 private static final String KEY_CLASS_NAME = "className";
72 private static final String KEY_ACL = "ACL";
73 private static final String KEY_CREATED_AT = "createdAt";
74 private static final String KEY_UPDATED_AT = "updatedAt";
75 /*
76 Internal JSON Keys - Used to store internal data when persisting {@code ParseObject}s locally.
77 */
78 private static final String KEY_COMPLETE = "__complete";
79 private static final String KEY_OPERATIONS = "__operations";
80 // Array of keys selected when querying for the object. Helps decoding nested {@code ParseObject}s
81 // correctly, and helps constructing the {@code State.availableKeys()} set.
82 private static final String KEY_SELECTED_KEYS = "__selectedKeys";
83 // Because Grantland messed up naming this... We'll only try to read from this for backward
84 // compat, but I think we can be safe to assume any deleteEventuallys from long ago are obsolete
85 // and not check after a while
86 private static final String KEY_IS_DELETING_EVENTUALLY_OLD = "isDeletingEventually";
87 private static final ThreadLocal<String> isCreatingPointerForObjectId =
88 new ThreadLocal<String>() {
89 @Override
90 protected String initialValue() {
91 return null;
92 }
93 };
94 /*
95 * This is used only so that we can pass it to createWithoutData as the objectId to make it create
96 * an un-fetched pointer that has no objectId. This is useful only in the context of the offline
97 * store, where you can have an un-fetched pointer for an object that can later be fetched from the
98 * store.
99 */
100 private static final String NEW_OFFLINE_OBJECT_ID_PLACEHOLDER =
101 "*** Offline Object ***";
102 public final static Creator<ParseObject> CREATOR = new Creator<ParseObject>() {
103 @Override
104 public ParseObject createFromParcel(Parcel source) {
105 return ParseObject.createFromParcel(source, new ParseObjectParcelDecoder());
106 }
107
108 @Override
109 public ParseObject[] newArray(int size) {
110 return new ParseObject[size];
111 }
112 };
113 final Object mutex = new Object();
114 final TaskQueue taskQueue = new TaskQueue();
115 final LinkedList<ParseOperationSet> operationSetQueue;
116 // Cached State
117 private final Map<String, Object> estimatedData;
118 private final ParseMulticastDelegate<ParseObject> saveEvent = new ParseMulticastDelegate<>();
119 String localId;
120 boolean isDeleted;
121 boolean isDeleting; // Since delete ops are queued, we don't need a counter.
122 //TODO (grantland): Derive this off the EventuallyPins as opposed to +/- count.
123 int isDeletingEventually;
124 private State state;
125 private boolean ldsEnabledWhenParceling;
126
127 /**
128 * The base class constructor to call in subclasses. Uses the class name specified with the
129 * {@link ParseClassName} annotation on the subclass.
130 */
131 protected ParseObject() {
132 this(AUTO_CLASS_NAME);
133 }
134
135 /**
136 * Constructs a new {@code ParseObject} with no data in it. A {@code ParseObject} constructed in
137 * this way will not have an objectId and will not persist to the database until {@link #save()}
138 * is called.
139 * <p>
140 * Class names must be alphanumerical plus underscore, and start with a letter. It is recommended
141 * to name classes in <code>PascalCaseLikeThis</code>.
142 *
143 * @param theClassName The className for this {@code ParseObject}.
144 */
145 public ParseObject(String theClassName) {
146 // We use a ThreadLocal rather than passing a parameter so that createWithoutData can do the
147 // right thing with subclasses. It's ugly and terrible, but it does provide the development
148 // experience we generally want, so... yeah. Sorry to whomever has to deal with this in the
149 // future. I pinky-swear we won't make a habit of this -- you believe me, don't you?
150 String objectIdForPointer = isCreatingPointerForObjectId.get();
151
152 if (theClassName == null) {
153 throw new IllegalArgumentException(
154 "You must specify a Parse class name when creating a new ParseObject.");
155 }
156 if (AUTO_CLASS_NAME.equals(theClassName)) {
157 theClassName = getSubclassingController().getClassName(getClass());
158 }
159
160 // If this is supposed to be created by a factory but wasn't, throw an exception.
161 if (!getSubclassingController().isSubclassValid(theClassName, getClass())) {
162 throw new IllegalArgumentException(
163 "You must create this type of ParseObject using ParseObject.create() or the proper subclass.");
164 }
165
166 operationSetQueue = new LinkedList<>();
167 operationSetQueue.add(new ParseOperationSet());
168 estimatedData = new HashMap<>();
169
170 State.Init<?> builder = newStateBuilder(theClassName);
171 // When called from new, assume hasData for the whole object is true.
172 if (objectIdForPointer == null) {
173 setDefaultValues();
174 builder.isComplete(true);
175 } else {
176 if (!objectIdForPointer.equals(NEW_OFFLINE_OBJECT_ID_PLACEHOLDER)) {
177 builder.objectId(objectIdForPointer);
178 }
179 builder.isComplete(false);
180 }
181 // This is a new untouched object, we don't need cache rebuilding, etc.
182 state = builder.build();
183
184 OfflineStore store = Parse.getLocalDatastore();
185 if (store != null) {
186 store.registerNewObject(this);
187 }
188 }
189
190 private static ParseObjectController getObjectController() {
191 return ParseCorePlugins.getInstance().getObjectController();
192 }
193
194 private static LocalIdManager getLocalIdManager() {
195 return ParseCorePlugins.getInstance().getLocalIdManager();
196 }
197
198 private static ParseObjectSubclassingController getSubclassingController() {
199 return ParseCorePlugins.getInstance().getSubclassingController();
200 }
201
202 /**
203 * Creates a new {@code ParseObject} based upon a class name. If the class name is a special type
204 * (e.g. for {@code ParseUser}), then the appropriate type of {@code ParseObject} is returned.
205 *
206 * @param className The class of object to create.
207 * @return A new {@code ParseObject} for the given class name.
208 */
209 public static ParseObject create(String className) {
210 return getSubclassingController().newInstance(className);
211 }
212
213 /**
214 * Creates a new {@code ParseObject} based upon a subclass type. Note that the object will be
215 * created based upon the {@link ParseClassName} of the given subclass type. For example, calling
216 * create(ParseUser.class) may create an instance of a custom subclass of {@code ParseUser}.
217 *
218 * @param subclass The class of object to create.
219 * @return A new {@code ParseObject} based upon the class name of the given subclass type.
220 */
221 @SuppressWarnings("unchecked")
222 public static <T extends ParseObject> T create(Class<T> subclass) {
223 return (T) create(getSubclassingController().getClassName(subclass));
224 }
225
226 /**
227 * Creates a reference to an existing {@code ParseObject} for use in creating associations between
228 * {@code ParseObject}s. Calling {@link #isDataAvailable()} on this object will return
229 * {@code false} until {@link #fetchIfNeeded()} or {@link #fetch()} has been called. No network
230 * request will be made.
231 *
232 * @param className The object's class.
233 * @param objectId The object id for the referenced object.
234 * @return A {@code ParseObject} without data.
235 */
236 public static ParseObject createWithoutData(String className, String objectId) {
237 OfflineStore store = Parse.getLocalDatastore();
238 try {
239 if (objectId == null) {
240 isCreatingPointerForObjectId.set(NEW_OFFLINE_OBJECT_ID_PLACEHOLDER);
241 } else {
242 isCreatingPointerForObjectId.set(objectId);
243 }
244 ParseObject object = null;
245 if (store != null && objectId != null) {
246 object = store.getObject(className, objectId);
247 }
248
249 if (object == null) {
250 object = create(className);
251 if (object.hasChanges()) {
252 throw new IllegalStateException(
253 "A ParseObject subclass default constructor must not make changes "
254 + "to the object that cause it to be dirty."
255 );
256 }
257 }
258
259 return object;
260
261 } catch (RuntimeException e) {
262 throw e;
263 } catch (Exception e) {
264 throw new RuntimeException("Failed to create instance of subclass.", e);
265 } finally {
266 isCreatingPointerForObjectId.set(null);
267 }
268 }
269
270 /**
271 * Creates a reference to an existing {@code ParseObject} for use in creating associations between
272 * {@code ParseObject}s. Calling {@link #isDataAvailable()} on this object will return
273 * {@code false} until {@link #fetchIfNeeded()} or {@link #fetch()} has been called. No network
274 * request will be made.
275 *
276 * @param subclass The {@code ParseObject} subclass to create.
277 * @param objectId The object id for the referenced object.
278 * @return A {@code ParseObject} without data.
279 */
280 @SuppressWarnings({"unused", "unchecked"})
281 public static <T extends ParseObject> T createWithoutData(Class<T> subclass, String objectId) {
282 return (T) createWithoutData(getSubclassingController().getClassName(subclass), objectId);
283 }
284
285 /**
286 * Registers a custom subclass type with the Parse SDK, enabling strong-typing of those
287 * {@code ParseObject}s whenever they appear. Subclasses must specify the {@link ParseClassName}
288 * annotation and have a default constructor.
289 *
290 * @param subclass The subclass type to register.
291 */
292 public static void registerSubclass(Class<? extends ParseObject> subclass) {
293 getSubclassingController().registerSubclass(subclass);
294 }
295
296 /* package for tests */
297 static void unregisterSubclass(Class<? extends ParseObject> subclass) {
298 getSubclassingController().unregisterSubclass(subclass);
299 }
300
301 /**
302 * Adds a task to the queue for all of the given objects.
303 */
304 static <T> Task<T> enqueueForAll(final List<? extends ParseObject> objects,
305 Continuation<Void, Task<T>> taskStart) {
306 // The task that will be complete when all of the child queues indicate they're ready to start.
307 final TaskCompletionSource<Void> readyToStart = new TaskCompletionSource<>();
308
309 // First, we need to lock the mutex for the queue for every object. We have to hold this
310 // from at least when taskStart() is called to when obj.taskQueue enqueue is called, so
311 // that saves actually get executed in the order they were setup by taskStart().
312 // The locks have to be sorted so that we always acquire them in the same order.
313 // Otherwise, there's some risk of deadlock.
314 List<Lock> locks = new ArrayList<>(objects.size());
315 for (ParseObject obj : objects) {
316 locks.add(obj.taskQueue.getLock());
317 }
318 LockSet lock = new LockSet(locks);
319
320 lock.lock();
321 try {
322 // The task produced by TaskStart
323 final Task<T> fullTask;
324 try {
325 // By running this immediately, we allow everything prior to toAwait to run before waiting
326 // for all of the queues on all of the objects.
327 fullTask = taskStart.then(readyToStart.getTask());
328 } catch (RuntimeException e) {
329 throw e;
330 } catch (Exception e) {
331 throw new RuntimeException(e);
332 }
333
334 // Add fullTask to each of the objects' queues.
335 final List<Task<Void>> childTasks = new ArrayList<>();
336 for (ParseObject obj : objects) {
337 obj.taskQueue.enqueue(new Continuation<Void, Task<T>>() {
338 @Override
339 public Task<T> then(Task<Void> task) {
340 childTasks.add(task);
341 return fullTask;
342 }
343 });
344 }
345
346 // When all of the objects' queues are ready, signal fullTask that it's ready to go on.
347 Task.whenAll(childTasks).continueWith(new Continuation<Void, Void>() {
348 @Override
349 public Void then(Task<Void> task) {
350 readyToStart.setResult(null);
351 return null;
352 }
353 });
354 return fullTask;
355 } finally {
356 lock.unlock();
357 }
358 }
359
360 /**
361 * Converts a {@code ParseObject.State} to a {@code ParseObject}.
362 *
363 * @param state The {@code ParseObject.State} to convert from.
364 * @return A {@code ParseObject} instance.
365 */
366 static <T extends ParseObject> T from(ParseObject.State state) {
367 @SuppressWarnings("unchecked")
368 T object = (T) ParseObject.createWithoutData(state.className(), state.objectId());
369 synchronized (object.mutex) {
370 State newState;
371 if (state.isComplete()) {
372 newState = state;
373 } else {
374 newState = object.getState().newBuilder().apply(state).build();
375 }
376 object.setState(newState);
377 }
378 return object;
379 }
380
381 /**
382 * Creates a new {@code ParseObject} based on data from the Parse server.
383 *
384 * @param json The object's data.
385 * @param defaultClassName The className of the object, if none is in the JSON.
386 * @param decoder Delegate for knowing how to decode the values in the JSON.
387 * @param selectedKeys Set of keys selected when quering for this object. If none, the object is assumed to
388 * be complete, i.e. this is all the data for the object on the server.
389 */
390 public static <T extends ParseObject> T fromJSON(JSONObject json, String defaultClassName,
391 ParseDecoder decoder,
392 Set<String> selectedKeys) {
393 if (selectedKeys != null && !selectedKeys.isEmpty()) {
394 JSONArray keys = new JSONArray(selectedKeys);
395 try {
396 json.put(KEY_SELECTED_KEYS, keys);
397 } catch (JSONException e) {
398 throw new RuntimeException(e);
399 }
400 }
401 return fromJSON(json, defaultClassName, decoder);
402 }
403
404 /**
405 * Creates a new {@code ParseObject} based on data from the Parse server.
406 *
407 * @param json The object's data. It is assumed to be complete, unless the JSON has the
408 * {@link #KEY_SELECTED_KEYS} key.
409 * @param defaultClassName The className of the object, if none is in the JSON.
410 * @param decoder Delegate for knowing how to decode the values in the JSON.
411 */
412 public static <T extends ParseObject> T fromJSON(JSONObject json, String defaultClassName,
413 ParseDecoder decoder) {
414 String className = json.optString(KEY_CLASS_NAME, defaultClassName);
415 if (className == null) {
416 return null;
417 }
418 String objectId = json.optString(KEY_OBJECT_ID, null);
419 boolean isComplete = !json.has(KEY_SELECTED_KEYS);
420 @SuppressWarnings("unchecked")
421 T object = (T) ParseObject.createWithoutData(className, objectId);
422 State newState = object.mergeFromServer(object.getState(), json, decoder, isComplete);
423 object.setState(newState);
424 return object;
425 }
426
427 //region Getter/Setter helper methods
428
429 /**
430 * Method used by parse server webhooks implementation to convert raw JSON to Parse Object
431 * <p>
432 * Method is used by parse server webhooks implementation to create a
433 * new {@code ParseObject} from the incoming json payload. The method is different from
434 * {@link #fromJSON(JSONObject, String, ParseDecoder, Set)} ()} in that it calls
435 * {@link #build(JSONObject, ParseDecoder)} which populates operation queue
436 * rather then the server data from the incoming JSON, as at external server the incoming
437 * JSON may not represent the actual server data. Also it handles
438 * {@link ParseFieldOperations} separately.
439 *
440 * @param json The object's data.
441 * @param decoder Delegate for knowing how to decode the values in the JSON.
442 */
443
444 static <T extends ParseObject> T fromJSONPayload(
445 JSONObject json, ParseDecoder decoder) {
446 String className = json.optString(KEY_CLASS_NAME);
447 if (className == null || ParseTextUtils.isEmpty(className)) {
448 return null;
449 }
450 String objectId = json.optString(KEY_OBJECT_ID, null);
451 @SuppressWarnings("unchecked")
452 T object = (T) ParseObject.createWithoutData(className, objectId);
453 object.build(json, decoder);
454 return object;
455 }
456
457 /**
458 * This deletes all of the objects from the given List.
459 */
460 private static <T extends ParseObject> Task<Void> deleteAllAsync(
461 final List<T> objects, final String sessionToken) {
462 if (objects.size() == 0) {
463 return Task.forResult(null);
464 }
465
466 // Create a list of unique objects based on objectIds
467 int objectCount = objects.size();
468 final List<ParseObject> uniqueObjects = new ArrayList<>(objectCount);
469 final HashSet<String> idSet = new HashSet<>();
470 for (int i = 0; i < objectCount; i++) {
471 ParseObject obj = objects.get(i);
472 if (!idSet.contains(obj.getObjectId())) {
473 idSet.add(obj.getObjectId());
474 uniqueObjects.add(obj);
475 }
476 }
477
478 return enqueueForAll(uniqueObjects, new Continuation<Void, Task<Void>>() {
479 @Override
480 public Task<Void> then(Task<Void> toAwait) {
481 return deleteAllAsync(uniqueObjects, sessionToken, toAwait);
482 }
483 });
484 }
485
486 private static <T extends ParseObject> Task<Void> deleteAllAsync(
487 final List<T> uniqueObjects, final String sessionToken, Task<Void> toAwait) {
488 return toAwait.continueWithTask(new Continuation<Void, Task<Void>>() {
489 @Override
490 public Task<Void> then(Task<Void> task) {
491 int objectCount = uniqueObjects.size();
492 List<ParseObject.State> states = new ArrayList<>(objectCount);
493 for (int i = 0; i < objectCount; i++) {
494 ParseObject object = uniqueObjects.get(i);
495 object.validateDelete();
496 states.add(object.getState());
497 }
498 List<Task<Void>> batchTasks = getObjectController().deleteAllAsync(states, sessionToken);
499
500 List<Task<Void>> tasks = new ArrayList<>(objectCount);
501 for (int i = 0; i < objectCount; i++) {
502 Task<Void> batchTask = batchTasks.get(i);
503 final T object = uniqueObjects.get(i);
504 tasks.add(batchTask.onSuccessTask(new Continuation<Void, Task<Void>>() {
505 @Override
506 public Task<Void> then(final Task<Void> batchTask) {
507 return object.handleDeleteResultAsync().continueWithTask(new Continuation<Void, Task<Void>>() {
508 @Override
509 public Task<Void> then(Task<Void> task) {
510 return batchTask;
511 }
512 });
513 }
514 }));
515 }
516 return Task.whenAll(tasks);
517 }
518 });
519 }
520
521 /**
522 * Deletes each object in the provided list. This is faster than deleting each object individually
523 * because it batches the requests.
524 *
525 * @param objects The objects to delete.
526 * @throws ParseException Throws an exception if the server returns an error or is inaccessible.
527 */
528 public static <T extends ParseObject> void deleteAll(List<T> objects) throws ParseException {
529 ParseTaskUtils.wait(deleteAllInBackground(objects));
530 }
531
532 /**
533 * Deletes each object in the provided list. This is faster than deleting each object individually
534 * because it batches the requests.
535 *
536 * @param objects The objects to delete.
537 * @param callback The callback method to execute when completed.
538 */
539 public static <T extends ParseObject> void deleteAllInBackground(List<T> objects, DeleteCallback callback) {
540 ParseTaskUtils.callbackOnMainThreadAsync(deleteAllInBackground(objects), callback);
541 }
542
543 /**
544 * Deletes each object in the provided list. This is faster than deleting each object individually
545 * because it batches the requests.
546 *
547 * @param objects The objects to delete.
548 * @return A {@link bolts.Task} that is resolved when deleteAll completes.
549 */
550 public static <T extends ParseObject> Task<Void> deleteAllInBackground(final List<T> objects) {
551 return ParseUser.getCurrentSessionTokenAsync().onSuccessTask(new Continuation<String, Task<Void>>() {
552 @Override
553 public Task<Void> then(Task<String> task) {
554 String sessionToken = task.getResult();
555 return deleteAllAsync(objects, sessionToken);
556 }
557 });
558 }
559
560 /**
561 * Finds all of the objects that are reachable from child, including child itself, and adds them
562 * to the given mutable array. It traverses arrays and json objects.
563 *
564 * @param node An kind object to search for children.
565 * @param dirtyChildren The array to collect the {@code ParseObject}s into.
566 * @param dirtyFiles The array to collect the {@link ParseFile}s into.
567 * @param alreadySeen The set of all objects that have already been seen.
568 * @param alreadySeenNew The set of new objects that have already been seen since the last existing object.
569 */
570 private static void collectDirtyChildren(Object node,
571 final Collection<ParseObject> dirtyChildren,
572 final Collection<ParseFile> dirtyFiles,
573 final Set<ParseObject> alreadySeen,
574 final Set<ParseObject> alreadySeenNew) {
575
576 new ParseTraverser() {
577 @Override
578 protected boolean visit(Object node) {
579 // If it's a file, then add it to the list if it's dirty.
580 if (node instanceof ParseFile) {
581 if (dirtyFiles == null) {
582 return true;
583 }
584
585 ParseFile file = (ParseFile) node;
586 if (file.getUrl() == null) {
587 dirtyFiles.add(file);
588 }
589 return true;
590 }
591
592 // If it's anything other than a file, then just continue;
593 if (!(node instanceof ParseObject)) {
594 return true;
595 }
596
597 if (dirtyChildren == null) {
598 return true;
599 }
600
601 // For files, we need to handle recursion manually to find cycles of new objects.
602 ParseObject object = (ParseObject) node;
603 Set<ParseObject> seen = alreadySeen;
604 Set<ParseObject> seenNew = alreadySeenNew;
605
606 // Check for cycles of new objects. Any such cycle means it will be
607 // impossible to save this collection of objects, so throw an exception.
608 if (object.getObjectId() != null) {
609 seenNew = new HashSet<>();
610 } else {
611 if (seenNew.contains(object)) {
612 throw new RuntimeException("Found a circular dependency while saving.");
613 }
614 seenNew = new HashSet<>(seenNew);
615 seenNew.add(object);
616 }
617
618 // Check for cycles of any object. If this occurs, then there's no
619 // problem, but we shouldn't recurse any deeper, because it would be
620 // an infinite recursion.
621 if (seen.contains(object)) {
622 return true;
623 }
624 seen = new HashSet<>(seen);
625 seen.add(object);
626
627 // Recurse into this object's children looking for dirty children.
628 // We only need to look at the child object's current estimated data,
629 // because that's the only data that might need to be saved now.
630 collectDirtyChildren(object.estimatedData, dirtyChildren, dirtyFiles, seen, seenNew);
631
632 if (object.isDirty(false)) {
633 dirtyChildren.add(object);
634 }
635
636 return true;
637 }
638 }.setYieldRoot(true).traverse(node);
639 }
640
641 //endregion
642
643 /**
644 * Helper version of collectDirtyChildren so that callers don't have to add the internally used
645 * parameters.
646 */
647 private static void collectDirtyChildren(Object node, Collection<ParseObject> dirtyChildren,
648 Collection<ParseFile> dirtyFiles) {
649 collectDirtyChildren(node, dirtyChildren, dirtyFiles,
650 new HashSet<ParseObject>(),
651 new HashSet<ParseObject>());
652 }
653
654 /**
655 * This saves all of the objects and files reachable from the given object. It does its work in
656 * multiple waves, saving as many as possible in each wave. If there's ever an error, it just
657 * gives up, sets error, and returns NO.
658 */
659 private static Task<Void> deepSaveAsync(final Object object, final String sessionToken) {
660 Set<ParseObject> objects = new HashSet<>();
661 Set<ParseFile> files = new HashSet<>();
662 collectDirtyChildren(object, objects, files);
663
664 // This has to happen separately from everything else because ParseUser.save() is
665 // special-cased to work for lazy users, but new users can't be created by
666 // ParseMultiCommand's regular save.
667 Set<ParseUser> users = new HashSet<>();
668 for (ParseObject o : objects) {
669 if (o instanceof ParseUser) {
670 ParseUser user = (ParseUser) o;
671 if (user.isLazy()) {
672 users.add((ParseUser) o);
673 }
674 }
675 }
676 objects.removeAll(users);
677
678 // objects will need to wait for files to be complete since they may be nested children.
679 final AtomicBoolean filesComplete = new AtomicBoolean(false);
680 List<Task<Void>> tasks = new ArrayList<>();
681 for (ParseFile file : files) {
682 tasks.add(file.saveAsync(sessionToken, null, null));
683 }
684 Task<Void> filesTask = Task.whenAll(tasks).continueWith(new Continuation<Void, Void>() {
685 @Override
686 public Void then(Task<Void> task) {
687 filesComplete.set(true);
688 return null;
689 }
690 });
691
692 // objects will need to wait for users to be complete since they may be nested children.
693 final AtomicBoolean usersComplete = new AtomicBoolean(false);
694 tasks = new ArrayList<>();
695 for (final ParseUser user : users) {
696 tasks.add(user.saveAsync(sessionToken));
697 }
698 Task<Void> usersTask = Task.whenAll(tasks).continueWith(new Continuation<Void, Void>() {
699 @Override
700 public Void then(Task<Void> task) {
701 usersComplete.set(true);
702 return null;
703 }
704 });
705
706 final Capture<Set<ParseObject>> remaining = new Capture<>(objects);
707 Task<Void> objectsTask = Task.forResult(null).continueWhile(new Callable<Boolean>() {
708 @Override
709 public Boolean call() {
710 return remaining.get().size() > 0;
711 }
712 }, new Continuation<Void, Task<Void>>() {
713 @Override
714 public Task<Void> then(Task<Void> task) {
715 // Partition the objects into two sets: those that can be save immediately,
716 // and those that rely on other objects to be created first.
717 final List<ParseObject> current = new ArrayList<>();
718 final Set<ParseObject> nextBatch = new HashSet<>();
719 for (ParseObject obj : remaining.get()) {
720 if (obj.canBeSerialized()) {
721 current.add(obj);
722 } else {
723 nextBatch.add(obj);
724 }
725 }
726 remaining.set(nextBatch);
727
728 if (current.size() == 0 && filesComplete.get() && usersComplete.get()) {
729 // We do cycle-detection when building the list of objects passed to this function, so
730 // this should never get called. But we should check for it anyway, so that we get an
731 // exception instead of an infinite loop.
732 throw new RuntimeException("Unable to save a ParseObject with a relation to a cycle.");
733 }
734
735 // Package all save commands together
736 if (current.size() == 0) {
737 return Task.forResult(null);
738 }
739
740 return enqueueForAll(current, new Continuation<Void, Task<Void>>() {
741 @Override
742 public Task<Void> then(Task<Void> toAwait) {
743 return saveAllAsync(current, sessionToken, toAwait);
744 }
745 });
746 }
747 });
748
749 return Task.whenAll(Arrays.asList(filesTask, usersTask, objectsTask));
750 }
751
752 private static <T extends ParseObject> Task<Void> saveAllAsync(
753 final List<T> uniqueObjects, final String sessionToken, Task<Void> toAwait) {
754 return toAwait.continueWithTask(new Continuation<Void, Task<Void>>() {
755 @Override
756 public Task<Void> then(Task<Void> task) {
757 int objectCount = uniqueObjects.size();
758 List<ParseObject.State> states = new ArrayList<>(objectCount);
759 List<ParseOperationSet> operationsList = new ArrayList<>(objectCount);
760 List<ParseDecoder> decoders = new ArrayList<>(objectCount);
761 for (int i = 0; i < objectCount; i++) {
762 ParseObject object = uniqueObjects.get(i);
763 object.updateBeforeSave();
764 object.validateSave();
765
766 states.add(object.getState());
767 operationsList.add(object.startSave());
768 final Map<String, ParseObject> fetchedObjects = object.collectFetchedObjects();
769 decoders.add(new KnownParseObjectDecoder(fetchedObjects));
770 }
771 List<Task<ParseObject.State>> batchTasks = getObjectController().saveAllAsync(
772 states, operationsList, sessionToken, decoders);
773
774 List<Task<Void>> tasks = new ArrayList<>(objectCount);
775 for (int i = 0; i < objectCount; i++) {
776 Task<ParseObject.State> batchTask = batchTasks.get(i);
777 final T object = uniqueObjects.get(i);
778 final ParseOperationSet operations = operationsList.get(i);
779 tasks.add(batchTask.continueWithTask(new Continuation<ParseObject.State, Task<Void>>() {
780 @Override
781 public Task<Void> then(final Task<ParseObject.State> batchTask) {
782 ParseObject.State result = batchTask.getResult(); // will be null on failure
783 return object.handleSaveResultAsync(result, operations).continueWithTask(new Continuation<Void, Task<Void>>() {
784 @Override
785 public Task<Void> then(Task<Void> task) {
786 if (task.isFaulted() || task.isCancelled()) {
787 return task;
788 }
789
790 // We still want to propagate batchTask errors
791 return batchTask.makeVoid();
792 }
793 });
794 }
795 }));
796 }
797 return Task.whenAll(tasks);
798 }
799 });
800 }
801
802 /**
803 * Saves each object in the provided list. This is faster than saving each object individually
804 * because it batches the requests.
805 *
806 * @param objects The objects to save.
807 * @throws ParseException Throws an exception if the server returns an error or is inaccessible.
808 */
809 public static <T extends ParseObject> void saveAll(List<T> objects) throws ParseException {
810 ParseTaskUtils.wait(saveAllInBackground(objects));
811 }
812
813 /**
814 * Saves each object in the provided list to the server in a background thread. This is preferable
815 * to using saveAll, unless your code is already running from a background thread.
816 *
817 * @param objects The objects to save.
818 * @param callback {@code callback.done(e)} is called when the save completes.
819 */
820 public static <T extends ParseObject> void saveAllInBackground(List<T> objects, SaveCallback callback) {
821 ParseTaskUtils.callbackOnMainThreadAsync(saveAllInBackground(objects), callback);
822 }
823
824 /**
825 * Saves each object in the provided list to the server in a background thread. This is preferable
826 * to using saveAll, unless your code is already running from a background thread.
827 *
828 * @param objects The objects to save.
829 * @return A {@link bolts.Task} that is resolved when saveAll completes.
830 */
831 public static <T extends ParseObject> Task<Void> saveAllInBackground(final List<T> objects) {
832 return ParseUser.getCurrentUserAsync().onSuccessTask(new Continuation<ParseUser, Task<String>>() {
833 @Override
834 public Task<String> then(Task<ParseUser> task) {
835 final ParseUser current = task.getResult();
836 if (current == null) {
837 return Task.forResult(null);
838 }
839 if (!current.isLazy()) {
840 return Task.forResult(current.getSessionToken());
841 }
842
843 // The current user is lazy/unresolved. If it is attached to any of the objects via ACL,
844 // we'll need to resolve/save it before proceeding.
845 for (ParseObject object : objects) {
846 if (!object.isDataAvailable(KEY_ACL)) {
847 continue;
848 }
849 final ParseACL acl = object.getACL(false);
850 if (acl == null) {
851 continue;
852 }
853 final ParseUser user = acl.getUnresolvedUser();
854 if (user != null && user.isCurrentUser()) {
855 // We only need to find one, since there's only one current user.
856 return user.saveAsync(null).onSuccess(new Continuation<Void, String>() {
857 @Override
858 public String then(Task<Void> task) {
859 if (acl.hasUnresolvedUser()) {
860 throw new IllegalStateException("ACL has an unresolved ParseUser. "
861 + "Save or sign up before attempting to serialize the ACL.");
862 }
863 return user.getSessionToken();
864 }
865 });
866 }
867 }
868
869 // There were no objects with ACLs pointing to unresolved users.
870 return Task.forResult(null);
871 }
872 }).onSuccessTask(new Continuation<String, Task<Void>>() {
873 @Override
874 public Task<Void> then(Task<String> task) {
875 final String sessionToken = task.getResult();
876 return deepSaveAsync(objects, sessionToken);
877 }
878 });
879 }
880
881 /**
882 * Fetches all the objects that don't have data in the provided list in the background.
883 *
884 * @param objects The list of objects to fetch.
885 * @return A {@link bolts.Task} that is resolved when fetchAllIfNeeded completes.
886 */
887 public static <T extends ParseObject> Task<List<T>> fetchAllIfNeededInBackground(
888 final List<T> objects) {
889 return fetchAllAsync(objects, true);
890 }
891
892 /**
893 * Fetches all the objects that don't have data in the provided list.
894 *
895 * @param objects The list of objects to fetch.
896 * @return The list passed in for convenience.
897 * @throws ParseException Throws an exception if the server returns an error or is inaccessible.
898 */
899 public static <T extends ParseObject> List<T> fetchAllIfNeeded(List<T> objects)
900 throws ParseException {
901 return ParseTaskUtils.wait(fetchAllIfNeededInBackground(objects));
902 }
903
904 //region LDS-processing methods.
905
906 /**
907 * Fetches all the objects that don't have data in the provided list in the background.
908 *
909 * @param objects The list of objects to fetch.
910 * @param callback {@code callback.done(result, e)} is called when the fetch completes.
911 */
912 public static <T extends ParseObject> void fetchAllIfNeededInBackground(final List<T> objects,
913 FindCallback<T> callback) {
914 ParseTaskUtils.callbackOnMainThreadAsync(fetchAllIfNeededInBackground(objects), callback);
915 }
916
917 private static <T extends ParseObject> Task<List<T>> fetchAllAsync(
918 final List<T> objects, final boolean onlyIfNeeded) {
919 return ParseUser.getCurrentUserAsync().onSuccessTask(new Continuation<ParseUser, Task<List<T>>>() {
920 @Override
921 public Task<List<T>> then(Task<ParseUser> task) {
922 final ParseUser user = task.getResult();
923 return enqueueForAll(objects, new Continuation<Void, Task<List<T>>>() {
924 @Override
925 public Task<List<T>> then(Task<Void> task) {
926 return fetchAllAsync(objects, user, onlyIfNeeded, task);
927 }
928 });
929 }
930 });
931 }
932
933 /**
934 * @param onlyIfNeeded If enabled, will only fetch if the object has an objectId and
935 * !isDataAvailable, otherwise it requires objectIds and will fetch regardless
936 * of data availability.
937 */
938 // TODO(grantland): Convert to ParseUser.State
939 private static <T extends ParseObject> Task<List<T>> fetchAllAsync(
940 final List<T> objects, final ParseUser user, final boolean onlyIfNeeded, Task<Void> toAwait) {
941 if (objects.size() == 0) {
942 return Task.forResult(objects);
943 }
944
945 List<String> objectIds = new ArrayList<>();
946 String className = null;
947 for (T object : objects) {
948 if (onlyIfNeeded && object.isDataAvailable()) {
949 continue;
950 }
951
952 if (className != null && !object.getClassName().equals(className)) {
953 throw new IllegalArgumentException("All objects should have the same class");
954 }
955 className = object.getClassName();
956
957 String objectId = object.getObjectId();
958 if (objectId != null) {
959 objectIds.add(object.getObjectId());
960 } else if (!onlyIfNeeded) {
961 throw new IllegalArgumentException("All objects must exist on the server");
962 }
963 }
964
965 if (objectIds.size() == 0) {
966 return Task.forResult(objects);
967 }
968
969 final ParseQuery<T> query = ParseQuery.<T>getQuery(className)
970 .whereContainedIn(KEY_OBJECT_ID, objectIds);
971 return toAwait.continueWithTask(new Continuation<Void, Task<List<T>>>() {
972 @Override
973 public Task<List<T>> then(Task<Void> task) {
974 return query.findAsync(query.getBuilder().build(), user, null);
975 }
976 }).onSuccess(new Continuation<List<T>, List<T>>() {
977 @Override
978 public List<T> then(Task<List<T>> task) throws Exception {
979 Map<String, T> resultMap = new HashMap<>();
980 for (T o : task.getResult()) {
981 resultMap.put(o.getObjectId(), o);
982 }
983 for (T object : objects) {
984 if (onlyIfNeeded && object.isDataAvailable()) {
985 continue;
986 }
987
988 T newObject = resultMap.get(object.getObjectId());
989 if (newObject == null) {
990 throw new ParseException(
991 ParseException.OBJECT_NOT_FOUND,
992 "Object id " + object.getObjectId() + " does not exist");
993 }
994 if (!Parse.isLocalDatastoreEnabled()) {
995 // We only need to merge if LDS is disabled, since single instance will do the merging
996 // for us.
997 object.mergeFromObject(newObject);
998 }
999 }
1000 return objects;
1001 }
1002 });
1003 }
1004
1005 //endregion
1006
1007 /**
1008 * Fetches all the objects in the provided list in the background.
1009 *
1010 * @param objects The list of objects to fetch.
1011 * @return A {@link bolts.Task} that is resolved when fetch completes.
1012 */
1013 public static <T extends ParseObject> Task<List<T>> fetchAllInBackground(final List<T> objects) {
1014 return fetchAllAsync(objects, false);
1015 }
1016
1017 /**
1018 * Fetches all the objects in the provided list.
1019 *
1020 * @param objects The list of objects to fetch.
1021 * @return The list passed in.
1022 * @throws ParseException Throws an exception if the server returns an error or is inaccessible.
1023 */
1024 public static <T extends ParseObject> List<T> fetchAll(List<T> objects) throws ParseException {
1025 return ParseTaskUtils.wait(fetchAllInBackground(objects));
1026 }
1027
1028 /**
1029 * Fetches all the objects in the provided list in the background.
1030 *
1031 * @param objects The list of objects to fetch.
1032 * @param callback {@code callback.done(result, e)} is called when the fetch completes.
1033 */
1034 public static <T extends ParseObject> void fetchAllInBackground(List<T> objects,
1035 FindCallback<T> callback) {
1036 ParseTaskUtils.callbackOnMainThreadAsync(fetchAllInBackground(objects), callback);
1037 }
1038
1039 /**
1040 * Registers the Parse-provided {@code ParseObject} subclasses. Do this here in a real method rather than
1041 * as part of a static initializer because doing this in a static initializer can lead to
1042 * deadlocks
1043 */
1044
1045 static void registerParseSubclasses() {
1046 registerSubclass(ParseUser.class);
1047 registerSubclass(ParseRole.class);
1048 registerSubclass(ParseInstallation.class);
1049 registerSubclass(ParseSession.class);
1050
1051 registerSubclass(ParsePin.class);
1052 registerSubclass(EventuallyPin.class);
1053 }
1054
1055
1056 static void unregisterParseSubclasses() {
1057 unregisterSubclass(ParseUser.class);
1058 unregisterSubclass(ParseRole.class);
1059 unregisterSubclass(ParseInstallation.class);
1060 unregisterSubclass(ParseSession.class);
1061
1062 unregisterSubclass(ParsePin.class);
1063 unregisterSubclass(EventuallyPin.class);
1064 }
1065
1066 /**
1067 * Stores the objects and every object they point to in the local datastore, recursively. If
1068 * those other objects have not been fetched from Parse, they will not be stored. However, if they
1069 * have changed data, all of the changes will be retained. To get the objects back later, you can
1070 * use {@link ParseQuery#fromLocalDatastore()}, or you can create an unfetched pointer with
1071 * {@link #createWithoutData(Class, String)} and then call {@link #fetchFromLocalDatastore()} on it.
1072 *
1073 * @param name the name
1074 * @param objects the objects to be pinned
1075 * @param callback the callback
1076 * @see #unpinAllInBackground(String, java.util.List, DeleteCallback)
1077 */
1078 public static <T extends ParseObject> void pinAllInBackground(String name,
1079 List<T> objects, SaveCallback callback) {
1080 ParseTaskUtils.callbackOnMainThreadAsync(pinAllInBackground(name, objects), callback);
1081 }
1082
1083 /**
1084 * Stores the objects and every object they point to in the local datastore, recursively. If
1085 * those other objects have not been fetched from Parse, they will not be stored. However, if they
1086 * have changed data, all of the changes will be retained. To get the objects back later, you can
1087 * use {@link ParseQuery#fromLocalDatastore()}, or you can create an unfetched pointer with
1088 * {@link #createWithoutData(Class, String)} and then call {@link #fetchFromLocalDatastore()} on it.
1089 *
1090 * @param name the name
1091 * @param objects the objects to be pinned
1092 * @return A {@link bolts.Task} that is resolved when pinning all completes.
1093 * @see #unpinAllInBackground(String, java.util.List)
1094 */
1095 public static <T extends ParseObject> Task<Void> pinAllInBackground(final String name,
1096 final List<T> objects) {
1097 return pinAllInBackground(name, objects, true);
1098 }
1099
1100 private static <T extends ParseObject> Task<Void> pinAllInBackground(final String name,
1101 final List<T> objects, final boolean includeAllChildren) {
1102 if (!Parse.isLocalDatastoreEnabled()) {
1103 throw new IllegalStateException("Method requires Local Datastore. " +
1104 "Please refer to `Parse#enableLocalDatastore(Context)`.");
1105 }
1106
1107 Task<Void> task = Task.forResult(null);
1108
1109 // Resolve and persist unresolved users attached via ACL, similarly how we do in saveAsync
1110 for (final ParseObject object : objects) {
1111 task = task.onSuccessTask(new Continuation<Void, Task<Void>>() {
1112 @Override
1113 public Task<Void> then(Task<Void> task) {
1114 if (!object.isDataAvailable(KEY_ACL)) {
1115 return Task.forResult(null);
1116 }
1117
1118 final ParseACL acl = object.getACL(false);
1119 if (acl == null) {
1120 return Task.forResult(null);
1121 }
1122
1123 ParseUser user = acl.getUnresolvedUser();
1124 if (user == null || !user.isCurrentUser()) {
1125 return Task.forResult(null);
1126 }
1127
1128 return ParseUser.pinCurrentUserIfNeededAsync(user);
1129 }
1130 });
1131 }
1132
1133 return task.onSuccessTask(new Continuation<Void, Task<Void>>() {
1134 @Override
1135 public Task<Void> then(Task<Void> task) {
1136 return Parse.getLocalDatastore().pinAllObjectsAsync(
1137 name != null ? name : DEFAULT_PIN,
1138 objects,
1139 includeAllChildren);
1140 }
1141 }).onSuccessTask(new Continuation<Void, Task<Void>>() {
1142 @Override
1143 public Task<Void> then(Task<Void> task) {
1144 // Hack to emulate persisting current user on disk after a save like in ParseUser#saveAsync
1145 // Note: This does not persist current user if it's a child object of `objects`, it probably
1146 // should, but we can't unless we do something similar to #deepSaveAsync.
1147 if (ParseCorePlugins.PIN_CURRENT_USER.equals(name)) {
1148 return task;
1149 }
1150 for (ParseObject object : objects) {
1151 if (object instanceof ParseUser) {
1152 final ParseUser user = (ParseUser) object;
1153 if (user.isCurrentUser()) {
1154 return ParseUser.pinCurrentUserIfNeededAsync(user);
1155 }
1156 }
1157 }
1158 return task;
1159 }
1160 });
1161 }
1162
1163 /**
1164 * Stores the objects and every object they point to in the local datastore, recursively. If
1165 * those other objects have not been fetched from Parse, they will not be stored. However, if they
1166 * have changed data, all of the changes will be retained. To get the objects back later, you can
1167 * use {@link ParseQuery#fromLocalDatastore()}, or you can create an unfetched pointer with
1168 * {@link #createWithoutData(Class, String)} and then call {@link #fetchFromLocalDatastore()} on it.
1169 * {@link #fetchFromLocalDatastore()} on it.
1170 *
1171 * @param name the name
1172 * @param objects the objects to be pinned
1173 * @throws ParseException exception if fails
1174 * @see #unpinAll(String, java.util.List)
1175 */
1176 public static <T extends ParseObject> void pinAll(String name,
1177 List<T> objects) throws ParseException {
1178 ParseTaskUtils.wait(pinAllInBackground(name, objects));
1179 }
1180
1181 /**
1182 * Stores the objects and every object they point to in the local datastore, recursively. If
1183 * those other objects have not been fetched from Parse, they will not be stored. However, if they
1184 * have changed data, all of the changes will be retained. To get the objects back later, you can
1185 * use {@link ParseQuery#fromLocalDatastore()}, or you can create an unfetched pointer with
1186 * {@link #createWithoutData(Class, String)} and then call {@link #fetchFromLocalDatastore()} on it.
1187 *
1188 * @param objects the objects to be pinned
1189 * @param callback the callback
1190 * @see #unpinAllInBackground(java.util.List, DeleteCallback)
1191 * @see #DEFAULT_PIN
1192 */
1193 public static <T extends ParseObject> void pinAllInBackground(List<T> objects,
1194 SaveCallback callback) {
1195 ParseTaskUtils.callbackOnMainThreadAsync(pinAllInBackground(DEFAULT_PIN, objects), callback);
1196 }
1197
1198 /**
1199 * Stores the objects and every object they point to in the local datastore, recursively. If
1200 * those other objects have not been fetched from Parse, they will not be stored. However, if they
1201 * have changed data, all of the changes will be retained. To get the objects back later, you can
1202 * use {@link ParseQuery#fromLocalDatastore()}, or you can create an unfetched pointer with
1203 * {@link #createWithoutData(Class, String)} and then call {@link #fetchFromLocalDatastore()} on it.
1204 *
1205 * @param objects the objects to be pinned
1206 * @return A {@link bolts.Task} that is resolved when pinning all completes.
1207 * @see #unpinAllInBackground(java.util.List)
1208 * @see #DEFAULT_PIN
1209 */
1210 public static <T extends ParseObject> Task<Void> pinAllInBackground(List<T> objects) {
1211 return pinAllInBackground(DEFAULT_PIN, objects);
1212 }
1213
1214 /**
1215 * Stores the objects and every object they point to in the local datastore, recursively. If
1216 * those other objects have not been fetched from Parse, they will not be stored. However, if they
1217 * have changed data, all of the changes will be retained. To get the objects back later, you can
1218 * use {@link ParseQuery#fromLocalDatastore()}, or you can create an unfetched pointer with
1219 * {@link #createWithoutData(Class, String)} and then call {@link #fetchFromLocalDatastore()} on it.
1220 *
1221 * @param objects the objects to be pinned
1222 * @throws ParseException exception if fails
1223 * @see #unpinAll(java.util.List)
1224 * @see #DEFAULT_PIN
1225 */
1226 public static <T extends ParseObject> void pinAll(List<T> objects) throws ParseException {
1227 ParseTaskUtils.wait(pinAllInBackground(DEFAULT_PIN, objects));
1228 }
1229
1230 /**
1231 * Removes the objects and every object they point to in the local datastore, recursively.
1232 *
1233 * @param name the name
1234 * @param objects the objects
1235 * @param callback the callback
1236 * @see #pinAllInBackground(String, java.util.List, SaveCallback)
1237 */
1238 public static <T extends ParseObject> void unpinAllInBackground(String name, List<T> objects,
1239 DeleteCallback callback) {
1240 ParseTaskUtils.callbackOnMainThreadAsync(unpinAllInBackground(name, objects), callback);
1241 }
1242
1243 /**
1244 * Removes the objects and every object they point to in the local datastore, recursively.
1245 *
1246 * @param name the name
1247 * @param objects the objects
1248 * @return A {@link bolts.Task} that is resolved when unpinning all completes.
1249 * @see #pinAllInBackground(String, java.util.List)
1250 */
1251 public static <T extends ParseObject> Task<Void> unpinAllInBackground(String name,
1252 List<T> objects) {
1253 if (!Parse.isLocalDatastoreEnabled()) {
1254 throw new IllegalStateException("Method requires Local Datastore. " +
1255 "Please refer to `Parse#enableLocalDatastore(Context)`.");
1256 }
1257 if (name == null) {
1258 name = DEFAULT_PIN;
1259 }
1260 return Parse.getLocalDatastore().unpinAllObjectsAsync(name, objects);
1261 }
1262
1263 /**
1264 * Removes the objects and every object they point to in the local datastore, recursively.
1265 *
1266 * @param name the name
1267 * @param objects the objects
1268 * @throws ParseException exception if fails
1269 * @see #pinAll(String, java.util.List)
1270 */
1271 public static <T extends ParseObject> void unpinAll(String name,
1272 List<T> objects) throws ParseException {
1273 ParseTaskUtils.wait(unpinAllInBackground(name, objects));
1274 }
1275
1276 /**
1277 * Removes the objects and every object they point to in the local datastore, recursively.
1278 *
1279 * @param objects the objects
1280 * @param callback the callback
1281 * @see #pinAllInBackground(java.util.List, SaveCallback)
1282 * @see #DEFAULT_PIN
1283 */
1284 public static <T extends ParseObject> void unpinAllInBackground(List<T> objects,
1285 DeleteCallback callback) {
1286 ParseTaskUtils.callbackOnMainThreadAsync(unpinAllInBackground(DEFAULT_PIN, objects), callback);
1287 }
1288
1289 /**
1290 * Removes the objects and every object they point to in the local datastore, recursively.
1291 *
1292 * @param objects the objects
1293 * @return A {@link bolts.Task} that is resolved when unpinning all completes.
1294 * @see #pinAllInBackground(java.util.List)
1295 * @see #DEFAULT_PIN
1296 */
1297 public static <T extends ParseObject> Task<Void> unpinAllInBackground(List<T> objects) {
1298 return unpinAllInBackground(DEFAULT_PIN, objects);
1299 }
1300
1301 /**
1302 * Removes the objects and every object they point to in the local datastore, recursively.
1303 *
1304 * @param objects the objects
1305 * @throws ParseException exception if fails
1306 * @see #pinAll(java.util.List)
1307 * @see #DEFAULT_PIN
1308 */
1309 public static <T extends ParseObject> void unpinAll(List<T> objects) throws ParseException {
1310 ParseTaskUtils.wait(unpinAllInBackground(DEFAULT_PIN, objects));
1311 }
1312
1313 /**
1314 * Removes the objects and every object they point to in the local datastore, recursively.
1315 *
1316 * @param name the name
1317 * @param callback the callback
1318 * @see #pinAll(String, java.util.List)
1319 */
1320 public static void unpinAllInBackground(String name, DeleteCallback callback) {
1321 ParseTaskUtils.callbackOnMainThreadAsync(unpinAllInBackground(name), callback);
1322 }
1323
1324 /**
1325 * Removes the objects and every object they point to in the local datastore, recursively.
1326 *
1327 * @param name the name
1328 * @return A {@link bolts.Task} that is resolved when unpinning all completes.
1329 * @see #pinAll(String, java.util.List)
1330 */
1331 public static Task<Void> unpinAllInBackground(String name) {
1332 if (!Parse.isLocalDatastoreEnabled()) {
1333 throw new IllegalStateException("Method requires Local Datastore. " +
1334 "Please refer to `Parse#enableLocalDatastore(Context)`.");
1335 }
1336 if (name == null) {
1337 name = DEFAULT_PIN;
1338 }
1339 return Parse.getLocalDatastore().unpinAllObjectsAsync(name);
1340 }
1341
1342 /**
1343 * Removes the objects and every object they point to in the local datastore, recursively.
1344 *
1345 * @param name the name
1346 * @throws ParseException exception if fails
1347 * @see #pinAll(String, java.util.List)
1348 */
1349 public static void unpinAll(String name) throws ParseException {
1350 ParseTaskUtils.wait(unpinAllInBackground(name));
1351 }
1352
1353 /**
1354 * Removes the objects and every object they point to in the local datastore, recursively.
1355 *
1356 * @param callback the callback
1357 * @see #pinAllInBackground(java.util.List, SaveCallback)
1358 * @see #DEFAULT_PIN
1359 */
1360 public static void unpinAllInBackground(DeleteCallback callback) {
1361 ParseTaskUtils.callbackOnMainThreadAsync(unpinAllInBackground(), callback);
1362 }
1363
1364 /**
1365 * Removes the objects and every object they point to in the local datastore, recursively.
1366 *
1367 * @return A {@link bolts.Task} that is resolved when unpinning all completes.
1368 * @see #pinAllInBackground(java.util.List, SaveCallback)
1369 * @see #DEFAULT_PIN
1370 */
1371 public static Task<Void> unpinAllInBackground() {
1372 return unpinAllInBackground(DEFAULT_PIN);
1373 }
1374
1375 /**
1376 * Removes the objects and every object they point to in the local datastore, recursively.
1377 *
1378 * @throws ParseException exception if fails
1379 * @see #pinAll(java.util.List)
1380 * @see #DEFAULT_PIN
1381 */
1382 public static void unpinAll() throws ParseException {
1383 ParseTaskUtils.wait(unpinAllInBackground());
1384 }
1385
1386
1387 static ParseObject createFromParcel(Parcel source, ParseParcelDecoder decoder) {
1388 String className = source.readString();
1389 String objectId = source.readByte() == 1 ? source.readString() : null;
1390 // Create empty object (might be the same instance if LDS is enabled)
1391 // and pass to decoder before unparceling child objects in State
1392 ParseObject object = createWithoutData(className, objectId);
1393 if (decoder instanceof ParseObjectParcelDecoder) {
1394 ((ParseObjectParcelDecoder) decoder).addKnownObject(object);
1395 }
1396 State state = State.createFromParcel(source, decoder);
1397 object.setState(state);
1398 if (source.readByte() == 1) object.localId = source.readString();
1399 if (source.readByte() == 1) object.isDeleted = true;
1400 // If object.ldsEnabledWhenParceling is true, we got this from OfflineStore.
1401 // There is no need to restore operations in that case.
1402 boolean restoreOperations = !object.ldsEnabledWhenParceling;
1403 ParseOperationSet set = ParseOperationSet.fromParcel(source, decoder);
1404 if (restoreOperations) {
1405 for (String key : set.keySet()) {
1406 ParseFieldOperation op = set.get(key);
1407 object.performOperation(key, op); // Update ops and estimatedData
1408 }
1409 }
1410 Bundle bundle = source.readBundle(ParseObject.class.getClassLoader());
1411 object.onRestoreInstanceState(bundle);
1412 return object;
1413 }
1414
1415 State.Init<?> newStateBuilder(String className) {
1416 return new State.Builder(className);
1417 }
1418
1419 State getState() {
1420 synchronized (mutex) {
1421 return state;
1422 }
1423 }
1424
1425 /**
1426 * Updates the current state of this object as well as updates our in memory cached state.
1427 *
1428 * @param newState The new state.
1429 */
1430 void setState(State newState) {
1431 synchronized (mutex) {
1432 setState(newState, true);
1433 }
1434 }
1435
1436 private void setState(State newState, boolean notifyIfObjectIdChanges) {
1437 synchronized (mutex) {
1438 String oldObjectId = state.objectId();
1439 String newObjectId = newState.objectId();
1440
1441 state = newState;
1442
1443 if (notifyIfObjectIdChanges && !ParseTextUtils.equals(oldObjectId, newObjectId)) {
1444 notifyObjectIdChanged(oldObjectId, newObjectId);
1445 }
1446
1447 rebuildEstimatedData();
1448 }
1449 }
1450
1451 /**
1452 * Accessor to the class name.
1453 */
1454 public String getClassName() {
1455 synchronized (mutex) {
1456 return state.className();
1457 }
1458 }
1459
1460 /**
1461 * This reports time as the server sees it, so that if you make changes to a {@code ParseObject}, then
1462 * wait a while, and then call {@link #save()}, the updated time will be the time of the
1463 * {@link #save()} call rather than the time the object was changed locally.
1464 *
1465 * @return The last time this object was updated on the server.
1466 */
1467 public Date getUpdatedAt() {
1468 long updatedAt = getState().updatedAt();
1469 return updatedAt > 0
1470 ? new Date(updatedAt)
1471 : null;
1472 }
1473
1474 /**
1475 * This reports time as the server sees it, so that if you create a {@code ParseObject}, then wait a
1476 * while, and then call {@link #save()}, the creation time will be the time of the first
1477 * {@link #save()} call rather than the time the object was created locally.
1478 *
1479 * @return The first time this object was saved on the server.
1480 */
1481 public Date getCreatedAt() {
1482 long createdAt = getState().createdAt();
1483 return createdAt > 0
1484 ? new Date(createdAt)
1485 : null;
1486 }
1487
1488 /**
1489 * Returns a set view of the keys contained in this object. This does not include createdAt,
1490 * updatedAt, authData, or objectId. It does include things like username and ACL.
1491 */
1492 public Set<String> keySet() {
1493 synchronized (mutex) {
1494 return Collections.unmodifiableSet(estimatedData.keySet());
1495 }
1496 }
1497
1498 /**
1499 * Copies all of the operations that have been performed on another object since its last save
1500 * onto this one.
1501 */
1502 void copyChangesFrom(ParseObject other) {
1503 synchronized (mutex) {
1504 ParseOperationSet operations = other.operationSetQueue.getFirst();
1505 for (String key : operations.keySet()) {
1506 performOperation(key, operations.get(key));
1507 }
1508 }
1509 }
1510
1511 void mergeFromObject(ParseObject other) {
1512 synchronized (mutex) {
1513 // If they point to the same instance, we don't need to merge.
1514 if (this == other) {
1515 return;
1516 }
1517
1518 State copy = other.getState().newBuilder().build();
1519
1520 // We don't want to notify if an objectId changed here since we utilize this method to merge
1521 // an anonymous current user with a new ParseUser instance that's calling signUp(). This
1522 // doesn't make any sense and we should probably remove that code in ParseUser.
1523 // Otherwise, there shouldn't be any objectId changes here since this method is only otherwise
1524 // used in fetchAll.
1525 setState(copy, false);
1526 }
1527 }
1528
1529 /**
1530 * Clears changes to this object's {@code key} made since the last call to {@link #save()} or
1531 * {@link #saveInBackground()}.
1532 *
1533 * @param key The {@code key} to revert changes for.
1534 */
1535 public void revert(String key) {
1536 synchronized (mutex) {
1537 if (isDirty(key)) {
1538 currentOperations().remove(key);
1539 rebuildEstimatedData();
1540 }
1541 }
1542 }
1543
1544 /**
1545 * Clears any changes to this object made since the last call to {@link #save()} or
1546 * {@link #saveInBackground()}.
1547 */
1548 public void revert() {
1549 synchronized (mutex) {
1550 if (isDirty()) {
1551 currentOperations().clear();
1552 rebuildEstimatedData();
1553 }
1554 }
1555 }
1556
1557 /**
1558 * Deep traversal on this object to grab a copy of any object referenced by this object. These
1559 * instances may have already been fetched, and we don't want to lose their data when refreshing
1560 * or saving.
1561 *
1562 * @return the map mapping from objectId to {@code ParseObject} which has been fetched.
1563 */
1564 private Map<String, ParseObject> collectFetchedObjects() {
1565 final Map<String, ParseObject> fetchedObjects = new HashMap<>();
1566 ParseTraverser traverser = new ParseTraverser() {
1567 @Override
1568 protected boolean visit(Object object) {
1569 if (object instanceof ParseObject) {
1570 ParseObject parseObj = (ParseObject) object;
1571 State state = parseObj.getState();
1572 if (state.objectId() != null && state.isComplete()) {
1573 fetchedObjects.put(state.objectId(), parseObj);
1574 }
1575 }
1576 return true;
1577 }
1578 };
1579 traverser.traverse(estimatedData);
1580 return fetchedObjects;
1581 }
1582
1583 /**
1584 * Helper method called by {@link #fromJSONPayload(JSONObject, ParseDecoder)}
1585 * <p>
1586 * The method helps webhooks implementation to build Parse object from raw JSON payload.
1587 * It is different from {@link #mergeFromServer(State, JSONObject, ParseDecoder, boolean)}
1588 * as the method saves the key value pairs (other than className, objectId, updatedAt and
1589 * createdAt) in the operation queue rather than the server data. It also handles
1590 * {@link ParseFieldOperations} differently.
1591 *
1592 * @param json : JSON object to be converted to Parse object
1593 * @param decoder : Decoder to be used for Decoding JSON
1594 */
1595 void build(JSONObject json, ParseDecoder decoder) {
1596 try {
1597 State.Builder builder = new State.Builder(state)
1598 .isComplete(true);
1599
1600 builder.clear();
1601
1602 Iterator<?> keys = json.keys();
1603 while (keys.hasNext()) {
1604 String key = (String) keys.next();
1605 /*
1606 __className: Used by fromJSONPayload, should be stripped out by the time it gets here...
1607 */
1608 if (key.equals(KEY_CLASS_NAME)) {
1609 continue;
1610 }
1611 if (key.equals(KEY_OBJECT_ID)) {
1612 String newObjectId = json.getString(key);
1613 builder.objectId(newObjectId);
1614 continue;
1615 }
1616 if (key.equals(KEY_CREATED_AT)) {
1617 builder.createdAt(ParseDateFormat.getInstance().parse(json.getString(key)));
1618 continue;
1619 }
1620 if (key.equals(KEY_UPDATED_AT)) {
1621 builder.updatedAt(ParseDateFormat.getInstance().parse(json.getString(key)));
1622 continue;
1623 }
1624
1625 Object value = json.get(key);
1626 Object decodedObject = decoder.decode(value);
1627 if (decodedObject instanceof ParseFieldOperation) {
1628 performOperation(key, (ParseFieldOperation) decodedObject);
1629 } else {
1630 put(key, decodedObject);
1631 }
1632 }
1633
1634 setState(builder.build());
1635 } catch (JSONException e) {
1636 throw new RuntimeException(e);
1637 }
1638 }
1639
1640 /**
1641 * Merges from JSON in REST format.
1642 * Updates this object with data from the server.
1643 *
1644 * @see #toJSONObjectForSaving(State, ParseOperationSet, ParseEncoder)
1645 */
1646 State mergeFromServer(
1647 State state, JSONObject json, ParseDecoder decoder, boolean completeData) {
1648 try {
1649 // If server data is complete, consider this object to be fetched.
1650 State.Init<?> builder = state.newBuilder();
1651 if (completeData) {
1652 builder.clear();
1653 }
1654 builder.isComplete(state.isComplete() || completeData);
1655
1656 Iterator<?> keys = json.keys();
1657 while (keys.hasNext()) {
1658 String key = (String) keys.next();
1659 /*
1660 __type: Returned by queries and cloud functions to designate body is a ParseObject
1661 __className: Used by fromJSON, should be stripped out by the time it gets here...
1662 */
1663 if (key.equals("__type") || key.equals(KEY_CLASS_NAME)) {
1664 continue;
1665 }
1666 if (key.equals(KEY_OBJECT_ID)) {
1667 String newObjectId = json.getString(key);
1668 builder.objectId(newObjectId);
1669 continue;
1670 }
1671 if (key.equals(KEY_CREATED_AT)) {
1672 builder.createdAt(ParseDateFormat.getInstance().parse(json.getString(key)));
1673 continue;
1674 }
1675 if (key.equals(KEY_UPDATED_AT)) {
1676 builder.updatedAt(ParseDateFormat.getInstance().parse(json.getString(key)));
1677 continue;
1678 }
1679 if (key.equals(KEY_ACL)) {
1680 ParseACL acl = ParseACL.createACLFromJSONObject(json.getJSONObject(key), decoder);
1681 builder.put(KEY_ACL, acl);
1682 continue;
1683 }
1684 if (key.equals(KEY_SELECTED_KEYS)) {
1685 JSONArray safeKeys = json.getJSONArray(key);
1686 if (safeKeys.length() > 0) {
1687 Collection<String> set = new HashSet<>();
1688 for (int i = 0; i < safeKeys.length(); i++) {
1689 // Don't add nested keys.
1690 String safeKey = safeKeys.getString(i);
1691 if (safeKey.contains(".")) safeKey = safeKey.split("\\.")[0];
1692 set.add(safeKey);
1693 }
1694 builder.availableKeys(set);
1695 }
1696 continue;
1697 }
1698
1699 Object value = json.get(key);
1700 if (value instanceof JSONObject && json.has(KEY_SELECTED_KEYS)) {
1701 // This might be a ParseObject. Pass selected keys to understand if it is complete.
1702 JSONArray selectedKeys = json.getJSONArray(KEY_SELECTED_KEYS);
1703 JSONArray nestedKeys = new JSONArray();
1704 for (int i = 0; i < selectedKeys.length(); i++) {
1705 String nestedKey = selectedKeys.getString(i);
1706 if (nestedKey.startsWith(key + "."))
1707 nestedKeys.put(nestedKey.substring(key.length() + 1));
1708 }
1709 if (nestedKeys.length() > 0) {
1710 ((JSONObject) value).put(KEY_SELECTED_KEYS, nestedKeys);
1711 }
1712 }
1713 Object decodedObject = decoder.decode(value);
1714 builder.put(key, decodedObject);
1715 }
1716
1717 return builder.build();
1718 } catch (JSONException e) {
1719 throw new RuntimeException(e);
1720 }
1721 }
1722
1723 /**
1724 * Convert to REST JSON for persisting in LDS.
1725 *
1726 * @see #mergeREST(State, org.json.JSONObject, ParseDecoder)
1727 */
1728 JSONObject toRest(ParseEncoder encoder) {
1729 State state;
1730 List<ParseOperationSet> operationSetQueueCopy;
1731 synchronized (mutex) {
1732 // mutex needed to lock access to state and operationSetQueue and operationSetQueue & children
1733 // are mutable
1734 state = getState();
1735
1736 // operationSetQueue is a List of Lists, so we'll need custom copying logic
1737 int operationSetQueueSize = operationSetQueue.size();
1738 operationSetQueueCopy = new ArrayList<>(operationSetQueueSize);
1739 for (int i = 0; i < operationSetQueueSize; i++) {
1740 ParseOperationSet original = operationSetQueue.get(i);
1741 ParseOperationSet copy = new ParseOperationSet(original);
1742 operationSetQueueCopy.add(copy);
1743 }
1744 }
1745 return toRest(state, operationSetQueueCopy, encoder);
1746 }
1747
1748 JSONObject toRest(
1749 State state, List<ParseOperationSet> operationSetQueue, ParseEncoder objectEncoder) {
1750 // Public data goes in dataJSON; special fields go in objectJSON.
1751 JSONObject json = new JSONObject();
1752
1753 try {
1754 // REST JSON (State)
1755 json.put(KEY_CLASS_NAME, state.className());
1756 if (state.objectId() != null) {
1757 json.put(KEY_OBJECT_ID, state.objectId());
1758 }
1759 if (state.createdAt() > 0) {
1760 json.put(KEY_CREATED_AT,
1761 ParseDateFormat.getInstance().format(new Date(state.createdAt())));
1762 }
1763 if (state.updatedAt() > 0) {
1764 json.put(KEY_UPDATED_AT,
1765 ParseDateFormat.getInstance().format(new Date(state.updatedAt())));
1766 }
1767 for (String key : state.keySet()) {
1768 Object value = state.get(key);
1769 json.put(key, objectEncoder.encode(value));
1770 }
1771
1772 // Internal JSON
1773 //TODO(klimt): We'll need to rip all this stuff out and put it somewhere else if we start
1774 // using the REST api and want to send data to Parse.
1775 json.put(KEY_COMPLETE, state.isComplete());
1776 json.put(KEY_IS_DELETING_EVENTUALLY, isDeletingEventually);
1777 JSONArray availableKeys = new JSONArray(state.availableKeys());
1778 json.put(KEY_SELECTED_KEYS, availableKeys);
1779
1780 // Operation Set Queue
1781 JSONArray operations = new JSONArray();
1782 for (ParseOperationSet operationSet : operationSetQueue) {
1783 operations.put(operationSet.toRest(objectEncoder));
1784 }
1785 json.put(KEY_OPERATIONS, operations);
1786
1787 } catch (JSONException e) {
1788 throw new RuntimeException("could not serialize object to JSON");
1789 }
1790
1791 return json;
1792 }
1793
1794 /**
1795 * Merge with REST JSON from LDS.
1796 *
1797 * @see #toRest(ParseEncoder)
1798 */
1799 void mergeREST(State state, JSONObject json, ParseDecoder decoder) {
1800 ArrayList<ParseOperationSet> saveEventuallyOperationSets = new ArrayList<>();
1801
1802 synchronized (mutex) {
1803 try {
1804 boolean isComplete = json.getBoolean(KEY_COMPLETE);
1805 isDeletingEventually = ParseJSONUtils.getInt(json, Arrays.asList(
1806 KEY_IS_DELETING_EVENTUALLY,
1807 KEY_IS_DELETING_EVENTUALLY_OLD
1808 ));
1809 JSONArray operations = json.getJSONArray(KEY_OPERATIONS);
1810 {
1811 ParseOperationSet newerOperations = currentOperations();
1812 operationSetQueue.clear();
1813
1814 // Add and enqueue any saveEventually operations, roll forward any other operation sets
1815 // (operation sets here are generally failed/incomplete saves).
1816 ParseOperationSet current = null;
1817 for (int i = 0; i < operations.length(); i++) {
1818 JSONObject operationSetJSON = operations.getJSONObject(i);
1819 ParseOperationSet operationSet = ParseOperationSet.fromRest(operationSetJSON, decoder);
1820
1821 if (operationSet.isSaveEventually()) {
1822 if (current != null) {
1823 operationSetQueue.add(current);
1824 current = null;
1825 }
1826 saveEventuallyOperationSets.add(operationSet);
1827 operationSetQueue.add(operationSet);
1828 continue;
1829 }
1830
1831 if (current != null) {
1832 operationSet.mergeFrom(current);
1833 }
1834 current = operationSet;
1835 }
1836 if (current != null) {
1837 operationSetQueue.add(current);
1838 }
1839
1840 // Merge the changes that were previously in memory into the updated object.
1841 currentOperations().mergeFrom(newerOperations);
1842 }
1843
1844 // We only want to merge server data if we our updatedAt is null (we're unsaved or from
1845 // #createWithoutData) or if the JSON's updatedAt is newer than ours.
1846 boolean mergeServerData = false;
1847 if (state.updatedAt() < 0) {
1848 mergeServerData = true;
1849 } else if (json.has(KEY_UPDATED_AT)) {
1850 Date otherUpdatedAt = ParseDateFormat.getInstance().parse(json.getString(KEY_UPDATED_AT));
1851 if (new Date(state.updatedAt()).compareTo(otherUpdatedAt) < 0) {
1852 mergeServerData = true;
1853 }
1854 }
1855
1856 if (mergeServerData) {
1857 // Clean up internal json keys
1858 JSONObject mergeJSON = ParseJSONUtils.create(json, Arrays.asList(
1859 KEY_COMPLETE, KEY_IS_DELETING_EVENTUALLY, KEY_IS_DELETING_EVENTUALLY_OLD,
1860 KEY_OPERATIONS
1861 ));
1862 State newState = mergeFromServer(state, mergeJSON, decoder, isComplete);
1863 setState(newState);
1864 }
1865 } catch (JSONException e) {
1866 throw new RuntimeException(e);
1867 }
1868 }
1869
1870 // We cannot modify the taskQueue inside synchronized (mutex).
1871 for (ParseOperationSet operationSet : saveEventuallyOperationSets) {
1872 enqueueSaveEventuallyOperationAsync(operationSet);
1873 }
1874 }
1875
1876 private boolean hasDirtyChildren() {
1877 synchronized (mutex) {
1878 // We only need to consider the currently estimated children here,
1879 // because they're the only ones that might need to be saved in a
1880 // subsequent call to save, which is the meaning of "dirtiness".
1881 List<ParseObject> unsavedChildren = new ArrayList<>();
1882 collectDirtyChildren(estimatedData, unsavedChildren, null);
1883 return unsavedChildren.size() > 0;
1884 }
1885 }
1886
1887 /**
1888 * Whether any key-value pair in this object (or its children) has been added/updated/removed and
1889 * not saved yet.
1890 *
1891 * @return Whether this object has been altered and not saved yet.
1892 */
1893 public boolean isDirty() {
1894 return this.isDirty(true);
1895 }
1896
1897 boolean isDirty(boolean considerChildren) {
1898 synchronized (mutex) {
1899 return (isDeleted || getObjectId() == null || hasChanges() || (considerChildren && hasDirtyChildren()));
1900 }
1901 }
1902
1903 boolean hasChanges() {
1904 synchronized (mutex) {
1905 return currentOperations().size() > 0;
1906 }
1907 }
1908
1909 /**
1910 * Returns {@code true} if this {@code ParseObject} has operations in operationSetQueue that
1911 * haven't been completed yet, {@code false} if there are no operations in the operationSetQueue.
1912 */
1913 boolean hasOutstandingOperations() {
1914 synchronized (mutex) {
1915 // > 1 since 1 is for unsaved changes.
1916 return operationSetQueue.size() > 1;
1917 }
1918 }
1919
1920 /**
1921 * Whether a value associated with a key has been added/updated/removed and not saved yet.
1922 *
1923 * @param key The key to check for
1924 * @return Whether this key has been altered and not saved yet.
1925 */
1926 public boolean isDirty(String key) {
1927 synchronized (mutex) {
1928 return currentOperations().containsKey(key);
1929 }
1930 }
1931
1932 /**
1933 * Accessor to the object id. An object id is assigned as soon as an object is saved to the
1934 * server. The combination of a className and an objectId uniquely identifies an object in your
1935 * application.
1936 *
1937 * @return The object id.
1938 */
1939 public String getObjectId() {
1940 synchronized (mutex) {
1941 return state.objectId();
1942 }
1943 }
1944
1945 /**
1946 * Setter for the object id. In general you do not need to use this. However, in some cases this
1947 * can be convenient. For example, if you are serializing a {@code ParseObject} yourself and wish
1948 * to recreate it, you can use this to recreate the {@code ParseObject} exactly.
1949 */
1950 public void setObjectId(String newObjectId) {
1951 synchronized (mutex) {
1952 String oldObjectId = state.objectId();
1953 if (ParseTextUtils.equals(oldObjectId, newObjectId)) {
1954 return;
1955 }
1956
1957 // We don't need to use setState since it doesn't affect our cached state.
1958 state = state.newBuilder().objectId(newObjectId).build();
1959 notifyObjectIdChanged(oldObjectId, newObjectId);
1960 }
1961 }
1962
1963 /**
1964 * Returns the localId, which is used internally for serializing relations to objects that don't
1965 * yet have an objectId.
1966 */
1967 String getOrCreateLocalId() {
1968 synchronized (mutex) {
1969 if (localId == null) {
1970 if (state.objectId() != null) {
1971 throw new IllegalStateException(
1972 "Attempted to get a localId for an object with an objectId.");
1973 }
1974 localId = getLocalIdManager().createLocalId();
1975 }
1976 return localId;
1977 }
1978 }
1979
1980 // Sets the objectId without marking dirty.
1981 private void notifyObjectIdChanged(String oldObjectId, String newObjectId) {
1982 synchronized (mutex) {
1983 // The offline store may throw if this object already had a different objectId.
1984 OfflineStore store = Parse.getLocalDatastore();
1985 if (store != null) {
1986 store.updateObjectId(this, oldObjectId, newObjectId);
1987 }
1988
1989 if (localId != null) {
1990 getLocalIdManager().setObjectId(localId, newObjectId);
1991 localId = null;
1992 }
1993 }
1994 }
1995
1996 private ParseRESTObjectCommand currentSaveEventuallyCommand(
1997 ParseOperationSet operations, ParseEncoder objectEncoder, String sessionToken) {
1998 State state = getState();
1999
2000 /*
2001 * Get the JSON representation of the object, and use some of the information to construct the
2002 * command.
2003 */
2004 JSONObject objectJSON = toJSONObjectForSaving(state, operations, objectEncoder);
2005
2006 return ParseRESTObjectCommand.saveObjectCommand(
2007 state,
2008 objectJSON,
2009 sessionToken);
2010 }
2011
2012 /**
2013 * Converts a {@code ParseObject} to a JSON representation for saving to Parse.
2014 * <p>
2015 * <pre>
2016 * {
2017 * data: { // objectId plus any ParseFieldOperations },
2018 * classname: class name for the object
2019 * }
2020 * </pre>
2021 * <p>
2022 * updatedAt and createdAt are not included. only dirty keys are represented in the data.
2023 *
2024 * @see #mergeFromServer(State state, org.json.JSONObject, ParseDecoder, boolean)
2025 */
2026 // Currently only used by saveEventually
2027 <T extends State> JSONObject toJSONObjectForSaving(
2028 T state, ParseOperationSet operations, ParseEncoder objectEncoder) {
2029 JSONObject objectJSON = new JSONObject();
2030
2031 try {
2032 // Serialize the data
2033 for (String key : operations.keySet()) {
2034 ParseFieldOperation operation = operations.get(key);
2035 objectJSON.put(key, objectEncoder.encode(operation));
2036
2037 // TODO(grantland): Use cached value from hashedObjects if it's a set operation.
2038 }
2039
2040 if (state.objectId() != null) {
2041 objectJSON.put(KEY_OBJECT_ID, state.objectId());
2042 }
2043 } catch (JSONException e) {
2044 throw new RuntimeException("could not serialize object to JSON");
2045 }
2046
2047 return objectJSON;
2048 }
2049
2050 /**
2051 * Handles the result of {@code save}.
2052 * <p>
2053 * Should be called on success or failure.
2054 */
2055 // TODO(grantland): Remove once we convert saveEventually and ParseUser.signUp/resolveLaziness
2056 // to controllers
2057 Task<Void> handleSaveResultAsync(
2058 final JSONObject result, final ParseOperationSet operationsBeforeSave) {
2059 ParseObject.State newState = null;
2060
2061 if (result != null) { // Success
2062 synchronized (mutex) {
2063 final Map<String, ParseObject> fetchedObjects = collectFetchedObjects();
2064 ParseDecoder decoder = new KnownParseObjectDecoder(fetchedObjects);
2065 newState = ParseObjectCoder.get().decode(getState().newBuilder().clear(), result, decoder)
2066 .isComplete(false)
2067 .build();
2068 }
2069 }
2070
2071 return handleSaveResultAsync(newState, operationsBeforeSave);
2072 }
2073
2074 /**
2075 * Handles the result of {@code save}.
2076 * <p>
2077 * Should be called on success or failure.
2078 */
2079 Task<Void> handleSaveResultAsync(
2080 final ParseObject.State result, final ParseOperationSet operationsBeforeSave) {
2081 Task<Void> task = Task.forResult(null);
2082
2083 /*
2084 * If this object is in the offline store, then we need to make sure that we pull in any dirty
2085 * changes it may have before merging the server data into it.
2086 */
2087 final OfflineStore store = Parse.getLocalDatastore();
2088 if (store != null) {
2089 task = task.onSuccessTask(new Continuation<Void, Task<Void>>() {
2090 @Override
2091 public Task<Void> then(Task<Void> task) {
2092 return store.fetchLocallyAsync(ParseObject.this).makeVoid();
2093 }
2094 });
2095 }
2096
2097 final boolean success = result != null;
2098 synchronized (mutex) {
2099 // Find operationsBeforeSave in the queue so that we can remove it and move to the next
2100 // operation set.
2101 ListIterator<ParseOperationSet> opIterator =
2102 operationSetQueue.listIterator(operationSetQueue.indexOf(operationsBeforeSave));
2103 opIterator.next();
2104 opIterator.remove();
2105
2106 if (!success) {
2107 // Merge the data from the failed save into the next save.
2108 ParseOperationSet nextOperation = opIterator.next();
2109 nextOperation.mergeFrom(operationsBeforeSave);
2110 if (store != null) {
2111 task = task.continueWithTask(new Continuation<Void, Task<Void>>() {
2112 @Override
2113 public Task<Void> then(Task<Void> task) {
2114 if (task.isFaulted()) {
2115 return Task.forResult(null);
2116 } else {
2117 return store.updateDataForObjectAsync(ParseObject.this);
2118 }
2119 }
2120 });
2121 }
2122 return task;
2123 }
2124 }
2125
2126 // fetchLocallyAsync will return an error if this object isn't in the LDS yet and that's ok
2127 task = task.continueWith(new Continuation<Void, Void>() {
2128 @Override
2129 public Void then(Task<Void> task) {
2130 synchronized (mutex) {
2131 State newState;
2132 if (result.isComplete()) {
2133 // Result is complete, so just replace
2134 newState = result;
2135 } else {
2136 // Result is incomplete, so we'll need to apply it to the current state
2137 newState = getState().newBuilder()
2138 .apply(operationsBeforeSave)
2139 .apply(result)
2140 .build();
2141 }
2142 setState(newState);
2143 }
2144 return null;
2145 }
2146 });
2147
2148 if (store != null) {
2149 task = task.onSuccessTask(new Continuation<Void, Task<Void>>() {
2150 @Override
2151 public Task<Void> then(Task<Void> task) {
2152 return store.updateDataForObjectAsync(ParseObject.this);
2153 }
2154 });
2155 }
2156
2157 task = task.onSuccess(new Continuation<Void, Void>() {
2158 @Override
2159 public Void then(Task<Void> task) {
2160 saveEvent.invoke(ParseObject.this, null);
2161 return null;
2162 }
2163 });
2164
2165 return task;
2166 }
2167
2168 ParseOperationSet startSave() {
2169 synchronized (mutex) {
2170 ParseOperationSet currentOperations = currentOperations();
2171 operationSetQueue.addLast(new ParseOperationSet());
2172 return currentOperations;
2173 }
2174 }
2175
2176 void validateSave() {
2177 // do nothing
2178 }
2179
2180 /**
2181 * Saves this object to the server. Typically, you should use {@link #saveInBackground} instead of
2182 * this, unless you are managing your own threading.
2183 *
2184 * @throws ParseException Throws an exception if the server is inaccessible.
2185 */
2186 public final void save() throws ParseException {
2187 ParseTaskUtils.wait(saveInBackground());
2188 }
2189
2190 /**
2191 * Saves this object to the server in a background thread. This is preferable to using {@link #save()},
2192 * unless your code is already running from a background thread.
2193 *
2194 * @return A {@link bolts.Task} that is resolved when the save completes.
2195 */
2196 public final Task<Void> saveInBackground() {
2197 return ParseUser.getCurrentUserAsync().onSuccessTask(new Continuation<ParseUser, Task<String>>() {
2198 @Override
2199 public Task<String> then(Task<ParseUser> task) {
2200 final ParseUser current = task.getResult();
2201 if (current == null) {
2202 return Task.forResult(null);
2203 }
2204 if (!current.isLazy()) {
2205 return Task.forResult(current.getSessionToken());
2206 }
2207
2208 // The current user is lazy/unresolved. If it is attached to us via ACL, we'll need to
2209 // resolve/save it before proceeding.
2210 if (!isDataAvailable(KEY_ACL)) {
2211 return Task.forResult(null);
2212 }
2213 final ParseACL acl = getACL(false);
2214 if (acl == null) {
2215 return Task.forResult(null);
2216 }
2217 final ParseUser user = acl.getUnresolvedUser();
2218 if (user == null || !user.isCurrentUser()) {
2219 return Task.forResult(null);
2220 }
2221 return user.saveAsync(null).onSuccess(new Continuation<Void, String>() {
2222 @Override
2223 public String then(Task<Void> task) {
2224 if (acl.hasUnresolvedUser()) {
2225 throw new IllegalStateException("ACL has an unresolved ParseUser. "
2226 + "Save or sign up before attempting to serialize the ACL.");
2227 }
2228 return user.getSessionToken();
2229 }
2230 });
2231 }
2232 }).onSuccessTask(new Continuation<String, Task<Void>>() {
2233 @Override
2234 public Task<Void> then(Task<String> task) {
2235 final String sessionToken = task.getResult();
2236 return saveAsync(sessionToken);
2237 }
2238 });
2239 }
2240
2241 Task<Void> saveAsync(final String sessionToken) {
2242 return taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
2243 @Override
2244 public Task<Void> then(Task<Void> toAwait) {
2245 return saveAsync(sessionToken, toAwait);
2246 }
2247 });
2248 }
2249
2250 Task<Void> saveAsync(final String sessionToken, final Task<Void> toAwait) {
2251 if (!isDirty()) {
2252 return Task.forResult(null);
2253 }
2254
2255 final ParseOperationSet operations;
2256 synchronized (mutex) {
2257 updateBeforeSave();
2258 validateSave();
2259 operations = startSave();
2260 }
2261
2262 Task<Void> task;
2263 synchronized (mutex) {
2264 // Recursively save children
2265
2266 /*
2267 * TODO(klimt): Why is this estimatedData and not... I mean, what if a child is
2268 * removed after save is called, but before the unresolved user gets resolved? It
2269 * won't get saved.
2270 */
2271 task = deepSaveAsync(estimatedData, sessionToken);
2272 }
2273
2274 return task.onSuccessTask(
2275 TaskQueue.<Void>waitFor(toAwait)
2276 ).onSuccessTask(new Continuation<Void, Task<ParseObject.State>>() {
2277 @Override
2278 public Task<ParseObject.State> then(Task<Void> task) {
2279 final Map<String, ParseObject> fetchedObjects = collectFetchedObjects();
2280 ParseDecoder decoder = new KnownParseObjectDecoder(fetchedObjects);
2281 return getObjectController().saveAsync(getState(), operations, sessionToken, decoder);
2282 }
2283 }).continueWithTask(new Continuation<ParseObject.State, Task<Void>>() {
2284 @Override
2285 public Task<Void> then(final Task<ParseObject.State> saveTask) {
2286 ParseObject.State result = saveTask.getResult();
2287 return handleSaveResultAsync(result, operations).continueWithTask(new Continuation<Void, Task<Void>>() {
2288 @Override
2289 public Task<Void> then(Task<Void> task) {
2290 if (task.isFaulted() || task.isCancelled()) {
2291 return task;
2292 }
2293
2294 // We still want to propagate saveTask errors
2295 return saveTask.makeVoid();
2296 }
2297 });
2298 }
2299 });
2300 }
2301
2302 // Currently only used by ParsePinningEventuallyQueue for saveEventually due to the limitation in
2303 // ParseCommandCache that it can only return JSONObject result.
2304 Task<JSONObject> saveAsync(
2305 ParseHttpClient client,
2306 final ParseOperationSet operationSet,
2307 String sessionToken) {
2308 final ParseRESTCommand command =
2309 currentSaveEventuallyCommand(operationSet, PointerEncoder.get(), sessionToken);
2310 return command.executeAsync(client);
2311 }
2312
2313 /**
2314 * Saves this object to the server in a background thread. This is preferable to using {@link #save()},
2315 * unless your code is already running from a background thread.
2316 *
2317 * @param callback {@code callback.done(e)} is called when the save completes.
2318 */
2319 public final void saveInBackground(SaveCallback callback) {
2320 ParseTaskUtils.callbackOnMainThreadAsync(saveInBackground(), callback);
2321 }
2322
2323 void validateSaveEventually() throws ParseException {
2324 // do nothing
2325 }
2326
2327 /**
2328 * Saves this object to the server at some unspecified time in the future, even if Parse is
2329 * currently inaccessible. Use this when you may not have a solid network connection, and don't
2330 * need to know when the save completes. If there is some problem with the object such that it
2331 * can't be saved, it will be silently discarded. Objects saved with this method will be stored
2332 * locally in an on-disk cache until they can be delivered to Parse. They will be sent immediately
2333 * if possible. Otherwise, they will be sent the next time a network connection is available.
2334 * Objects saved this way will persist even after the app is closed, in which case they will be
2335 * sent the next time the app is opened. If more than 10MB of data is waiting to be sent,
2336 * subsequent calls to {@code #saveEventually()} or {@link #deleteEventually()} will cause old
2337 * saves to be silently discarded until the connection can be re-established, and the queued
2338 * objects can be saved.
2339 *
2340 * @param callback - A callback which will be called if the save completes before the app exits.
2341 */
2342 public final void saveEventually(SaveCallback callback) {
2343 ParseTaskUtils.callbackOnMainThreadAsync(saveEventually(), callback);
2344 }
2345
2346 /**
2347 * Saves this object to the server at some unspecified time in the future, even if Parse is
2348 * currently inaccessible. Use this when you may not have a solid network connection, and don't
2349 * need to know when the save completes. If there is some problem with the object such that it
2350 * can't be saved, it will be silently discarded. Objects saved with this method will be stored
2351 * locally in an on-disk cache until they can be delivered to Parse. They will be sent immediately
2352 * if possible. Otherwise, they will be sent the next time a network connection is available.
2353 * Objects saved this way will persist even after the app is closed, in which case they will be
2354 * sent the next time the app is opened. If more than 10MB of data is waiting to be sent,
2355 * subsequent calls to {@code #saveEventually()} or {@link #deleteEventually()} will cause old
2356 * saves to be silently discarded until the connection can be re-established, and the queued
2357 * objects can be saved.
2358 *
2359 * @return A {@link bolts.Task} that is resolved when the save completes.
2360 */
2361 public final Task<Void> saveEventually() {
2362 if (!isDirty()) {
2363 Parse.getEventuallyQueue().fakeObjectUpdate();
2364 return Task.forResult(null);
2365 }
2366
2367 final ParseOperationSet operationSet;
2368 final ParseRESTCommand command;
2369 final Task<JSONObject> runEventuallyTask;
2370
2371 synchronized (mutex) {
2372 updateBeforeSave();
2373 try {
2374 validateSaveEventually();
2375 } catch (ParseException e) {
2376 return Task.forError(e);
2377 }
2378
2379 // TODO(klimt): Once we allow multiple saves on an object, this
2380 // should be collecting dirty children from the estimate based on
2381 // whatever data is going to be sent by this saveEventually, which
2382 // won't necessarily be the current estimatedData. We should resolve
2383 // this when the multiple save code is added.
2384 List<ParseObject> unsavedChildren = new ArrayList<>();
2385 collectDirtyChildren(estimatedData, unsavedChildren, null);
2386
2387 String localId = null;
2388 if (getObjectId() == null) {
2389 localId = getOrCreateLocalId();
2390 }
2391
2392 operationSet = startSave();
2393 operationSet.setIsSaveEventually(true);
2394
2395 //TODO (grantland): Convert to async
2396 final String sessionToken = ParseUser.getCurrentSessionToken();
2397
2398 // See [1]
2399 command = currentSaveEventuallyCommand(operationSet, PointerOrLocalIdEncoder.get(),
2400 sessionToken);
2401
2402 // TODO: Make this logic make sense once we have deepSaveEventually
2403 command.setLocalId(localId);
2404
2405 // Mark the command with a UUID so that we can match it up later.
2406 command.setOperationSetUUID(operationSet.getUUID());
2407
2408 // Ensure local ids are retained before saveEventually-ing children
2409 command.retainLocalIds();
2410
2411 for (ParseObject object : unsavedChildren) {
2412 object.saveEventually();
2413 }
2414
2415 }
2416
2417 // We cannot modify the taskQueue inside synchronized (mutex).
2418 ParseEventuallyQueue cache = Parse.getEventuallyQueue();
2419 runEventuallyTask = cache.enqueueEventuallyAsync(command, ParseObject.this);
2420 enqueueSaveEventuallyOperationAsync(operationSet);
2421
2422 // Release the extra retained local ids.
2423 command.releaseLocalIds();
2424
2425 Task<Void> handleSaveResultTask;
2426 if (Parse.isLocalDatastoreEnabled()) {
2427 // ParsePinningEventuallyQueue calls handleSaveEventuallyResultAsync directly.
2428 handleSaveResultTask = runEventuallyTask.makeVoid();
2429 } else {
2430 handleSaveResultTask = runEventuallyTask.onSuccessTask(new Continuation<JSONObject, Task<Void>>() {
2431 @Override
2432 public Task<Void> then(Task<JSONObject> task) {
2433 JSONObject json = task.getResult();
2434 return handleSaveEventuallyResultAsync(json, operationSet);
2435 }
2436 });
2437 }
2438 return handleSaveResultTask;
2439 }
2440
2441 /**
2442 * Enqueues the saveEventually ParseOperationSet in {@link #taskQueue}.
2443 */
2444 private void enqueueSaveEventuallyOperationAsync(final ParseOperationSet operationSet) {
2445 if (!operationSet.isSaveEventually()) {
2446 throw new IllegalStateException(
2447 "This should only be used to enqueue saveEventually operation sets");
2448 }
2449
2450 taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
2451 @Override
2452 public Task<Void> then(Task<Void> toAwait) {
2453 return toAwait.continueWithTask(new Continuation<Void, Task<Void>>() {
2454 @Override
2455 public Task<Void> then(Task<Void> task) {
2456 ParseEventuallyQueue cache = Parse.getEventuallyQueue();
2457 return cache.waitForOperationSetAndEventuallyPin(operationSet, null).makeVoid();
2458 }
2459 });
2460 }
2461 });
2462 }
2463
2464 /**
2465 * Handles the result of {@code saveEventually}.
2466 * <p>
2467 * In addition to normal save handling, this also notifies the saveEventually test helper.
2468 * <p>
2469 * Should be called on success or failure.
2470 */
2471 Task<Void> handleSaveEventuallyResultAsync(
2472 JSONObject json, ParseOperationSet operationSet) {
2473 final boolean success = json != null;
2474 Task<Void> handleSaveResultTask = handleSaveResultAsync(json, operationSet);
2475
2476 return handleSaveResultTask.onSuccessTask(new Continuation<Void, Task<Void>>() {
2477 @Override
2478 public Task<Void> then(Task<Void> task) {
2479 if (success) {
2480 Parse.getEventuallyQueue()
2481 .notifyTestHelper(ParseCommandCache.TestHelper.OBJECT_UPDATED);
2482 }
2483 return task;
2484 }
2485 });
2486 }
2487
2488 /**
2489 * Called by {@link #saveInBackground()} and {@link #saveEventually(SaveCallback)}
2490 * and guaranteed to be thread-safe. Subclasses can override this method to do any custom updates
2491 * before an object gets saved.
2492 */
2493 void updateBeforeSave() {
2494 // do nothing
2495 }
2496
2497 /**
2498 * Deletes this object from the server at some unspecified time in the future, even if Parse is
2499 * currently inaccessible. Use this when you may not have a solid network connection, and don't
2500 * need to know when the delete completes. If there is some problem with the object such that it
2501 * can't be deleted, the request will be silently discarded. Delete requests made with this method
2502 * will be stored locally in an on-disk cache until they can be transmitted to Parse. They will be
2503 * sent immediately if possible. Otherwise, they will be sent the next time a network connection
2504 * is available. Delete instructions saved this way will persist even after the app is closed, in
2505 * which case they will be sent the next time the app is opened. If more than 10MB of commands are
2506 * waiting to be sent, subsequent calls to {@code #deleteEventually()} or
2507 * {@link #saveEventually()} will cause old instructions to be silently discarded until the
2508 * connection can be re-established, and the queued objects can be saved.
2509 *
2510 * @param callback - A callback which will be called if the delete completes before the app exits.
2511 */
2512 public final void deleteEventually(DeleteCallback callback) {
2513 ParseTaskUtils.callbackOnMainThreadAsync(deleteEventually(), callback);
2514 }
2515
2516 /**
2517 * Deletes this object from the server at some unspecified time in the future, even if Parse is
2518 * currently inaccessible. Use this when you may not have a solid network connection, and don't
2519 * need to know when the delete completes. If there is some problem with the object such that it
2520 * can't be deleted, the request will be silently discarded. Delete requests made with this method
2521 * will be stored locally in an on-disk cache until they can be transmitted to Parse. They will be
2522 * sent immediately if possible. Otherwise, they will be sent the next time a network connection
2523 * is available. Delete instructions saved this way will persist even after the app is closed, in
2524 * which case they will be sent the next time the app is opened. If more than 10MB of commands are
2525 * waiting to be sent, subsequent calls to {@code #deleteEventually()} or
2526 * {@link #saveEventually()} will cause old instructions to be silently discarded until the
2527 * connection can be re-established, and the queued objects can be saved.
2528 *
2529 * @return A {@link bolts.Task} that is resolved when the delete completes.
2530 */
2531 public final Task<Void> deleteEventually() {
2532 final ParseRESTCommand command;
2533 final Task<JSONObject> runEventuallyTask;
2534 synchronized (mutex) {
2535 validateDelete();
2536 isDeletingEventually += 1;
2537
2538 String localId = null;
2539 if (getObjectId() == null) {
2540 localId = getOrCreateLocalId();
2541 }
2542
2543 // TODO(grantland): Convert to async
2544 final String sessionToken = ParseUser.getCurrentSessionToken();
2545
2546 // See [1]
2547 command = ParseRESTObjectCommand.deleteObjectCommand(
2548 getState(), sessionToken);
2549 command.setLocalId(localId);
2550
2551 runEventuallyTask = Parse.getEventuallyQueue().enqueueEventuallyAsync(command, ParseObject.this);
2552 }
2553
2554 Task<Void> handleDeleteResultTask;
2555 if (Parse.isLocalDatastoreEnabled()) {
2556 // ParsePinningEventuallyQueue calls handleDeleteEventuallyResultAsync directly.
2557 handleDeleteResultTask = runEventuallyTask.makeVoid();
2558 } else {
2559 handleDeleteResultTask = runEventuallyTask.onSuccessTask(new Continuation<JSONObject, Task<Void>>() {
2560 @Override
2561 public Task<Void> then(Task<JSONObject> task) {
2562 return handleDeleteEventuallyResultAsync();
2563 }
2564 });
2565 }
2566
2567 return handleDeleteResultTask;
2568 }
2569
2570 /**
2571 * Handles the result of {@code deleteEventually}.
2572 * <p>
2573 * Should only be called on success.
2574 */
2575 Task<Void> handleDeleteEventuallyResultAsync() {
2576 synchronized (mutex) {
2577 isDeletingEventually -= 1;
2578 }
2579 Task<Void> handleDeleteResultTask = handleDeleteResultAsync();
2580
2581 return handleDeleteResultTask.onSuccessTask(new Continuation<Void, Task<Void>>() {
2582 @Override
2583 public Task<Void> then(Task<Void> task) {
2584 Parse.getEventuallyQueue()
2585 .notifyTestHelper(ParseCommandCache.TestHelper.OBJECT_REMOVED);
2586 return task;
2587 }
2588 });
2589 }
2590
2591 /**
2592 * Handles the result of {@code fetch}.
2593 * <p>
2594 * Should only be called on success.
2595 */
2596 Task<Void> handleFetchResultAsync(final ParseObject.State result) {
2597 Task<Void> task = Task.forResult(null);
2598
2599 /*
2600 * If this object is in the offline store, then we need to make sure that we pull in any dirty
2601 * changes it may have before merging the server data into it.
2602 */
2603 final OfflineStore store = Parse.getLocalDatastore();
2604 if (store != null) {
2605 task = task.onSuccessTask(new Continuation<Void, Task<Void>>() {
2606 @Override
2607 public Task<Void> then(Task<Void> task) {
2608 return store.fetchLocallyAsync(ParseObject.this).makeVoid();
2609 }
2610 }).continueWithTask(new Continuation<Void, Task<Void>>() {
2611 @Override
2612 public Task<Void> then(Task<Void> task) {
2613 // Catch CACHE_MISS
2614 if (task.getError() instanceof ParseException
2615 && ((ParseException) task.getError()).getCode() == ParseException.CACHE_MISS) {
2616 return null;
2617 }
2618 return task;
2619 }
2620 });
2621 }
2622
2623 task = task.onSuccessTask(new Continuation<Void, Task<Void>>() {
2624 @Override
2625 public Task<Void> then(Task<Void> task) {
2626 synchronized (mutex) {
2627 State newState;
2628 if (result.isComplete()) {
2629 // Result is complete, so just replace
2630 newState = result;
2631 } else {
2632 // Result is incomplete, so we'll need to apply it to the current state
2633 newState = getState().newBuilder().apply(result).build();
2634 }
2635 setState(newState);
2636 }
2637 return null;
2638 }
2639 });
2640
2641 if (store != null) {
2642 task = task.onSuccessTask(new Continuation<Void, Task<Void>>() {
2643 @Override
2644 public Task<Void> then(Task<Void> task) {
2645 return store.updateDataForObjectAsync(ParseObject.this);
2646 }
2647 }).continueWithTask(new Continuation<Void, Task<Void>>() {
2648 @Override
2649 public Task<Void> then(Task<Void> task) {
2650 // Catch CACHE_MISS
2651 if (task.getError() instanceof ParseException
2652 && ((ParseException) task.getError()).getCode() == ParseException.CACHE_MISS) {
2653 return null;
2654 }
2655 return task;
2656 }
2657 });
2658 }
2659
2660 return task;
2661 }
2662
2663 /**
2664 * Fetches this object with the data from the server. Call this whenever you want the state of the
2665 * object to reflect exactly what is on the server.
2666 *
2667 * @return The {@code ParseObject} that was fetched.
2668 * @throws ParseException Throws an exception if the server is inaccessible.
2669 */
2670 public <T extends ParseObject> T fetch() throws ParseException {
2671 return ParseTaskUtils.wait(this.<T>fetchInBackground());
2672 }
2673
2674 @SuppressWarnings("unchecked")
2675 <T extends ParseObject> Task<T> fetchAsync(
2676 final String sessionToken, Task<Void> toAwait) {
2677 return toAwait.onSuccessTask(new Continuation<Void, Task<ParseObject.State>>() {
2678 @Override
2679 public Task<ParseObject.State> then(Task<Void> task) {
2680 State state;
2681 Map<String, ParseObject> fetchedObjects;
2682 synchronized (mutex) {
2683 state = getState();
2684 fetchedObjects = collectFetchedObjects();
2685 }
2686 ParseDecoder decoder = new KnownParseObjectDecoder(fetchedObjects);
2687 return getObjectController().fetchAsync(state, sessionToken, decoder);
2688 }
2689 }).onSuccessTask(new Continuation<ParseObject.State, Task<Void>>() {
2690 @Override
2691 public Task<Void> then(Task<ParseObject.State> task) {
2692 ParseObject.State result = task.getResult();
2693 return handleFetchResultAsync(result);
2694 }
2695 }).onSuccess(new Continuation<Void, T>() {
2696 @Override
2697 public T then(Task<Void> task) {
2698 return (T) ParseObject.this;
2699 }
2700 });
2701 }
2702
2703 /**
2704 * Fetches this object with the data from the server in a background thread. This is preferable to
2705 * using fetch(), unless your code is already running from a background thread.
2706 *
2707 * @return A {@link bolts.Task} that is resolved when fetch completes.
2708 */
2709 public final <T extends ParseObject> Task<T> fetchInBackground() {
2710 return ParseUser.getCurrentSessionTokenAsync().onSuccessTask(new Continuation<String, Task<T>>() {
2711 @Override
2712 public Task<T> then(Task<String> task) {
2713 final String sessionToken = task.getResult();
2714 return taskQueue.enqueue(new Continuation<Void, Task<T>>() {
2715 @Override
2716 public Task<T> then(Task<Void> toAwait) {
2717 return fetchAsync(sessionToken, toAwait);
2718 }
2719 });
2720 }
2721 });
2722 }
2723
2724 /**
2725 * Fetches this object with the data from the server in a background thread. This is preferable to
2726 * using fetch(), unless your code is already running from a background thread.
2727 *
2728 * @param callback {@code callback.done(object, e)} is called when the fetch completes.
2729 */
2730 public final <T extends ParseObject> void fetchInBackground(GetCallback<T> callback) {
2731 ParseTaskUtils.callbackOnMainThreadAsync(this.<T>fetchInBackground(), callback);
2732 }
2733
2734 /**
2735 * If this {@code ParseObject} has not been fetched (i.e. {@link #isDataAvailable()} returns {@code false}),
2736 * fetches this object with the data from the server in a background thread. This is preferable to
2737 * using {@link #fetchIfNeeded()}, unless your code is already running from a background thread.
2738 *
2739 * @return A {@link bolts.Task} that is resolved when fetch completes.
2740 */
2741 public final <T extends ParseObject> Task<T> fetchIfNeededInBackground() {
2742 if (isDataAvailable()) {
2743 return Task.forResult((T) this);
2744 }
2745 return ParseUser.getCurrentSessionTokenAsync().onSuccessTask(new Continuation<String, Task<T>>() {
2746 @Override
2747 public Task<T> then(Task<String> task) {
2748 final String sessionToken = task.getResult();
2749 return taskQueue.enqueue(new Continuation<Void, Task<T>>() {
2750 @Override
2751 public Task<T> then(Task<Void> toAwait) {
2752 if (isDataAvailable()) {
2753 return Task.forResult((T) ParseObject.this);
2754 }
2755 return fetchAsync(sessionToken, toAwait);
2756 }
2757 });
2758 }
2759 });
2760
2761 }
2762
2763 /**
2764 * If this {@code ParseObject} has not been fetched (i.e. {@link #isDataAvailable()} returns {@code false}),
2765 * fetches this object with the data from the server.
2766 *
2767 * @return The fetched {@code ParseObject}.
2768 * @throws ParseException Throws an exception if the server is inaccessible.
2769 */
2770 public <T extends ParseObject> T fetchIfNeeded() throws ParseException {
2771 return ParseTaskUtils.wait(this.<T>fetchIfNeededInBackground());
2772 }
2773
2774 /**
2775 * If this {@code ParseObject} has not been fetched (i.e. {@link #isDataAvailable()} returns {@code false}),
2776 * fetches this object with the data from the server in a background thread. This is preferable to
2777 * using {@link #fetchIfNeeded()}, unless your code is already running from a background thread.
2778 *
2779 * @param callback {@code callback.done(object, e)} is called when the fetch completes.
2780 */
2781 public final <T extends ParseObject> void fetchIfNeededInBackground(GetCallback<T> callback) {
2782 ParseTaskUtils.callbackOnMainThreadAsync(this.<T>fetchIfNeededInBackground(), callback);
2783 }
2784
2785 // Validates the delete method
2786 void validateDelete() {
2787 // do nothing
2788 }
2789
2790 private Task<Void> deleteAsync(final String sessionToken, Task<Void> toAwait) {
2791 validateDelete();
2792
2793 return toAwait.onSuccessTask(new Continuation<Void, Task<Void>>() {
2794 @Override
2795 public Task<Void> then(Task<Void> task) {
2796 isDeleting = true;
2797 if (state.objectId() == null) {
2798 return task.cast(); // no reason to call delete since it doesn't exist
2799 }
2800 return deleteAsync(sessionToken);
2801 }
2802 }).onSuccessTask(new Continuation<Void, Task<Void>>() {
2803 @Override
2804 public Task<Void> then(Task<Void> task) {
2805 return handleDeleteResultAsync();
2806 }
2807 }).continueWith(new Continuation<Void, Void>() {
2808 @Override
2809 public Void then(Task<Void> task) throws Exception {
2810 isDeleting = false;
2811 if (task.isFaulted()) {
2812 throw task.getError();
2813 }
2814 return null;
2815 }
2816 });
2817 }
2818
2819 //TODO (grantland): I'm not sure we want direct access to this. All access to `delete` should
2820 // enqueue on the taskQueue...
2821 Task<Void> deleteAsync(String sessionToken) {
2822 return getObjectController().deleteAsync(getState(), sessionToken);
2823 }
2824
2825 /**
2826 * Handles the result of {@code delete}.
2827 * <p>
2828 * Should only be called on success.
2829 */
2830 Task<Void> handleDeleteResultAsync() {
2831 Task<Void> task = Task.forResult(null);
2832
2833 synchronized (mutex) {
2834 isDeleted = true;
2835 }
2836
2837 final OfflineStore store = Parse.getLocalDatastore();
2838 if (store != null) {
2839 task = task.continueWithTask(new Continuation<Void, Task<Void>>() {
2840 @Override
2841 public Task<Void> then(Task<Void> task) {
2842 synchronized (mutex) {
2843 if (isDeleted) {
2844 store.unregisterObject(ParseObject.this);
2845 return store.deleteDataForObjectAsync(ParseObject.this);
2846 } else {
2847 return store.updateDataForObjectAsync(ParseObject.this);
2848 }
2849 }
2850 }
2851 });
2852 }
2853
2854 return task;
2855 }
2856
2857 /**
2858 * Deletes this object on the server in a background thread. This is preferable to using
2859 * {@link #delete()}, unless your code is already running from a background thread.
2860 *
2861 * @return A {@link bolts.Task} that is resolved when delete completes.
2862 */
2863 public final Task<Void> deleteInBackground() {
2864 return ParseUser.getCurrentSessionTokenAsync().onSuccessTask(new Continuation<String, Task<Void>>() {
2865 @Override
2866 public Task<Void> then(Task<String> task) {
2867 final String sessionToken = task.getResult();
2868 return taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
2869 @Override
2870 public Task<Void> then(Task<Void> toAwait) {
2871 return deleteAsync(sessionToken, toAwait);
2872 }
2873 });
2874 }
2875 });
2876 }
2877
2878 /**
2879 * Deletes this object on the server. This does not delete or destroy the object locally.
2880 *
2881 * @throws ParseException Throws an error if the object does not exist or if the internet fails.
2882 */
2883 public final void delete() throws ParseException {
2884 ParseTaskUtils.wait(deleteInBackground());
2885 }
2886
2887 /**
2888 * Deletes this object on the server in a background thread. This is preferable to using
2889 * {@link #delete()}, unless your code is already running from a background thread.
2890 *
2891 * @param callback {@code callback.done(e)} is called when the save completes.
2892 */
2893 public final void deleteInBackground(DeleteCallback callback) {
2894 ParseTaskUtils.callbackOnMainThreadAsync(deleteInBackground(), callback);
2895 }
2896
2897 /**
2898 * Returns {@code true} if this object can be serialized for saving.
2899 */
2900 private boolean canBeSerialized() {
2901 synchronized (mutex) {
2902 final Capture<Boolean> result = new Capture<>(true);
2903
2904 // This method is only used for batching sets of objects for saveAll
2905 // and when saving children automatically. Since it's only used to
2906 // determine whether or not save should be called on them, it only
2907 // needs to examine their current values, so we use estimatedData.
2908 new ParseTraverser() {
2909 @Override
2910 protected boolean visit(Object value) {
2911 if (value instanceof ParseFile) {
2912 ParseFile file = (ParseFile) value;
2913 if (file.isDirty()) {
2914 result.set(false);
2915 }
2916 }
2917
2918 if (value instanceof ParseObject) {
2919 ParseObject object = (ParseObject) value;
2920 if (object.getObjectId() == null) {
2921 result.set(false);
2922 }
2923 }
2924
2925 // Continue to traverse only if it can still be serialized.
2926 return result.get();
2927 }
2928 }.setYieldRoot(false).setTraverseParseObjects(true).traverse(this);
2929
2930 return result.get();
2931 }
2932 }
2933
2934 /**
2935 * Return the operations that will be sent in the next call to save.
2936 */
2937 private ParseOperationSet currentOperations() {
2938 synchronized (mutex) {
2939 return operationSetQueue.getLast();
2940 }
2941 }
2942
2943 /**
2944 * Updates the estimated values in the map based on the given set of ParseFieldOperations.
2945 */
2946 private void applyOperations(ParseOperationSet operations, Map<String, Object> map) {
2947 for (String key : operations.keySet()) {
2948 ParseFieldOperation operation = operations.get(key);
2949 Object oldValue = map.get(key);
2950 Object newValue = operation.apply(oldValue, key);
2951 if (newValue != null) {
2952 map.put(key, newValue);
2953 } else {
2954 map.remove(key);
2955 }
2956 }
2957 }
2958
2959 /**
2960 * Regenerates the estimatedData map from the serverData and operations.
2961 */
2962 private void rebuildEstimatedData() {
2963 synchronized (mutex) {
2964 estimatedData.clear();
2965 for (String key : state.keySet()) {
2966 estimatedData.put(key, state.get(key));
2967 }
2968 for (ParseOperationSet operations : operationSetQueue) {
2969 applyOperations(operations, estimatedData);
2970 }
2971 }
2972 }
2973
2974 void markAllFieldsDirty() {
2975 synchronized (mutex) {
2976 for (String key : state.keySet()) {
2977 performPut(key, state.get(key));
2978 }
2979 }
2980 }
2981
2982 /**
2983 * performOperation() is like {@link #put(String, Object)} but instead of just taking a new value,
2984 * it takes a ParseFieldOperation that modifies the value.
2985 */
2986 void performOperation(String key, ParseFieldOperation operation) {
2987 synchronized (mutex) {
2988 Object oldValue = estimatedData.get(key);
2989 Object newValue = operation.apply(oldValue, key);
2990 if (newValue != null) {
2991 estimatedData.put(key, newValue);
2992 } else {
2993 estimatedData.remove(key);
2994 }
2995
2996 ParseFieldOperation oldOperation = currentOperations().get(key);
2997 ParseFieldOperation newOperation = operation.mergeWithPrevious(oldOperation);
2998 currentOperations().put(key, newOperation);
2999 }
3000 }
3001
3002 /**
3003 * Add a key-value pair to this object. It is recommended to name keys in
3004 * <code>camelCaseLikeThis</code>.
3005 *
3006 * @param key Keys must be alphanumerical plus underscore, and start with a letter.
3007 * @param value Values may be numerical, {@link String}, {@link JSONObject}, {@link JSONArray},
3008 * {@link JSONObject#NULL}, or other {@code ParseObject}s. value may not be {@code null}.
3009 */
3010 public void put(@NonNull String key, @NonNull Object value) {
3011 checkKeyIsMutable(key);
3012
3013 performPut(key, value);
3014 }
3015
3016 void performPut(String key, Object value) {
3017 if (key == null) {
3018 throw new IllegalArgumentException("key may not be null.");
3019 }
3020
3021 if (value == null) {
3022 throw new IllegalArgumentException("value may not be null.");
3023 }
3024
3025 if (value instanceof JSONObject) {
3026 ParseDecoder decoder = ParseDecoder.get();
3027 value = decoder.convertJSONObjectToMap((JSONObject) value);
3028 } else if (value instanceof JSONArray) {
3029 ParseDecoder decoder = ParseDecoder.get();
3030 value = decoder.convertJSONArrayToList((JSONArray) value);
3031 }
3032
3033 if (!ParseEncoder.isValidType(value)) {
3034 throw new IllegalArgumentException("invalid type for value: " + value.getClass().toString());
3035 }
3036
3037 performOperation(key, new ParseSetOperation(value));
3038 }
3039
3040 /**
3041 * Whether this object has a particular key. Same as {@link #containsKey(String)}.
3042 *
3043 * @param key The key to check for
3044 * @return Whether this object contains the key
3045 */
3046 public boolean has(@NonNull String key) {
3047 return containsKey(key);
3048 }
3049
3050 /**
3051 * Atomically increments the given key by 1.
3052 *
3053 * @param key The key to increment.
3054 */
3055 public void increment(@NonNull String key) {
3056 increment(key, 1);
3057 }
3058
3059 /**
3060 * Atomically increments the given key by the given number.
3061 *
3062 * @param key The key to increment.
3063 * @param amount The amount to increment by.
3064 */
3065 public void increment(@NonNull String key, @NonNull Number amount) {
3066 ParseIncrementOperation operation = new ParseIncrementOperation(amount);
3067 performOperation(key, operation);
3068 }
3069
3070 /**
3071 * Atomically adds an object to the end of the array associated with a given key.
3072 *
3073 * @param key The key.
3074 * @param value The object to add.
3075 */
3076 public void add(@NonNull String key, Object value) {
3077 this.addAll(key, Collections.singletonList(value));
3078 }
3079
3080 /**
3081 * Atomically adds the objects contained in a {@code Collection} to the end of the array
3082 * associated with a given key.
3083 *
3084 * @param key The key.
3085 * @param values The objects to add.
3086 */
3087 public void addAll(@NonNull String key, Collection<?> values) {
3088 ParseAddOperation operation = new ParseAddOperation(values);
3089 performOperation(key, operation);
3090 }
3091
3092 /**
3093 * Atomically adds an object to the array associated with a given key, only if it is not already
3094 * present in the array. The position of the insert is not guaranteed.
3095 *
3096 * @param key The key.
3097 * @param value The object to add.
3098 */
3099 public void addUnique(@NonNull String key, Object value) {
3100 this.addAllUnique(key, Collections.singletonList(value));
3101 }
3102
3103 /**
3104 * Atomically adds the objects contained in a {@code Collection} to the array associated with a
3105 * given key, only adding elements which are not already present in the array. The position of the
3106 * insert is not guaranteed.
3107 *
3108 * @param key The key.
3109 * @param values The objects to add.
3110 */
3111 public void addAllUnique(@NonNull String key, Collection<?> values) {
3112 ParseAddUniqueOperation operation = new ParseAddUniqueOperation(values);
3113 performOperation(key, operation);
3114 }
3115
3116 /**
3117 * Removes a key from this object's data if it exists.
3118 *
3119 * @param key The key to remove.
3120 */
3121 public void remove(@NonNull String key) {
3122 checkKeyIsMutable(key);
3123
3124 performRemove(key);
3125 }
3126
3127 void performRemove(String key) {
3128 synchronized (mutex) {
3129 Object object = get(key);
3130
3131 if (object != null) {
3132 performOperation(key, ParseDeleteOperation.getInstance());
3133 }
3134 }
3135 }
3136
3137 /**
3138 * Atomically removes all instances of the objects contained in a {@code Collection} from the
3139 * array associated with a given key. To maintain consistency with the Java Collection API, there
3140 * is no method removing all instances of a single object. Instead, you can call
3141 * {@code parseObject.removeAll(key, Arrays.asList(value))}.
3142 *
3143 * @param key The key.
3144 * @param values The objects to remove.
3145 */
3146 public void removeAll(@NonNull String key, Collection<?> values) {
3147 checkKeyIsMutable(key);
3148
3149 ParseRemoveOperation operation = new ParseRemoveOperation(values);
3150 performOperation(key, operation);
3151 }
3152
3153 private void checkKeyIsMutable(String key) {
3154 if (!isKeyMutable(key)) {
3155 throw new IllegalArgumentException("Cannot modify `" + key
3156 + "` property of an " + getClassName() + " object.");
3157 }
3158 }
3159
3160 boolean isKeyMutable(String key) {
3161 return true;
3162 }
3163
3164 /**
3165 * Whether this object has a particular key. Same as {@link #has(String)}.
3166 *
3167 * @param key The key to check for
3168 * @return Whether this object contains the key
3169 */
3170 public boolean containsKey(@NonNull String key) {
3171 synchronized (mutex) {
3172 return estimatedData.containsKey(key);
3173 }
3174 }
3175
3176 /**
3177 * Access a {@link String} value.
3178 *
3179 * @param key The key to access the value for.
3180 * @return {@code null} if there is no such key or if it is not a {@link String}.
3181 */
3182 @Nullable
3183 public String getString(@NonNull String key) {
3184 synchronized (mutex) {
3185 checkGetAccess(key);
3186 Object value = estimatedData.get(key);
3187 if (!(value instanceof String)) {
3188 return null;
3189 }
3190 return (String) value;
3191 }
3192 }
3193
3194 /**
3195 * Access a {@code byte[]} value.
3196 *
3197 * @param key The key to access the value for.
3198 * @return {@code null} if there is no such key or if it is not a {@code byte[]}.
3199 */
3200 @Nullable
3201 public byte[] getBytes(String key) {
3202 synchronized (mutex) {
3203 checkGetAccess(key);
3204 Object value = estimatedData.get(key);
3205 if (!(value instanceof byte[])) {
3206 return null;
3207 }
3208
3209 return (byte[]) value;
3210 }
3211 }
3212
3213 /**
3214 * Access a {@link Number} value.
3215 *
3216 * @param key The key to access the value for.
3217 * @return {@code null} if there is no such key or if it is not a {@link Number}.
3218 */
3219 @Nullable
3220 public Number getNumber(String key) {
3221 synchronized (mutex) {
3222 checkGetAccess(key);
3223 Object value = estimatedData.get(key);
3224 if (!(value instanceof Number)) {
3225 return null;
3226 }
3227 return (Number) value;
3228 }
3229 }
3230
3231 /**
3232 * Access a {@link JSONArray} value.
3233 *
3234 * @param key The key to access the value for.
3235 * @return {@code null} if there is no such key or if it is not a {@link JSONArray}.
3236 */
3237 @Nullable
3238 public JSONArray getJSONArray(String key) {
3239 synchronized (mutex) {
3240 checkGetAccess(key);
3241 Object value = estimatedData.get(key);
3242
3243 if (value instanceof List) {
3244 value = PointerOrLocalIdEncoder.get().encode(value);
3245 }
3246
3247 if (!(value instanceof JSONArray)) {
3248 return null;
3249 }
3250 return (JSONArray) value;
3251 }
3252 }
3253
3254 /**
3255 * Access a {@link List} value.
3256 *
3257 * @param key The key to access the value for
3258 * @return {@code null} if there is no such key or if the value can't be converted to a
3259 * {@link List}.
3260 */
3261 @Nullable
3262 public <T> List<T> getList(String key) {
3263 synchronized (mutex) {
3264 Object value = estimatedData.get(key);
3265 if (!(value instanceof List)) {
3266 return null;
3267 }
3268 @SuppressWarnings("unchecked")
3269 List<T> returnValue = (List<T>) value;
3270 return returnValue;
3271 }
3272 }
3273
3274 /**
3275 * Access a {@link Map} value
3276 *
3277 * @param key The key to access the value for
3278 * @return {@code null} if there is no such key or if the value can't be converted to a
3279 * {@link Map}.
3280 */
3281 @Nullable
3282 public <V> Map<String, V> getMap(String key) {
3283 synchronized (mutex) {
3284 Object value = estimatedData.get(key);
3285 if (!(value instanceof Map)) {
3286 return null;
3287 }
3288 @SuppressWarnings("unchecked")
3289 Map<String, V> returnValue = (Map<String, V>) value;
3290 return returnValue;
3291 }
3292 }
3293
3294 /**
3295 * Access a {@link JSONObject} value.
3296 *
3297 * @param key The key to access the value for.
3298 * @return {@code null} if there is no such key or if it is not a {@link JSONObject}.
3299 */
3300 @Nullable
3301 public JSONObject getJSONObject(String key) {
3302 synchronized (mutex) {
3303 checkGetAccess(key);
3304 Object value = estimatedData.get(key);
3305
3306 if (value instanceof Map) {
3307 value = PointerOrLocalIdEncoder.get().encode(value);
3308 }
3309
3310 if (!(value instanceof JSONObject)) {
3311 return null;
3312 }
3313
3314 return (JSONObject) value;
3315 }
3316 }
3317
3318 /**
3319 * Access an {@code int} value.
3320 *
3321 * @param key The key to access the value for.
3322 * @return {@code 0} if there is no such key or if it is not a {@code int}.
3323 */
3324 public int getInt(@NonNull String key) {
3325 Number number = getNumber(key);
3326 if (number == null) {
3327 return 0;
3328 }
3329 return number.intValue();
3330 }
3331
3332 /**
3333 * Access a {@code double} value.
3334 *
3335 * @param key The key to access the value for.
3336 * @return {@code 0} if there is no such key or if it is not a {@code double}.
3337 */
3338 public double getDouble(@NonNull String key) {
3339 Number number = getNumber(key);
3340 if (number == null) {
3341 return 0;
3342 }
3343 return number.doubleValue();
3344 }
3345
3346 /**
3347 * Access a {@code long} value.
3348 *
3349 * @param key The key to access the value for.
3350 * @return {@code 0} if there is no such key or if it is not a {@code long}.
3351 */
3352 public long getLong(@NonNull String key) {
3353 Number number = getNumber(key);
3354 if (number == null) {
3355 return 0;
3356 }
3357 return number.longValue();
3358 }
3359
3360 /**
3361 * Access a {@code boolean} value.
3362 *
3363 * @param key The key to access the value for.
3364 * @return {@code false} if there is no such key or if it is not a {@code boolean}.
3365 */
3366 public boolean getBoolean(@NonNull String key) {
3367 synchronized (mutex) {
3368 checkGetAccess(key);
3369 Object value = estimatedData.get(key);
3370 if (!(value instanceof Boolean)) {
3371 return false;
3372 }
3373 return (Boolean) value;
3374 }
3375 }
3376
3377 /**
3378 * Access a {@link Date} value.
3379 *
3380 * @param key The key to access the value for.
3381 * @return {@code null} if there is no such key or if it is not a {@link Date}.
3382 */
3383 @Nullable
3384 public Date getDate(@NonNull String key) {
3385 synchronized (mutex) {
3386 checkGetAccess(key);
3387 Object value = estimatedData.get(key);
3388 if (!(value instanceof Date)) {
3389 return null;
3390 }
3391 return (Date) value;
3392 }
3393 }
3394
3395 /**
3396 * Access a {@code ParseObject} value. This function will not perform a network request. Unless the
3397 * {@code ParseObject} has been downloaded (e.g. by a {@link ParseQuery#include(String)} or by calling
3398 * {@link #fetchIfNeeded()} or {@link #fetch()}), {@link #isDataAvailable()} will return
3399 * {@code false}.
3400 *
3401 * @param key The key to access the value for.
3402 * @return {@code null} if there is no such key or if it is not a {@code ParseObject}.
3403 */
3404 @Nullable
3405 public ParseObject getParseObject(@NonNull String key) {
3406 Object value = get(key);
3407 if (!(value instanceof ParseObject)) {
3408 return null;
3409 }
3410 return (ParseObject) value;
3411 }
3412
3413 /**
3414 * Access a {@link ParseUser} value. This function will not perform a network request. Unless the
3415 * {@code ParseObject} has been downloaded (e.g. by a {@link ParseQuery#include(String)} or by calling
3416 * {@link #fetchIfNeeded()} or {@link #fetch()}), {@link #isDataAvailable()} will return
3417 * {@code false}.
3418 *
3419 * @param key The key to access the value for.
3420 * @return {@code null} if there is no such key or if the value is not a {@link ParseUser}.
3421 */
3422 @Nullable
3423 public ParseUser getParseUser(@NonNull String key) {
3424 Object value = get(key);
3425 if (!(value instanceof ParseUser)) {
3426 return null;
3427 }
3428 return (ParseUser) value;
3429 }
3430
3431 /**
3432 * Access a {@link ParseFile} value. This function will not perform a network request. Unless the
3433 * {@link ParseFile} has been downloaded (e.g. by calling {@link ParseFile#getData()}),
3434 * {@link ParseFile#isDataAvailable()} will return {@code false}.
3435 *
3436 * @param key The key to access the value for.
3437 * @return {@code null} if there is no such key or if it is not a {@link ParseFile}.
3438 */
3439 @Nullable
3440 public ParseFile getParseFile(@NonNull String key) {
3441 Object value = get(key);
3442 if (!(value instanceof ParseFile)) {
3443 return null;
3444 }
3445 return (ParseFile) value;
3446 }
3447
3448 /**
3449 * Access a {@link ParseGeoPoint} value.
3450 *
3451 * @param key The key to access the value for
3452 * @return {@code null} if there is no such key or if it is not a {@link ParseGeoPoint}.
3453 */
3454 @Nullable
3455 public ParseGeoPoint getParseGeoPoint(@NonNull String key) {
3456 synchronized (mutex) {
3457 checkGetAccess(key);
3458 Object value = estimatedData.get(key);
3459 if (!(value instanceof ParseGeoPoint)) {
3460 return null;
3461 }
3462 return (ParseGeoPoint) value;
3463 }
3464 }
3465
3466 /**
3467 * Access a {@link ParsePolygon} value.
3468 *
3469 * @param key The key to access the value for
3470 * @return {@code null} if there is no such key or if it is not a {@link ParsePolygon}.
3471 */
3472 @Nullable
3473 public ParsePolygon getParsePolygon(@NonNull String key) {
3474 synchronized (mutex) {
3475 checkGetAccess(key);
3476 Object value = estimatedData.get(key);
3477 if (!(value instanceof ParsePolygon)) {
3478 return null;
3479 }
3480 return (ParsePolygon) value;
3481 }
3482 }
3483
3484 /**
3485 * Access the {@link ParseACL} governing this object.
3486 */
3487 @Nullable
3488 public ParseACL getACL() {
3489 return getACL(true);
3490 }
3491
3492 /**
3493 * Set the {@link ParseACL} governing this object.
3494 */
3495 public void setACL(ParseACL acl) {
3496 put(KEY_ACL, acl);
3497 }
3498
3499 private ParseACL getACL(boolean mayCopy) {
3500 synchronized (mutex) {
3501 checkGetAccess(KEY_ACL);
3502 Object acl = estimatedData.get(KEY_ACL);
3503 if (acl == null) {
3504 return null;
3505 }
3506 if (!(acl instanceof ParseACL)) {
3507 throw new RuntimeException("only ACLs can be stored in the ACL key");
3508 }
3509 if (mayCopy && ((ParseACL) acl).isShared()) {
3510 ParseACL copy = new ParseACL((ParseACL) acl);
3511 estimatedData.put(KEY_ACL, copy);
3512 return copy;
3513 }
3514 return (ParseACL) acl;
3515 }
3516 }
3517
3518 /**
3519 * Gets whether the {@code ParseObject} has been fetched.
3520 *
3521 * @return {@code true} if the {@code ParseObject} is new or has been fetched or refreshed. {@code false}
3522 * otherwise.
3523 */
3524 public boolean isDataAvailable() {
3525 synchronized (mutex) {
3526 return state.isComplete();
3527 }
3528 }
3529
3530 /**
3531 * Gets whether the {@code ParseObject} specified key has been fetched.
3532 * This means the property can be accessed safely.
3533 *
3534 * @return {@code true} if the {@code ParseObject} key is new or has been fetched or refreshed. {@code false}
3535 * otherwise.
3536 */
3537 public boolean isDataAvailable(@NonNull String key) {
3538 synchronized (mutex) {
3539 // Fallback to estimatedData to include dirty changes.
3540 return isDataAvailable() || state.availableKeys().contains(key) || estimatedData.containsKey(key);
3541 }
3542 }
3543
3544 /**
3545 * Access or create a {@link ParseRelation} value for a key
3546 *
3547 * @param key The key to access the relation for.
3548 * @return the ParseRelation object if the relation already exists for the key or can be created
3549 * for this key.
3550 */
3551 @NonNull
3552 public <T extends ParseObject> ParseRelation<T> getRelation(@NonNull String key) {
3553 synchronized (mutex) {
3554 // All the sanity checking is done when add or remove is called on the relation.
3555 Object value = estimatedData.get(key);
3556 if (value instanceof ParseRelation) {
3557 @SuppressWarnings("unchecked")
3558 ParseRelation<T> relation = (ParseRelation<T>) value;
3559 relation.ensureParentAndKey(this, key);
3560 return relation;
3561 } else {
3562 ParseRelation<T> relation = new ParseRelation<>(this, key);
3563 /*
3564 * We put the relation into the estimated data so that we'll get the same instance later,
3565 * which may have known objects cached. If we rebuildEstimatedData, then this relation will
3566 * be lost, and we'll get a new one. That's okay, because any cached objects it knows about
3567 * must be replayable from the operations in the queue. If there were any objects in this
3568 * relation that weren't still in the queue, then they would be in the copy of the
3569 * ParseRelation that's in the serverData, so we would have gotten that instance instead.
3570 */
3571 estimatedData.put(key, relation);
3572 return relation;
3573 }
3574 }
3575 }
3576
3577 /**
3578 * Access a value. In most cases it is more convenient to use a helper function such as
3579 * {@link #getString(String)} or {@link #getInt(String)}.
3580 *
3581 * @param key The key to access the value for.
3582 * @return {@code null} if there is no such key.
3583 */
3584 @Nullable
3585 public Object get(@NonNull String key) {
3586 synchronized (mutex) {
3587 if (key.equals(KEY_ACL)) {
3588 return getACL();
3589 }
3590
3591 checkGetAccess(key);
3592 Object value = estimatedData.get(key);
3593
3594 // A relation may be deserialized without a parent or key.
3595 // Either way, make sure it's consistent.
3596 if (value instanceof ParseRelation) {
3597 ((ParseRelation<?>) value).ensureParentAndKey(this, key);
3598 }
3599
3600 return value;
3601 }
3602 }
3603
3604 private void checkGetAccess(String key) {
3605 if (!isDataAvailable(key)) {
3606 throw new IllegalStateException(
3607 "ParseObject has no data for '" + key + "'. Call fetchIfNeeded() to get the data.");
3608 }
3609 }
3610
3611 public boolean hasSameId(ParseObject other) {
3612 synchronized (mutex) {
3613 return this.getClassName() != null && this.getObjectId() != null
3614 && this.getClassName().equals(other.getClassName())
3615 && this.getObjectId().equals(other.getObjectId());
3616 }
3617 }
3618
3619 void registerSaveListener(GetCallback<ParseObject> callback) {
3620 synchronized (mutex) {
3621 saveEvent.subscribe(callback);
3622 }
3623 }
3624
3625 void unregisterSaveListener(GetCallback<ParseObject> callback) {
3626 synchronized (mutex) {
3627 saveEvent.unsubscribe(callback);
3628 }
3629 }
3630
3631 /**
3632 * Called when a non-pointer is being created to allow additional initialization to occur.
3633 */
3634 void setDefaultValues() {
3635 if (needsDefaultACL() && ParseACL.getDefaultACL() != null) {
3636 this.setACL(ParseACL.getDefaultACL());
3637 }
3638 }
3639
3640 /**
3641 * Determines whether this object should get a default ACL. Override in subclasses to turn off
3642 * default ACLs.
3643 */
3644 boolean needsDefaultACL() {
3645 return true;
3646 }
3647
3648 /**
3649 * Loads data from the local datastore into this object, if it has not been fetched from the
3650 * server already. If the object is not stored in the local datastore, this method with do
3651 * nothing.
3652 */
3653 @SuppressWarnings("unchecked")
3654 <T extends ParseObject> Task<T> fetchFromLocalDatastoreAsync() {
3655 if (!Parse.isLocalDatastoreEnabled()) {
3656 throw new IllegalStateException("Method requires Local Datastore. " +
3657 "Please refer to `Parse#enableLocalDatastore(Context)`.");
3658 }
3659 return Parse.getLocalDatastore().fetchLocallyAsync((T) this);
3660 }
3661
3662 /**
3663 * Loads data from the local datastore into this object, if it has not been fetched from the
3664 * server already. If the object is not stored in the local datastore, this method with do
3665 * nothing.
3666 */
3667 public <T extends ParseObject> void fetchFromLocalDatastoreInBackground(GetCallback<T> callback) {
3668 ParseTaskUtils.callbackOnMainThreadAsync(this.<T>fetchFromLocalDatastoreAsync(), callback);
3669 }
3670
3671 /**
3672 * Loads data from the local datastore into this object, if it has not been fetched from the
3673 * server already. If the object is not stored in the local datastore, this method with throw a
3674 * CACHE_MISS exception.
3675 *
3676 * @throws ParseException exception if fails
3677 */
3678 public void fetchFromLocalDatastore() throws ParseException {
3679 ParseTaskUtils.wait(fetchFromLocalDatastoreAsync());
3680 }
3681
3682 /**
3683 * Stores the object and every object it points to in the local datastore, recursively. If those
3684 * other objects have not been fetched from Parse, they will not be stored. However, if they have
3685 * changed data, all of the changes will be retained. To get the objects back later, you can use
3686 * {@link ParseQuery#fromLocalDatastore()}, or you can create an unfetched pointer with
3687 * {@link #createWithoutData(Class, String)} and then call {@link #fetchFromLocalDatastore()} on
3688 * it.
3689 *
3690 * @param callback the callback
3691 * @see #unpinInBackground(String, DeleteCallback)
3692 */
3693 public void pinInBackground(String name, SaveCallback callback) {
3694 ParseTaskUtils.callbackOnMainThreadAsync(pinInBackground(name), callback);
3695 }
3696
3697 /**
3698 * Stores the object and every object it points to in the local datastore, recursively. If those
3699 * other objects have not been fetched from Parse, they will not be stored. However, if they have
3700 * changed data, all of the changes will be retained. To get the objects back later, you can use
3701 * {@link ParseQuery#fromLocalDatastore()}, or you can create an unfetched pointer with
3702 * {@link #createWithoutData(Class, String)} and then call {@link #fetchFromLocalDatastore()} on
3703 * it.
3704 *
3705 * @return A {@link bolts.Task} that is resolved when pinning completes.
3706 * @see #unpinInBackground(String)
3707 */
3708 public Task<Void> pinInBackground(String name) {
3709 return pinAllInBackground(name, Collections.singletonList(this));
3710 }
3711
3712 Task<Void> pinInBackground(String name, boolean includeAllChildren) {
3713 return pinAllInBackground(name, Collections.singletonList(this), includeAllChildren);
3714 }
3715
3716 /**
3717 * Stores the object and every object it points to in the local datastore, recursively. If those
3718 * other objects have not been fetched from Parse, they will not be stored. However, if they have
3719 * changed data, all of the changes will be retained. To get the objects back later, you can use
3720 * {@link ParseQuery#fromLocalDatastore()}, or you can create an unfetched pointer with
3721 * {@link #createWithoutData(Class, String)} and then call {@link #fetchFromLocalDatastore()} on
3722 * it.
3723 *
3724 * @throws ParseException exception if fails
3725 * @see #unpin(String)
3726 */
3727 public void pin(String name) throws ParseException {
3728 ParseTaskUtils.wait(pinInBackground(name));
3729 }
3730
3731 /**
3732 * Stores the object and every object it points to in the local datastore, recursively. If those
3733 * other objects have not been fetched from Parse, they will not be stored. However, if they have
3734 * changed data, all of the changes will be retained. To get the objects back later, you can use
3735 * {@link ParseQuery#fromLocalDatastore()}, or you can create an unfetched pointer with
3736 * {@link #createWithoutData(Class, String)} and then call {@link #fetchFromLocalDatastore()} on
3737 * it.
3738 *
3739 * @param callback the callback
3740 * @see #unpinInBackground(DeleteCallback)
3741 * @see #DEFAULT_PIN
3742 */
3743 public void pinInBackground(SaveCallback callback) {
3744 ParseTaskUtils.callbackOnMainThreadAsync(pinInBackground(), callback);
3745 }
3746
3747 /**
3748 * Stores the object and every object it points to in the local datastore, recursively. If those
3749 * other objects have not been fetched from Parse, they will not be stored. However, if they have
3750 * changed data, all of the changes will be retained. To get the objects back later, you can use
3751 * {@link ParseQuery#fromLocalDatastore()}, or you can create an unfetched pointer with
3752 * {@link #createWithoutData(Class, String)} and then call {@link #fetchFromLocalDatastore()} on
3753 * it.
3754 *
3755 * @return A {@link bolts.Task} that is resolved when pinning completes.
3756 * @see #unpinInBackground()
3757 * @see #DEFAULT_PIN
3758 */
3759 public Task<Void> pinInBackground() {
3760 return pinAllInBackground(DEFAULT_PIN, Collections.singletonList(this));
3761 }
3762
3763 /**
3764 * Stores the object and every object it points to in the local datastore, recursively. If those
3765 * other objects have not been fetched from Parse, they will not be stored. However, if they have
3766 * changed data, all of the changes will be retained. To get the objects back later, you can use
3767 * {@link ParseQuery#fromLocalDatastore()}, or you can create an unfetched pointer with
3768 * {@link #createWithoutData(Class, String)} and then call {@link #fetchFromLocalDatastore()} on
3769 * it.
3770 *
3771 * @throws ParseException exception if fails
3772 * @see #unpin()
3773 * @see #DEFAULT_PIN
3774 */
3775 public void pin() throws ParseException {
3776 ParseTaskUtils.wait(pinInBackground());
3777 }
3778
3779 /**
3780 * Removes the object and every object it points to in the local datastore, recursively.
3781 *
3782 * @param callback the callback
3783 * @see #pinInBackground(String, SaveCallback)
3784 */
3785 public void unpinInBackground(String name, DeleteCallback callback) {
3786 ParseTaskUtils.callbackOnMainThreadAsync(unpinInBackground(name), callback);
3787 }
3788
3789 /**
3790 * Removes the object and every object it points to in the local datastore, recursively.
3791 *
3792 * @return A {@link bolts.Task} that is resolved when unpinning completes.
3793 * @see #pinInBackground(String)
3794 */
3795 public Task<Void> unpinInBackground(String name) {
3796 return unpinAllInBackground(name, Collections.singletonList(this));
3797 }
3798
3799 /**
3800 * Removes the object and every object it points to in the local datastore, recursively.
3801 *
3802 * @see #pin(String)
3803 */
3804 public void unpin(String name) throws ParseException {
3805 ParseTaskUtils.wait(unpinInBackground(name));
3806 }
3807
3808 /**
3809 * Removes the object and every object it points to in the local datastore, recursively.
3810 *
3811 * @param callback the callback
3812 * @see #pinInBackground(SaveCallback)
3813 * @see #DEFAULT_PIN
3814 */
3815 public void unpinInBackground(DeleteCallback callback) {
3816 ParseTaskUtils.callbackOnMainThreadAsync(unpinInBackground(), callback);
3817 }
3818
3819 /**
3820 * Removes the object and every object it points to in the local datastore, recursively.
3821 *
3822 * @return A {@link bolts.Task} that is resolved when unpinning completes.
3823 * @see #pinInBackground()
3824 * @see #DEFAULT_PIN
3825 */
3826 public Task<Void> unpinInBackground() {
3827 return unpinAllInBackground(DEFAULT_PIN, Collections.singletonList(this));
3828 }
3829
3830 /**
3831 * Removes the object and every object it points to in the local datastore, recursively.
3832 *
3833 * @see #pin()
3834 * @see #DEFAULT_PIN
3835 */
3836 public void unpin() throws ParseException {
3837 ParseTaskUtils.wait(unpinInBackground());
3838 }
3839
3840 @Override
3841 public int describeContents() {
3842 return 0;
3843 }
3844
3845 @Override
3846 public void writeToParcel(Parcel dest, int flags) {
3847 writeToParcel(dest, new ParseObjectParcelEncoder(this));
3848 }
3849
3850 void writeToParcel(Parcel dest, ParseParcelEncoder encoder) {
3851 synchronized (mutex) {
3852 // Developer warnings.
3853 ldsEnabledWhenParceling = Parse.isLocalDatastoreEnabled();
3854 boolean saving = hasOutstandingOperations();
3855 boolean deleting = isDeleting || isDeletingEventually > 0;
3856 if (saving) {
3857 PLog.w(TAG, "About to parcel a ParseObject while a save / saveEventually operation is " +
3858 "going on. If recovered from LDS, the unparceled object will be internally updated when " +
3859 "these tasks end. If not, it will act as if these tasks have failed. This means that " +
3860 "the subsequent call to save() will update again the same keys, and this is dangerous " +
3861 "for certain operations, like increment(). To avoid inconsistencies, wait for operations " +
3862 "to end before parceling.");
3863 }
3864 if (deleting) {
3865 PLog.w(TAG, "About to parcel a ParseObject while a delete / deleteEventually operation is " +
3866 "going on. If recovered from LDS, the unparceled object will be internally updated when " +
3867 "these tasks end. If not, it will assume it's not deleted, and might incorrectly " +
3868 "return false for isDirty(). To avoid inconsistencies, wait for operations to end " +
3869 "before parceling.");
3870 }
3871 // Write className and id first, regardless of state.
3872 dest.writeString(getClassName());
3873 String objectId = getObjectId();
3874 dest.writeByte(objectId != null ? (byte) 1 : 0);
3875 if (objectId != null) dest.writeString(objectId);
3876 // Write state and other members
3877 state.writeToParcel(dest, encoder);
3878 dest.writeByte(localId != null ? (byte) 1 : 0);
3879 if (localId != null) dest.writeString(localId);
3880 dest.writeByte(isDeleted ? (byte) 1 : 0);
3881 // Care about dirty changes and ongoing tasks.
3882 ParseOperationSet set;
3883 if (hasOutstandingOperations()) {
3884 // There's more than one set. Squash the queue, creating copies
3885 // to preserve the original queue when LDS is enabled.
3886 set = new ParseOperationSet();
3887 for (ParseOperationSet operationSet : operationSetQueue) {
3888 ParseOperationSet copy = new ParseOperationSet(operationSet);
3889 copy.mergeFrom(set);
3890 set = copy;
3891 }
3892 } else {
3893 set = operationSetQueue.getLast();
3894 }
3895 set.setIsSaveEventually(false);
3896 set.toParcel(dest, encoder);
3897 // Pass a Bundle to subclasses.
3898 Bundle bundle = new Bundle();
3899 onSaveInstanceState(bundle);
3900 dest.writeBundle(bundle);
3901 }
3902 }
3903
3904 /**
3905 * Called when parceling this ParseObject.
3906 * Subclasses can put values into the provided {@link Bundle} and receive them later
3907 * {@link #onRestoreInstanceState(Bundle)}. Note that internal fields are already parceled by
3908 * the framework.
3909 *
3910 * @param outState Bundle to host extra values
3911 */
3912 protected void onSaveInstanceState(Bundle outState) {
3913 }
3914
3915 /**
3916 * Called when unparceling this ParseObject.
3917 * Subclasses can read values from the provided {@link Bundle} that were previously put
3918 * during {@link #onSaveInstanceState(Bundle)}. At this point the internal state is already
3919 * recovered.
3920 *
3921 * @param savedState Bundle to read the values from
3922 */
3923 protected void onRestoreInstanceState(Bundle savedState) {
3924 }
3925
3926 static class State {
3927
3928 private final String className;
3929 private final String objectId;
3930 private final long createdAt;
3931 private final long updatedAt;
3932 private final Map<String, Object> serverData;
3933 private final Set<String> availableKeys;
3934 private final boolean isComplete;
3935
3936 State(Init<?> builder) {
3937 className = builder.className;
3938 objectId = builder.objectId;
3939 createdAt = builder.createdAt;
3940 updatedAt = builder.updatedAt > 0
3941 ? builder.updatedAt
3942 : createdAt;
3943 serverData = Collections.unmodifiableMap(new HashMap<>(builder.serverData));
3944 isComplete = builder.isComplete;
3945 availableKeys = Collections.synchronizedSet(builder.availableKeys);
3946 }
3947
3948 State(Parcel parcel, String clazz, ParseParcelDecoder decoder) {
3949 className = clazz; // Already read
3950 objectId = parcel.readByte() == 1 ? parcel.readString() : null;
3951 createdAt = parcel.readLong();
3952 long updated = parcel.readLong();
3953 updatedAt = updated > 0 ? updated : createdAt;
3954 int size = parcel.readInt();
3955 HashMap<String, Object> map = new HashMap<>();
3956 for (int i = 0; i < size; i++) {
3957 String key = parcel.readString();
3958 Object obj = decoder.decode(parcel);
3959 map.put(key, obj);
3960 }
3961 serverData = Collections.unmodifiableMap(map);
3962 isComplete = parcel.readByte() == 1;
3963 List<String> available = new ArrayList<>();
3964 parcel.readStringList(available);
3965 availableKeys = new HashSet<>(available);
3966 }
3967
3968 public static Init<?> newBuilder(String className) {
3969 if ("_User".equals(className)) {
3970 return new ParseUser.State.Builder();
3971 }
3972 return new Builder(className);
3973 }
3974
3975
3976 static State createFromParcel(Parcel source, ParseParcelDecoder decoder) {
3977 String className = source.readString();
3978 if ("_User".equals(className)) {
3979 return new ParseUser.State(source, className, decoder);
3980 }
3981 return new State(source, className, decoder);
3982 }
3983
3984 @SuppressWarnings("unchecked")
3985 public <T extends Init<?>> T newBuilder() {
3986 return (T) new Builder(this);
3987 }
3988
3989 public String className() {
3990 return className;
3991 }
3992
3993 public String objectId() {
3994 return objectId;
3995 }
3996
3997 public long createdAt() {
3998 return createdAt;
3999 }
4000
4001 public long updatedAt() {
4002 return updatedAt;
4003 }
4004
4005 public boolean isComplete() {
4006 return isComplete;
4007 }
4008
4009 public Object get(String key) {
4010 return serverData.get(key);
4011 }
4012
4013 public Set<String> keySet() {
4014 return serverData.keySet();
4015 }
4016
4017 // Available keys for this object. With respect to keySet(), this includes also keys that are
4018 // undefined in the server, but that should be accessed without throwing.
4019 // These extra keys come e.g. from ParseQuery.selectKeys(). Selected keys must be available to
4020 // get() methods even if undefined, for consistency with complete objects.
4021 // For a complete object, this set is equal to keySet().
4022 public Set<String> availableKeys() {
4023 return availableKeys;
4024 }
4025
4026 protected void writeToParcel(Parcel dest, ParseParcelEncoder encoder) {
4027 dest.writeString(className);
4028 dest.writeByte(objectId != null ? (byte) 1 : 0);
4029 if (objectId != null) {
4030 dest.writeString(objectId);
4031 }
4032 dest.writeLong(createdAt);
4033 dest.writeLong(updatedAt);
4034 dest.writeInt(serverData.size());
4035 Set<String> keys = serverData.keySet();
4036 for (String key : keys) {
4037 dest.writeString(key);
4038 encoder.encode(serverData.get(key), dest);
4039 }
4040 dest.writeByte(isComplete ? (byte) 1 : 0);
4041 dest.writeStringList(new ArrayList<>(availableKeys));
4042 }
4043
4044 @Override
4045 public String toString() {
4046 return String.format(Locale.US, "%s@%s[" +
4047 "className=%s, objectId=%s, createdAt=%d, updatedAt=%d, isComplete=%s, " +
4048 "serverData=%s, availableKeys=%s]",
4049 getClass().getName(),
4050 Integer.toHexString(hashCode()),
4051 className,
4052 objectId,
4053 createdAt,
4054 updatedAt,
4055 isComplete,
4056 serverData,
4057 availableKeys);
4058 }
4059
4060 static abstract class Init<T extends Init> {
4061
4062 private final String className;
4063 Map<String, Object> serverData = new HashMap<>();
4064 private String objectId;
4065 private long createdAt = -1;
4066 private long updatedAt = -1;
4067 private boolean isComplete;
4068 private Set<String> availableKeys = new HashSet<>();
4069
4070 public Init(String className) {
4071 this.className = className;
4072 }
4073
4074 Init(State state) {
4075 className = state.className();
4076 objectId = state.objectId();
4077 createdAt = state.createdAt();
4078 updatedAt = state.updatedAt();
4079 availableKeys = Collections.synchronizedSet(state.availableKeys());
4080 for (String key : state.keySet()) {
4081 serverData.put(key, state.get(key));
4082 availableKeys.add(key);
4083 }
4084 isComplete = state.isComplete();
4085 }
4086
4087
4088 abstract T self();
4089
4090
4091 abstract <S extends State> S build();
4092
4093 public T objectId(String objectId) {
4094 this.objectId = objectId;
4095 return self();
4096 }
4097
4098 public T createdAt(Date createdAt) {
4099 this.createdAt = createdAt.getTime();
4100 return self();
4101 }
4102
4103 public T createdAt(long createdAt) {
4104 this.createdAt = createdAt;
4105 return self();
4106 }
4107
4108 public T updatedAt(Date updatedAt) {
4109 this.updatedAt = updatedAt.getTime();
4110 return self();
4111 }
4112
4113 public T updatedAt(long updatedAt) {
4114 this.updatedAt = updatedAt;
4115 return self();
4116 }
4117
4118 public T isComplete(boolean complete) {
4119 isComplete = complete;
4120 return self();
4121 }
4122
4123 public T put(String key, Object value) {
4124 serverData.put(key, value);
4125 availableKeys.add(key);
4126 return self();
4127 }
4128
4129 public T remove(String key) {
4130 serverData.remove(key);
4131 return self();
4132 }
4133
4134 public T availableKeys(Collection<String> keys) {
4135 availableKeys.addAll(keys);
4136 return self();
4137 }
4138
4139 public T clear() {
4140 objectId = null;
4141 createdAt = -1;
4142 updatedAt = -1;
4143 isComplete = false;
4144 serverData.clear();
4145 availableKeys.clear();
4146 return self();
4147 }
4148
4149 /**
4150 * Applies a {@code State} on top of this {@code Builder} instance.
4151 *
4152 * @param other The {@code State} to apply over this instance.
4153 * @return A new {@code Builder} instance.
4154 */
4155 public T apply(State other) {
4156 if (other.objectId() != null) {
4157 objectId(other.objectId());
4158 }
4159 if (other.createdAt() > 0) {
4160 createdAt(other.createdAt());
4161 }
4162 if (other.updatedAt() > 0) {
4163 updatedAt(other.updatedAt());
4164 }
4165 isComplete(isComplete || other.isComplete());
4166 for (String key : other.keySet()) {
4167 put(key, other.get(key));
4168 }
4169 availableKeys(other.availableKeys());
4170 return self();
4171 }
4172
4173 public T apply(ParseOperationSet operations) {
4174 for (String key : operations.keySet()) {
4175 ParseFieldOperation operation = operations.get(key);
4176 Object oldValue = serverData.get(key);
4177 Object newValue = operation.apply(oldValue, key);
4178 if (newValue != null) {
4179 put(key, newValue);
4180 } else {
4181 remove(key);
4182 }
4183 }
4184 return self();
4185 }
4186 }
4187
4188 static class Builder extends Init<Builder> {
4189
4190 public Builder(String className) {
4191 super(className);
4192 }
4193
4194 public Builder(State state) {
4195 super(state);
4196 }
4197
4198 @Override
4199 Builder self() {
4200 return this;
4201 }
4202
4203 public State build() {
4204 return new State(this);
4205 }
4206 }
4207 }
4208
4209}
4210
4211// [1] Normally we should only construct the command from state when it's our turn in the
4212// taskQueue so that new objects will have an updated objectId from previous saves.
4213// We can't do this for save/deleteEventually since this will break the promise that we'll
4214// try to run the command eventually, since our process might die before it's our turn in
4215// the taskQueue.
4216// This seems like this will only be a problem for new objects that are saved &
4217// save/deleteEventually'd at the same time, as the first will create 2 objects and the second
4218// the delete might fail.
4219