Skip to content

Implement copying #311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 21, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions Parse/src/main/java/com/parse/ParseACL.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,32 +103,41 @@ public static void setDefaultACL(ParseACL acl, boolean withAccessForCurrentUser)
return getDefaultACLController().get();
}

// State
private final Map<String, Permissions> 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<String, Permissions> 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a public API, do we need to add null checking here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It implicitly NPE on the next line, which I think is fine since Java APIs work similarly: https://android.googlesource.com/platform/libcore/+/refs/heads/master/luni/src/main/java/java/util/HashMap.java#194

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() {
Expand Down
10 changes: 10 additions & 0 deletions Parse/src/main/java/com/parse/ParseGeoPoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
2 changes: 1 addition & 1 deletion Parse/src/main/java/com/parse/ParseObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
45 changes: 41 additions & 4 deletions Parse/src/main/java/com/parse/ParsePush.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<ParseInstallation>(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;
Expand Down Expand Up @@ -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;
}
Expand Down
17 changes: 17 additions & 0 deletions Parse/src/main/java/com/parse/ParseQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,17 @@ public ParseQuery(String theClassName) {
this(new State.Builder<T>(theClassName));
}

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

/* package */ ParseQuery(State.Builder<T> builder) {
this.builder = builder;
}
Expand All @@ -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<T> setUser(ParseUser user) {
this.user = user;
return this;
Expand Down
4 changes: 2 additions & 2 deletions Parse/src/test/java/com/parse/ParseACLTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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<GetCallback> callbackCaptor = ArgumentCaptor.forClass(GetCallback.class);
Expand Down
33 changes: 33 additions & 0 deletions Parse/src/test/java/com/parse/ParseGeoPointTest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
39 changes: 39 additions & 0 deletions Parse/src/test/java/com/parse/ParsePushStateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 {

Expand Down Expand Up @@ -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<ParseInstallation> 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<ParseInstallation> queryStateCopy = copy.queryState();
assertNotSame(queryState, queryStateCopy);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

assertEquals("_Installation", queryStateCopy.className());
}

//region testExpirationTime

@Test
Expand Down
20 changes: 20 additions & 0 deletions Parse/src/test/java/com/parse/ParseQueryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -66,6 +67,25 @@ public void testConstructors() {
assertSame(builder, query.getBuilder());
}

@Test
public void testCopy() throws InterruptedException {
ParseQuery<ParseObject> query = new ParseQuery<>("TestObject");
query.setUser(new ParseUser());
query.whereEqualTo("foo", "bar");
ParseQuery.State.Builder<ParseObject> builder = query.getBuilder();
ParseQuery.State<ParseObject> state = query.getBuilder().build();

ParseQuery<ParseObject> queryCopy = new ParseQuery<>(query);
ParseQuery.State.Builder<ParseObject> builderCopy = queryCopy.getBuilder();
ParseQuery.State<ParseObject> 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 {
Expand Down