From a1ab2ebc01e36ae5a97937638ca0f2ccdbd76f5b Mon Sep 17 00:00:00 2001 From: Grantland Chew Date: Fri, 18 Dec 2015 12:45:22 -0800 Subject: [PATCH] Implement copying * ParseQuery * ParseGeoPoint * ParseACL * ParsePush --- Parse/src/main/java/com/parse/ParseACL.java | 29 +++++++----- .../main/java/com/parse/ParseGeoPoint.java | 10 +++++ .../src/main/java/com/parse/ParseObject.java | 2 +- Parse/src/main/java/com/parse/ParsePush.java | 45 +++++++++++++++++-- Parse/src/main/java/com/parse/ParseQuery.java | 17 +++++++ .../src/test/java/com/parse/ParseACLTest.java | 4 +- .../java/com/parse/ParseGeoPointTest.java | 33 ++++++++++++++ .../java/com/parse/ParsePushStateTest.java | 39 ++++++++++++++++ .../test/java/com/parse/ParseQueryTest.java | 20 +++++++++ 9 files changed, 182 insertions(+), 17 deletions(-) create mode 100644 Parse/src/test/java/com/parse/ParseGeoPointTest.java diff --git a/Parse/src/main/java/com/parse/ParseACL.java b/Parse/src/main/java/com/parse/ParseACL.java index f8041619a..c7381d563 100644 --- a/Parse/src/main/java/com/parse/ParseACL.java +++ b/Parse/src/main/java/com/parse/ParseACL.java @@ -103,32 +103,41 @@ public static void setDefaultACL(ParseACL acl, boolean withAccessForCurrentUser) return getDefaultACLController().get(); } + // State + private final Map permissionsById = new HashMap<>(); private boolean shared; + /** * A lazy user that hasn't been saved to Parse. */ //TODO (grantland): This should be a list for multiple lazy users with read/write permissions. private ParseUser unresolvedUser; - private Map permissionsById; - /** * Creates an ACL with no permissions granted. */ public ParseACL() { - permissionsById = new HashMap<>(); + // do nothing } - /* package */ ParseACL copy() { - ParseACL copy = new ParseACL(); - for (String id : permissionsById.keySet()) { - copy.permissionsById.put(id, new Permissions(permissionsById.get(id))); + /** + * Creates a copy of {@code acl}. + * + * @param acl + * The acl to copy. + */ + public ParseACL(ParseACL acl) { + for (String id : acl.permissionsById.keySet()) { + permissionsById.put(id, new Permissions(acl.permissionsById.get(id))); } - copy.unresolvedUser = unresolvedUser; + unresolvedUser = acl.unresolvedUser; if (unresolvedUser != null) { - unresolvedUser.registerSaveListener(new UserResolutionListener(copy)); + unresolvedUser.registerSaveListener(new UserResolutionListener(this)); } - return copy; + } + + /* package for tests */ ParseACL copy() { + return new ParseACL(this); } boolean isShared() { diff --git a/Parse/src/main/java/com/parse/ParseGeoPoint.java b/Parse/src/main/java/com/parse/ParseGeoPoint.java index dae2f1788..71e5ef5fc 100644 --- a/Parse/src/main/java/com/parse/ParseGeoPoint.java +++ b/Parse/src/main/java/com/parse/ParseGeoPoint.java @@ -58,6 +58,16 @@ public ParseGeoPoint(double latitude, double longitude) { setLongitude(longitude); } + /** + * Creates a copy of {@code point}; + * + * @param point + * The point to copy. + */ + public ParseGeoPoint(ParseGeoPoint point) { + this(point.getLatitude(), point.getLongitude()); + } + /** * Set latitude. Valid range is (-90.0, 90.0). Extremes should not be used. * diff --git a/Parse/src/main/java/com/parse/ParseObject.java b/Parse/src/main/java/com/parse/ParseObject.java index 2f2449e3c..deebc5691 100644 --- a/Parse/src/main/java/com/parse/ParseObject.java +++ b/Parse/src/main/java/com/parse/ParseObject.java @@ -3397,7 +3397,7 @@ private ParseACL getACL(boolean mayCopy) { throw new RuntimeException("only ACLs can be stored in the ACL key"); } if (mayCopy && ((ParseACL) acl).isShared()) { - ParseACL copy = ((ParseACL) acl).copy(); + ParseACL copy = new ParseACL((ParseACL) acl); estimatedData.put(KEY_ACL, copy); return copy; } diff --git a/Parse/src/main/java/com/parse/ParsePush.java b/Parse/src/main/java/com/parse/ParsePush.java index c95059070..73cfd0f45 100644 --- a/Parse/src/main/java/com/parse/ParsePush.java +++ b/Parse/src/main/java/com/parse/ParsePush.java @@ -55,6 +55,31 @@ private static void checkArgument(boolean expression, Object errorMessage) { private Boolean pushToAndroid; private JSONObject data; + public Builder() { + // do nothing + } + + public Builder(State state) { + this.channelSet = state.channelSet() == null + ? null + : Collections.unmodifiableSet(new HashSet<>(state.channelSet())); + this.query = state.queryState() == null + ? null + : new ParseQuery<>(new ParseQuery.State.Builder(state.queryState())); + this.expirationTime = state.expirationTime(); + this.expirationTimeInterval = state.expirationTimeInterval(); + this.pushToIOS = state.pushToIOS(); + this.pushToAndroid = state.pushToAndroid(); + // Since in state.build() we check data is not null, we do not need to check it again here. + JSONObject copyData = null; + try { + copyData = new JSONObject(state.data().toString()); + } catch (JSONException e) { + // Swallow this silently since it is impossible to happen + } + this.data = copyData; + } + public Builder expirationTime(Long expirationTime) { this.expirationTime = expirationTime; expirationTimeInterval = null; @@ -184,15 +209,27 @@ public JSONObject data() { /* package for test */ final State.Builder builder; /** - * Creates a new push notification. The default channel is the empty string, also known as the - * global broadcast channel, but this value can be overridden using {@link #setChannel(String)}, - * {@link #setChannels(Collection)} or {@link #setQuery(ParseQuery)}. Before sending the push - * notification you must call either {@link #setMessage(String)} or {@link #setData(JSONObject)}. + * Creates a new push notification. + * + * The default channel is the empty string, also known as the global broadcast channel, but this + * value can be overridden using {@link #setChannel(String)}, {@link #setChannels(Collection)} or + * {@link #setQuery(ParseQuery)}. Before sending the push notification you must call either + * {@link #setMessage(String)} or {@link #setData(JSONObject)}. */ public ParsePush() { this(new State.Builder()); } + /** + * Creates a copy of {@code push}. + * + * @param push + * The push to copy. + */ + public ParsePush(ParsePush push) { + this(new State.Builder(push.builder.build())); + } + private ParsePush(State.Builder builder) { this.builder = builder; } diff --git a/Parse/src/main/java/com/parse/ParseQuery.java b/Parse/src/main/java/com/parse/ParseQuery.java index 9770eccd3..e1537bb1f 100644 --- a/Parse/src/main/java/com/parse/ParseQuery.java +++ b/Parse/src/main/java/com/parse/ParseQuery.java @@ -910,6 +910,17 @@ public ParseQuery(String theClassName) { this(new State.Builder(theClassName)); } + /** + * Constructs a copy of {@code query}; + * + * @param query + * The query to copy. + */ + public ParseQuery(ParseQuery query) { + this(new State.Builder<>(query.getBuilder())); + user = query.user; + } + /* package */ ParseQuery(State.Builder builder) { this.builder = builder; } @@ -918,6 +929,12 @@ public ParseQuery(String theClassName) { return builder; } + /** + * Sets the user to be used for this query. + * + * + * The query will use the user if set, otherwise it will read the current user. + */ /* package for tests */ ParseQuery setUser(ParseUser user) { this.user = user; return this; diff --git a/Parse/src/test/java/com/parse/ParseACLTest.java b/Parse/src/test/java/com/parse/ParseACLTest.java index 14c97f59b..5b66104dd 100644 --- a/Parse/src/test/java/com/parse/ParseACLTest.java +++ b/Parse/src/test/java/com/parse/ParseACLTest.java @@ -88,7 +88,7 @@ public void testCopy() throws Exception { // setReadAccess() reset(unresolvedUser); - ParseACL copiedACL = acl.copy(); + ParseACL copiedACL = new ParseACL(acl); assertEquals(1, copiedACL.getPermissionsById().size()); assertTrue(copiedACL.getPermissionsById().containsKey(UNRESOLVED_KEY)); @@ -111,7 +111,7 @@ public void testCopyWithSaveListener() throws Exception { // setReadAccess() reset(unresolvedUser); - ParseACL copiedACL = acl.copy(); + ParseACL copiedACL = new ParseACL(acl); // Make sure the callback is called ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass(GetCallback.class); diff --git a/Parse/src/test/java/com/parse/ParseGeoPointTest.java b/Parse/src/test/java/com/parse/ParseGeoPointTest.java new file mode 100644 index 000000000..ab5646ca9 --- /dev/null +++ b/Parse/src/test/java/com/parse/ParseGeoPointTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.parse; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ParseGeoPointTest { + + @Test + public void testConstructors() { + ParseGeoPoint point = new ParseGeoPoint(); + assertEquals(0, point.getLatitude(), 0); + assertEquals(0, point.getLongitude(), 0); + + double lat = 1.0; + double lng = 2.0; + point = new ParseGeoPoint(lat, lng); + assertEquals(lat, point.getLatitude(), 0); + assertEquals(lng, point.getLongitude(), 0); + + ParseGeoPoint copy = new ParseGeoPoint(point); + assertEquals(lat, copy.getLatitude(), 0); + assertEquals(lng, copy.getLongitude(), 0); + } +} diff --git a/Parse/src/test/java/com/parse/ParsePushStateTest.java b/Parse/src/test/java/com/parse/ParsePushStateTest.java index 808c80376..34aa03542 100644 --- a/Parse/src/test/java/com/parse/ParsePushStateTest.java +++ b/Parse/src/test/java/com/parse/ParsePushStateTest.java @@ -8,8 +8,10 @@ */ package com.parse; +import org.json.JSONException; import org.json.JSONObject; import org.junit.Test; +import org.mockito.internal.util.collections.Sets; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; @@ -20,8 +22,13 @@ import java.util.Set; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class ParsePushStateTest { @@ -56,6 +63,38 @@ public void testDefaultsWithData() throws Exception { //endregion + @Test + public void testCopy() throws JSONException { + ParsePush.State state = mock(ParsePush.State.class); + when(state.expirationTime()).thenReturn(1L); + when(state.expirationTimeInterval()).thenReturn(2L); + Set channelSet = Sets.newSet("one", "two"); + when(state.channelSet()).thenReturn(channelSet); + JSONObject data = new JSONObject(); + data.put("foo", "bar"); + when(state.data()).thenReturn(data); + when(state.pushToAndroid()).thenReturn(true); + when(state.pushToIOS()).thenReturn(false); + ParseQuery.State queryState = + new ParseQuery.State.Builder<>(ParseInstallation.class).build(); + when(state.queryState()).thenReturn(queryState); + + ParsePush.State copy = new ParsePush.State.Builder(state).build(); + assertSame(1L, copy.expirationTime()); + assertSame(2L, copy.expirationTimeInterval()); + Set channelSetCopy = copy.channelSet(); + assertNotSame(channelSet, channelSetCopy); + assertTrue(channelSetCopy.size() == 2 && channelSetCopy.contains("one")); + JSONObject dataCopy = copy.data(); + assertNotSame(data, dataCopy); + assertEquals("bar", dataCopy.get("foo")); + assertTrue(copy.pushToAndroid()); + assertFalse(copy.pushToIOS()); + ParseQuery.State queryStateCopy = copy.queryState(); + assertNotSame(queryState, queryStateCopy); + assertEquals("_Installation", queryStateCopy.className()); + } + //region testExpirationTime @Test diff --git a/Parse/src/test/java/com/parse/ParseQueryTest.java b/Parse/src/test/java/com/parse/ParseQueryTest.java index 4650de8b3..e40e66ff5 100644 --- a/Parse/src/test/java/com/parse/ParseQueryTest.java +++ b/Parse/src/test/java/com/parse/ParseQueryTest.java @@ -27,6 +27,7 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; @@ -66,6 +67,25 @@ public void testConstructors() { assertSame(builder, query.getBuilder()); } + @Test + public void testCopy() throws InterruptedException { + ParseQuery query = new ParseQuery<>("TestObject"); + query.setUser(new ParseUser()); + query.whereEqualTo("foo", "bar"); + ParseQuery.State.Builder builder = query.getBuilder(); + ParseQuery.State state = query.getBuilder().build(); + + ParseQuery queryCopy = new ParseQuery<>(query); + ParseQuery.State.Builder builderCopy = queryCopy.getBuilder(); + ParseQuery.State stateCopy = queryCopy.getBuilder().build(); + + assertNotSame(query, queryCopy); + assertSame(query.getUserAsync(state).getResult(), queryCopy.getUserAsync(stateCopy).getResult()); + + assertNotSame(builder, builderCopy); + assertSame(state.constraints().get("foo"), stateCopy.constraints().get("foo")); + } + // ParseUser#setUser is for tests only @Test public void testSetUser() throws ParseException {