Skip to content

Commit

Permalink
Implement equals and hashCode in models
Browse files Browse the repository at this point in the history
**Summary**
Implement `equals()` and `hashCode()` for all models,
specifically the subclasses of `StripeJsonModel`

**Motivation**
The implementation of `equals()`, `hashCode()`, and
`toString()` in `StripeJsonModel`, which is the base
class of almost all model classes, is extremely
inefficient. It implements these methods by first
transforming the model object to a JSON string, which
is an expensive operation.

**Testing**
- Updated unit tests
- Tested on device with example app
  • Loading branch information
mshafrir-stripe committed Mar 1, 2019
1 parent b6fc272 commit 911e36c
Show file tree
Hide file tree
Showing 42 changed files with 1,225 additions and 715 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public void call() {
@Override
public void call(PaymentIntent paymentIntent) {
if (paymentIntent != null) {
mPaymentIntentValue.setText(paymentIntent.toString());
mPaymentIntentValue.setText(paymentIntent.toJson().toString());
Uri authUrl = paymentIntent.getAuthorizationUrl();
if ("requires_source_action".equals(paymentIntent.getStatus())
&& authUrl != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ private String formatStringResults(PaymentSessionData data) {
.append(" ending in ")
.append(scd.getLast4());
} else {
stringBuilder.append('\n').append(source.toString()).append('\n');
stringBuilder.append('\n').append(source.toJson().toString()).append('\n');
}
String isOrNot = data.isPaymentReadyToCharge() ? " IS " : " IS NOT ";
stringBuilder.append(isOrNot).append("ready to charge.\n\n");
Expand Down
65 changes: 41 additions & 24 deletions stripe/src/main/java/com/stripe/android/AbstractEphemeralKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.stripe.android.model.StripeJsonModel;
import com.stripe.android.utils.ObjectUtils;

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

import java.lang.reflect.InvocationTargetException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand All @@ -33,14 +34,15 @@ abstract class AbstractEphemeralKey extends StripeJsonModel implements Parcelabl
static final String FIELD_ASSOCIATED_OBJECTS = "associated_objects";
static final String FIELD_TYPE = "type";
static final String NULL = "null";
protected @NonNull String mObjectId;
private long mCreated;
private long mExpires;
private @NonNull String mId;
private boolean mLiveMode;
private @NonNull String mObject;
private @NonNull String mSecret;
private @NonNull String mType;

@NonNull final String mObjectId;
private final long mCreated;
private final long mExpires;
@NonNull private final String mId;
private final boolean mLiveMode;
@NonNull private final String mObject;
@NonNull private final String mSecret;
@NonNull private final String mType;

/**
* Parcel constructor for this {@link Parcelable} class.
Expand All @@ -50,7 +52,7 @@ abstract class AbstractEphemeralKey extends StripeJsonModel implements Parcelabl
*
* @param in the {@link Parcel} in which this Ephemeral Key has been stored.
*/
protected AbstractEphemeralKey(Parcel in) {
AbstractEphemeralKey(@NonNull Parcel in) {
mCreated = in.readLong();
mObjectId = in.readString();
mExpires = in.readLong();
Expand All @@ -61,7 +63,7 @@ protected AbstractEphemeralKey(Parcel in) {
mType = in.readString();
}

protected AbstractEphemeralKey(
AbstractEphemeralKey(
long created,
@NonNull String objectId,
long expires,
Expand All @@ -81,9 +83,7 @@ protected AbstractEphemeralKey(
mType = type;
}

protected AbstractEphemeralKey(
@Nullable JSONObject jsonObject
) throws JSONException {
AbstractEphemeralKey(@Nullable JSONObject jsonObject) throws JSONException {
mCreated = jsonObject.getLong(FIELD_CREATED);
mExpires = jsonObject.getLong(FIELD_EXPIRES);
mId = jsonObject.getString(FIELD_ID);
Expand Down Expand Up @@ -129,18 +129,17 @@ public JSONObject toJson() {

@NonNull
@Override
@SuppressWarnings("unchecked")
public Map<String, Object> toMap() {
Map map = new HashMap<>();
final AbstractMap<String, Object> map = new HashMap<>();
map.put(FIELD_CREATED, mCreated);
map.put(FIELD_EXPIRES, mExpires);
map.put(FIELD_OBJECT, mObject);
map.put(FIELD_ID, mId);
map.put(FIELD_SECRET, mSecret);
map.put(FIELD_LIVEMODE, mLiveMode);

List<Object> associatedObjectsList = new ArrayList<>();
Map<String, String> associatedObjectMap = new HashMap<>();
final List<Object> associatedObjectsList = new ArrayList<>();
final Map<String, String> associatedObjectMap = new HashMap<>();
associatedObjectMap.put(FIELD_ID, mObjectId);
associatedObjectMap.put(FIELD_TYPE, mType);
associatedObjectsList.add(associatedObjectMap);
Expand Down Expand Up @@ -183,11 +182,6 @@ long getExpires() {
return mExpires;
}

@VisibleForTesting
void setExpires(long value) {
mExpires = value;
}

@NonNull
String getId() {
return mId;
Expand Down Expand Up @@ -223,7 +217,7 @@ String getType() {
return fromJson(object, ephemeralKeyClass);
}

@Nullable
@NonNull
protected static <TEphemeralKey extends AbstractEphemeralKey> TEphemeralKey
fromJson(@Nullable JSONObject jsonObject, Class ephemeralKeyClass) {
if (jsonObject == null) {
Expand Down Expand Up @@ -256,4 +250,27 @@ String getType() {
" does not have an accessible (JSONObject) constructor", e);
}
}

@Override
public boolean equals(@Nullable Object obj) {
return this == obj
|| (obj instanceof AbstractEphemeralKey && typedEquals((AbstractEphemeralKey) obj));
}

private boolean typedEquals(@NonNull AbstractEphemeralKey ephemeralKey) {
return ObjectUtils.equals(mObjectId, ephemeralKey.mObjectId)
&& mCreated == ephemeralKey.mCreated
&& mExpires == ephemeralKey.mExpires
&& ObjectUtils.equals(mId, ephemeralKey.mId)
&& mLiveMode == ephemeralKey.mLiveMode
&& ObjectUtils.equals(mObject, ephemeralKey.mObject)
&& ObjectUtils.equals(mSecret, ephemeralKey.mSecret)
&& ObjectUtils.equals(mType, ephemeralKey.mType);
}

@Override
public int hashCode() {
return ObjectUtils.hash(mObjectId, mCreated, mExpires, mId, mLiveMode, mObject, mSecret,
mType);
}
}
35 changes: 15 additions & 20 deletions stripe/src/main/java/com/stripe/android/CustomerEphemeralKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,21 @@
import org.json.JSONObject;

class CustomerEphemeralKey extends AbstractEphemeralKey {
public static final Parcelable.Creator<CustomerEphemeralKey> CREATOR
= new Parcelable.Creator<CustomerEphemeralKey>() {

@Override
public CustomerEphemeralKey createFromParcel(Parcel in) {
return new CustomerEphemeralKey(in);
}

protected CustomerEphemeralKey(Parcel in) {
@Override
public CustomerEphemeralKey[] newArray(int size) {
return new CustomerEphemeralKey[size];
}
};

private CustomerEphemeralKey(@NonNull Parcel in) {
super(in);
}

Expand All @@ -37,33 +50,15 @@ protected CustomerEphemeralKey(
}

@SuppressWarnings("checkstyle:RedundantModifier") // Not actually redundant :|
public CustomerEphemeralKey(
@Nullable JSONObject jsonObject
) throws JSONException {
public CustomerEphemeralKey(@Nullable JSONObject jsonObject) throws JSONException {
super(jsonObject);

}


@NonNull
String getCustomerId() {
return mObjectId;
}

public static final Parcelable.Creator<CustomerEphemeralKey> CREATOR
= new Parcelable.Creator<CustomerEphemeralKey>() {

@Override
public CustomerEphemeralKey createFromParcel(Parcel in) {
return new CustomerEphemeralKey(in);
}

@Override
public CustomerEphemeralKey[] newArray(int size) {
return new CustomerEphemeralKey[size];
}
};

@Nullable
static CustomerEphemeralKey fromString(@Nullable String rawJson) throws JSONException {
return (CustomerEphemeralKey) AbstractEphemeralKey
Expand Down
47 changes: 34 additions & 13 deletions stripe/src/main/java/com/stripe/android/model/AccountParams.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.stripe.android.model;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.stripe.android.utils.ObjectUtils;

import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -12,10 +16,11 @@
*/
public class AccountParams {

static final String API_PARAM_LEGAL_ENTITY = "legal_entity";
static final String API_TOS_SHOWN_AND_ACCEPTED = "tos_shown_and_accepted";
private Boolean mTosShownAndAccepted;
private Map<String, Object> mLegalEntity;
private static final String API_PARAM_LEGAL_ENTITY = "legal_entity";
private static final String API_TOS_SHOWN_AND_ACCEPTED = "tos_shown_and_accepted";

@Nullable private Boolean mTosShownAndAccepted;
@Nullable private Map<String, Object> mLegalEntity;

/**
* @param tosShownAndAccepted indicates that the platform showed the user the appropriate text
Expand All @@ -25,20 +30,20 @@ public class AccountParams {
* created. Can contain any of the fields specified by legal_entity in the
* API docs.
*
* See {@linktourl https://stripe.com/docs/api#account_object-legal_entity}
* See https://stripe.com/docs/api/accounts/object#account_object-legal_entity
*
* The object in the map is expected to be a string or a list or map of
* strings. All {@link StripeJsonModel} types have a toMap() function that
* can be used to convert the {@link StripeJsonModel} to map representation
* that can be passed in here.
* The object in the map is expected to be a string or a list or map of
* strings. All {@link StripeJsonModel} types have a toMap() function that
* can be used to convert the {@link StripeJsonModel} to map representation
* that can be passed in here.
*/
@NonNull
public static AccountParams createAccountParams(
boolean tosShownAndAccepted,
Map<String, Object> legalEntity) {
AccountParams accountParams = new AccountParams()
return new AccountParams()
.setTosShownAndAccepted(tosShownAndAccepted)
.setLegalEntity(legalEntity);
return accountParams;
}

/**
Expand All @@ -47,6 +52,7 @@ public static AccountParams createAccountParams(
* when this is true.
* @return {@code this}, for chaining purposes
*/
@NonNull
public AccountParams setTosShownAndAccepted(boolean tosShownAndAccepted) {
mTosShownAndAccepted = tosShownAndAccepted;
return this;
Expand All @@ -65,6 +71,7 @@ public AccountParams setTosShownAndAccepted(boolean tosShownAndAccepted) {
* that can be passed in here.
* @return {@code this}, for chaining purposes
*/
@NonNull
public AccountParams setLegalEntity(Map<String, Object> legalEntity) {
mLegalEntity = legalEntity;
return this;
Expand All @@ -78,13 +85,27 @@ public AccountParams setLegalEntity(Map<String, Object> legalEntity) {
*/
@NonNull
public Map<String, Object> toParamMap() {
Map<String, Object> networkReadyMap = new HashMap<>();
Map<String, Object> tokenMap = new HashMap<>();
final Map<String, Object> networkReadyMap = new HashMap<>();
final AbstractMap<String, Object> tokenMap = new HashMap<>();
tokenMap.put(API_TOS_SHOWN_AND_ACCEPTED, mTosShownAndAccepted);
tokenMap.put(API_PARAM_LEGAL_ENTITY, mLegalEntity);
networkReadyMap.put("account", tokenMap);
removeNullAndEmptyParams(networkReadyMap);
return networkReadyMap;
}

@Override
public int hashCode() {
return ObjectUtils.hash(mTosShownAndAccepted, mLegalEntity);
}

@Override
public boolean equals(@Nullable Object obj) {
return this == obj || (obj instanceof AccountParams && typedEquals((AccountParams) obj));
}

private boolean typedEquals(@NonNull AccountParams accountParams) {
return ObjectUtils.equals(mTosShownAndAccepted, accountParams.mTosShownAndAccepted)
&& ObjectUtils.equals(mLegalEntity, accountParams.mLegalEntity);
}
}
15 changes: 7 additions & 8 deletions stripe/src/main/java/com/stripe/android/model/Address.java
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,13 @@ public boolean equals(Object obj) {
return this == obj || (obj instanceof Address && typedEquals((Address) obj));
}

private boolean typedEquals(@NonNull Address obj) {
return ObjectUtils.equals(mCity, obj.mCity)
&& ObjectUtils.equals(mCountry, obj.mCountry)
&& ObjectUtils.equals(mLine1, obj.mLine1)
&& ObjectUtils.equals(mLine2, obj.mLine2)
&& ObjectUtils.equals(mPostalCode, obj.mPostalCode)
&& ObjectUtils.equals(mState, obj.mState);
private boolean typedEquals(@NonNull Address address) {
return ObjectUtils.equals(mCity, address.mCity)
&& ObjectUtils.equals(mCountry, address.mCountry)
&& ObjectUtils.equals(mLine1, address.mLine1)
&& ObjectUtils.equals(mLine2, address.mLine2)
&& ObjectUtils.equals(mPostalCode, address.mPostalCode)
&& ObjectUtils.equals(mState, address.mState);
}

@Override
Expand Down Expand Up @@ -291,6 +291,5 @@ public Builder setState(@Nullable String state) {
public Address build() {
return new Address(this);
}

}
}
25 changes: 25 additions & 0 deletions stripe/src/main/java/com/stripe/android/model/BankAccount.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import androidx.annotation.Size;
import androidx.annotation.StringDef;

import com.stripe.android.utils.ObjectUtils;

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

Expand Down Expand Up @@ -204,4 +206,27 @@ public static BankAccount fromJson(@Nullable JSONObject jsonObject) {
StripeJsonUtils.optString(jsonObject, FIELD_LAST4),
StripeJsonUtils.optString(jsonObject, FIELD_ROUTING_NUMBER));
}

@Override
public int hashCode() {
return ObjectUtils.hash(mAccountHolderName, mAccountHolderType, mAccountNumber,
mBankName, mCountryCode, mCurrency, mFingerprint, mLast4, mRoutingNumber);
}

@Override
public boolean equals(@Nullable Object obj) {
return super.equals(obj) || (obj instanceof BankAccount && typedEquals((BankAccount) obj));
}

private boolean typedEquals(@NonNull BankAccount bankAccount) {
return ObjectUtils.equals(mAccountHolderName, bankAccount.mAccountHolderName)
&& ObjectUtils.equals(mAccountHolderType, bankAccount.mAccountHolderType)
&& ObjectUtils.equals(mAccountNumber, bankAccount.mAccountNumber)
&& ObjectUtils.equals(mBankName, bankAccount.mBankName)
&& ObjectUtils.equals(mCountryCode, bankAccount.mCountryCode)
&& ObjectUtils.equals(mCurrency, bankAccount.mCurrency)
&& ObjectUtils.equals(mFingerprint, bankAccount.mFingerprint)
&& ObjectUtils.equals(mLast4, bankAccount.mLast4)
&& ObjectUtils.equals(mRoutingNumber, bankAccount.mRoutingNumber);
}
}
Loading

0 comments on commit 911e36c

Please sign in to comment.