Java tutorial
/******************************************************************************* * Copyright 2014 eTilbudsavis * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ package com.eTilbudsavis.etasdk.model; import java.io.Serializable; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.os.Parcel; import android.os.Parcelable; import com.eTilbudsavis.etasdk.Eta; import com.eTilbudsavis.etasdk.ListManager; import com.eTilbudsavis.etasdk.log.EtaLog; import com.eTilbudsavis.etasdk.model.interfaces.IErn; import com.eTilbudsavis.etasdk.model.interfaces.IJson; import com.eTilbudsavis.etasdk.model.interfaces.SyncState; import com.eTilbudsavis.etasdk.utils.Api.JsonKey; import com.eTilbudsavis.etasdk.utils.Json; import com.eTilbudsavis.etasdk.utils.Utils; /** * This class is a representation of the eTilbudsavis API v2 shopppinglist. * * @author Danny Hvam - danny@etilbudsavis.dk * */ public class Shoppinglist implements Comparable<Shoppinglist>, SyncState<Shoppinglist>, IErn<Shoppinglist>, IJson<JSONObject>, Serializable, Parcelable { public static final String TAG = Eta.TAG_PREFIX + Shoppinglist.class.getSimpleName(); private static final long serialVersionUID = 5718447151312028262L; /** * The default {@link Shoppinglist} type */ public static final String TYPE_SHOPPING_LIST = ""; /** * A type used for wishlists, this could be for christmas, birthdays e.t.c. */ public static final String TYPE_WISH_LIST = "wish_list"; /** * Access level that only permits the owner of the list to view it. */ public static final String ACCESS_PRIVATE = "private"; /** * Access level that allows {@link Share owner}, and {@link Share shares} of the * list to view/edit it. */ public static final String ACCESS_SHARED = "shared"; /** * Access level that makes it visible to everyone, but only editable * to anyone who has a {@link Share share} with read/write privileges. */ public static final String ACCESS_PUBLIC = "public"; private String mErn; private String mName = ""; private String mAccess = ACCESS_PRIVATE; private Date mModified; private String mPrevId; private String mType; private String mMeta; private HashMap<String, Share> mShares = new HashMap<String, Share>(1); private int mUserId = -1; private int mSyncState = SyncState.TO_SYNC; private Shoppinglist() { setId(Utils.createUUID()); mModified = Utils.roundTime(new Date()); } /** * Factory method for creating a shoppinglist from a name. * @param name A name for the {@link Shoppinglist} * @return A {@link Shoppinglist} */ public static Shoppinglist fromName(String name) { Shoppinglist sl = new Shoppinglist(); sl.setName(name); return sl; } /** * Convert a {@link JSONArray} into a {@link List}<T>. * @param shoppinglists A {@link JSONArray} in the format of a valid API v2 shoppinglist response * @return A {@link List} of POJO; */ public static ArrayList<Shoppinglist> fromJSON(JSONArray shoppinglists) { ArrayList<Shoppinglist> list = new ArrayList<Shoppinglist>(); try { for (int i = 0; i < shoppinglists.length(); i++) { Shoppinglist s = Shoppinglist.fromJSON(shoppinglists.getJSONObject(i)); list.add(s); } } catch (JSONException e) { EtaLog.e(TAG, "", e); } return list; } /** * A factory method for converting {@link JSONObject} into a POJO. * @param shoppinglist A {@link JSONObject} in the format of a valid API v * shoppinglist response * @return An {@link Shoppinglist} object */ public static Shoppinglist fromJSON(JSONObject jShoppinglist) { Shoppinglist shoppinglist = new Shoppinglist(); if (jShoppinglist == null) { return shoppinglist; } try { // We've don't use OWNER anymore, since we now get all share info. shoppinglist.setId(Json.valueOf(jShoppinglist, JsonKey.ID)); shoppinglist.setErn(Json.valueOf(jShoppinglist, JsonKey.ERN)); shoppinglist.setName(Json.valueOf(jShoppinglist, JsonKey.NAME)); shoppinglist.setAccess(Json.valueOf(jShoppinglist, JsonKey.ACCESS)); String modified = Json.valueOf(jShoppinglist, JsonKey.MODIFIED, Utils.DATE_EPOC); shoppinglist.setModified(Utils.stringToDate(modified)); shoppinglist.setPreviousId(Json.valueOf(jShoppinglist, JsonKey.PREVIOUS_ID)); shoppinglist.setType(Json.valueOf(jShoppinglist, JsonKey.TYPE)); // A whole lot of 'saving my ass from exceptions' for meta String metaString = Json.valueOf(jShoppinglist, JsonKey.META, "{}").trim(); // If it looks like a JSONObject, try parsing it if (metaString.startsWith("{") && metaString.endsWith("}")) { try { // Try to parse the json string shoppinglist.setMeta(new JSONObject(metaString)); } catch (JSONException e) { EtaLog.e(TAG, "", e); // Meta parsing failed, so we'll do a recovery shoppinglist.setMeta(new JSONObject()); shoppinglist.setModified(new Date()); } } else { // String doesn't look like json, so we'll do a recovery shoppinglist.setMeta(new JSONObject()); shoppinglist.setModified(new Date()); } shoppinglist.putShares(Share.fromJSON(jShoppinglist.getJSONArray(JsonKey.SHARES))); } catch (JSONException e) { EtaLog.e(TAG, "", e); } return shoppinglist; } public JSONObject toJSON() { JSONObject o = new JSONObject(); try { o.put(JsonKey.ID, Json.nullCheck(getId())); o.put(JsonKey.ERN, Json.nullCheck(getErn())); o.put(JsonKey.NAME, Json.nullCheck(getName())); o.put(JsonKey.ACCESS, Json.nullCheck(getAccess())); o.put(JsonKey.MODIFIED, Json.nullCheck(Utils.dateToString(getModified()))); o.put(JsonKey.PREVIOUS_ID, Json.nullCheck(getPreviousId())); o.put(JsonKey.TYPE, Json.nullCheck(getType())); o.put(JsonKey.META, Json.nullCheck(getMeta())); JSONArray shares = new JSONArray(); for (Share share : getShares().values()) { shares.put(share.toJSON()); } o.put(JsonKey.SHARES, shares); } catch (JSONException e) { EtaLog.e(TAG, "", e); } return o; } public Shoppinglist setId(String id) { setErn((id == null) ? null : String.format("ern:%s:%s", getErnType(), id)); return this; } public String getId() { if (mErn == null) { return null; } String[] parts = mErn.split(":"); return parts[parts.length - 1]; } public Shoppinglist setErn(String ern) { if (ern == null || (ern.startsWith("ern:") && ern.split(":").length == 4 && ern.contains(getErnType()))) { mErn = ern; } return this; } public String getErn() { return mErn; } public String getErnType() { return IErn.TYPE_SHOPPINGLIST; } /** * Get the human readable name for this {@link Shoppinglist} * @return A name, or {@code null} */ public String getName() { return mName; } /** * Set the name for this {@link Shoppinglist} * @param name A name * @return This object */ public Shoppinglist setName(String name) { mName = name; return this; } /** * Get the access level for this {@link Shoppinglist}, that other will have. * @return The access level */ public String getAccess() { return mAccess; } /** * Set the access level for this {@link Shoppinglist}. * <p> * Current valid options are: * <ul> * <li> {@link #ACCESS_PRIVATE} </li> * <li> {@link #ACCESS_SHARED} </li> * <li> {@link #ACCESS_PUBLIC} </li> * </ul> * </p> * @param access * @return */ public Shoppinglist setAccess(String access) { mAccess = access; return this; } /** * Get the latest modified date for the {@link Shoppinglist} * @return A date */ public Date getModified() { return mModified; } /** * Set the latest modified date for this {@link Shoppinglist}. * * <p>If you are using the {@link ListManager} this will implicitly be * handled for you.</p> * @param time A date for latest edit to the list * @return This object */ public Shoppinglist setModified(Date time) { mModified = Utils.roundTime(time); return this; } /** * Get the {@link Shoppinglist#getId() id} of the item that is 'above' or the * previous element in the list of {@link Shoppinglist shoppinglists} for * this user, when you display the lists to the user. * * <p>If there are no {@link Shoppinglist} above this one the value will be * {@link EtaListObject#FIRST_ITEM}</p> * * @return An {@link Shoppinglist#getId() id} */ public String getPreviousId() { return mPrevId; } /** * * Set the {@link Shoppinglist#getId() id} of the item that is 'above' or the * previous element in the list of {@link Shoppinglist shoppinglists} for * this user, when you display the lists to the user. * * <p>If there are no {@link Shoppinglist} above this one the value will be * {@link EtaListObject#FIRST_ITEM}</p> * * <p><b>important</b> if you are using {@link ListManager} other * {@link Shoppinglist shoppinglists} will have their * {@link Shoppinglist#setPreviousId(String) previous_id}, * {@link Shoppinglist#setModified(Date) modified} e.t.c. updated to reflect * any changes to this {@link Shoppinglist}, but if you do not use the * {@link ListManager} you <b>must</b> update all other {@link Shoppinglist} * that may be accefted by these changes</p> * * @param id A new previous_id * @return This object */ public Shoppinglist setPreviousId(String id) { mPrevId = id; return this; } /** * Get the type of {@link Shoppinglist} * * Currently eTilbudsavis are using the two types below, but you are free to * do other types too. * <ul> * <li> {@link Shoppinglist#TYPE_SHOPPING_LIST} </li> * <li> {@link Shoppinglist#TYPE_WISH_LIST} </li> * </ul> * @return A type */ public String getType() { if (mType == null) { mType = TYPE_SHOPPING_LIST; } return mType; } /** * Set the type of {@link Shoppinglist} * * Currently eTilbudsavis are using the two types below, but you are free to * do other types too. * <ul> * <li> {@link Shoppinglist#TYPE_SHOPPING_LIST} </li> * <li> {@link Shoppinglist#TYPE_WISH_LIST} </li> * </ul> * @return This object */ public Shoppinglist setType(String type) { if (type == null) { // it's a Shoppinglist mType = TYPE_SHOPPING_LIST; } else { // It's a different type mType = type; } return this; } /** * Get a {@link Map} of all shares in this {@link Shoppinglist} * @return All shares */ public HashMap<String, Share> getShares() { return mShares; } /** * Update the set of {@link Share shares} that may read/edit this {@link Shoppinglist} * * <p>This method will <i>override</i> the current {@link Share shares} in * the list. To append/edit the current set of shares use {@link #putShares(List)}</p> * @param shares A new list of {@link Share shares} * @return */ public Shoppinglist setShares(List<Share> shares) { mShares.clear(); for (Share s : shares) { s.setShoppinglistId(getId()); mShares.put(s.getEmail(), s); } return this; } /** * Appends/edit the current list of {@link Share shares} that may read/write * this {@link Shoppinglist}. * @param shares A list of {@link Share shares} to append/edit * @return This object */ public Shoppinglist putShares(List<Share> shares) { if (shares == null) return this; for (Share s : shares) { s.setShoppinglistId(getId()); mShares.put(s.getEmail(), s); } return this; } /** * Add a share in the list of {@link Share shares} that may read/write to * this {@link Shoppinglist}. If the {@link Share#getEmail() email} already * exists, it will be overridden. * @param share A share to add/edit * @return This object */ public Shoppinglist putShare(Share share) { if (share == null) return this; share.setShoppinglistId(getId()); mShares.put(share.getEmail(), share); return this; } /** * Remove a {@link Share} from this {@link Shoppinglist}. * * <p>This will revoke/remove all the privileges the share previously had, * to read/write to the list.</p> * @param s The share to remove * @return This object */ public Shoppinglist removeShare(Share s) { if (s == null) return this; mShares.remove(s.getEmail()); return this; } /** * Get the meta information for this {@link Shoppinglist}. * * <p>If you edit the object, remember to use {@link #setMeta(JSONObject)} * or your changes will not be saved to local DB.</p> * * <p>Meta doesn't currently have any restrictions other that it must * be a {@link JSONObject}, what goes inside is completely up to you.</p> * * <p>What being said, please ensure not to accidentally override any keys * that others may have set on the object. This can be done by name spacing * your keys. Currently eTilbudsavis are using the <b>{@code eta_}</b> name space for * our keys.</p> * * @return A {@link JSONObject} */ public JSONObject getMeta() { if (mMeta == null) { return new JSONObject(); } else { try { return new JSONObject(mMeta); } catch (JSONException e) { EtaLog.e(TAG, e.getMessage(), e); } } return new JSONObject(); } /** * Set any meta information you may wish on the {@link Shoppinglist}. * * <p><b>important:</b> to avoid deleting others keys by getting old meta * information from {@link Shoppinglist#getMeta()}</p> * * <p>Meta doesn't currently have any restrictions other that it must * be a {@link JSONObject}, what goes inside is completely up to you.</p> * * <p>What being said, please ensure not to accidentally override any keys * that others may have set on the object. This can be done by name spacing * your keys. Currently eTilbudsavis are using the <b>{@code eta_}</b> name space for * our keys.</p> * * @param meta The new meta date to use * @return This object */ public Shoppinglist setMeta(JSONObject meta) { mMeta = meta == null ? "{}" : meta.toString(); return this; } /** * Get the {@link Share owner} of the {@link Shoppinglist} * @return A {@link Share} that is the owner, or {@code null} */ public Share getOwner() { for (Share s : mShares.values()) { if (s.getAccess().equals(Share.ACCESS_OWNER)) return s; } return null; } /** * Get the id, of the user that has this {@link Shoppinglist}. * <p>This is mostly a use case when storing the item in a DB, where several * users can have access to the same item (same {@link Shoppinglist#getId()}.</p> * @return A user id */ public int getUserId() { return mUserId; } /** * Set the id of the user, that this {@link Shoppinglist} is associated with. * <p>This is mostly a use case when storing the item in a DB, where several * users can have access to the same item (same {@link Shoppinglist#getId()}.</p> * @param userId An id of a user * @return This object */ public Shoppinglist setUserId(int userId) { mUserId = userId; return this; } public int getState() { return mSyncState; } public Shoppinglist setState(int state) { mSyncState = state; return this; } /** * Compare method, that uses the {@link Shoppinglist#getName() name} * to compare two lists. */ public int compareTo(Shoppinglist another) { if (another == null) return -1; String t1 = getName(); String t2 = another.getName(); if (t1 == null || t2 == null) { return t1 == null ? (t2 == null ? 0 : 1) : -1; } //ascending order return t1.compareToIgnoreCase(t2); } /** * Compare object, that uses {@link Shoppinglist#getName() name} * to compare two lists. */ public static Comparator<Shoppinglist> NAME_COMPARATOR = new Comparator<Shoppinglist>() { public int compare(Shoppinglist item1, Shoppinglist item2) { if (item1 == null || item2 == null) { return item1 == null ? (item2 == null ? 0 : 1) : -1; } else { String t1 = item1.getName(); String t2 = item2.getName(); if (t1 == null || t2 == null) { return t1 == null ? (t2 == null ? 0 : 1) : -1; } //ascending order return t1.compareToIgnoreCase(t2); } } }; public static Parcelable.Creator<Shoppinglist> CREATOR = new Parcelable.Creator<Shoppinglist>() { public Shoppinglist createFromParcel(Parcel source) { return new Shoppinglist(source); } public Shoppinglist[] newArray(int size) { return new Shoppinglist[size]; } }; public boolean same(Object obj) { return compare(obj, false, false, false); } public boolean compare(Object obj, boolean modified, boolean syncState, boolean user) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Shoppinglist other = (Shoppinglist) obj; if (mAccess == null) { if (other.mAccess != null) return false; } else if (!mAccess.equals(other.mAccess)) return false; if (mErn == null) { if (other.mErn != null) return false; } else if (!mErn.equals(other.mErn)) return false; if (mMeta == null) { if (other.mMeta != null) return false; } else if (!mMeta.equals(other.mMeta)) return false; if (modified) { if (mModified == null) { if (other.mModified != null) return false; } else if (!mModified.equals(other.mModified)) return false; } if (mName == null) { if (other.mName != null) return false; } else if (!mName.equals(other.mName)) return false; if (mPrevId == null) { if (other.mPrevId != null) return false; } else if (!mPrevId.equals(other.mPrevId)) return false; if (mShares == null) { if (other.mShares != null) return false; } else if (!mShares.equals(other.mShares)) return false; if (syncState) { if (mSyncState != other.mSyncState) return false; } if (mType == null) { if (other.mType != null) return false; } else if (!mType.equals(other.mType)) return false; if (user) { if (mUserId != other.mUserId) return false; } return true; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((mAccess == null) ? 0 : mAccess.hashCode()); result = prime * result + ((mErn == null) ? 0 : mErn.hashCode()); result = prime * result + ((mMeta == null) ? 0 : mMeta.hashCode()); result = prime * result + ((mModified == null) ? 0 : mModified.hashCode()); result = prime * result + ((mName == null) ? 0 : mName.hashCode()); result = prime * result + ((mPrevId == null) ? 0 : mPrevId.hashCode()); result = prime * result + ((mShares == null) ? 0 : mShares.hashCode()); result = prime * result + mSyncState; result = prime * result + ((mType == null) ? 0 : mType.hashCode()); result = prime * result + mUserId; return result; } @Override public boolean equals(Object obj) { return compare(obj, true, true, true); } private Shoppinglist(Parcel in) { this.mErn = in.readString(); this.mName = in.readString(); this.mAccess = in.readString(); long tmpMModified = in.readLong(); this.mModified = tmpMModified == -1 ? null : new Date(tmpMModified); this.mPrevId = in.readString(); this.mType = in.readString(); this.mMeta = in.readString(); this.mShares = (HashMap<String, Share>) in.readSerializable(); this.mUserId = in.readInt(); this.mSyncState = in.readInt(); } public int describeContents() { return 0; } public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.mErn); dest.writeString(this.mName); dest.writeString(this.mAccess); dest.writeLong(mModified != null ? mModified.getTime() : -1); dest.writeString(this.mPrevId); dest.writeString(this.mType); dest.writeString(this.mMeta); dest.writeSerializable(this.mShares); dest.writeInt(this.mUserId); dest.writeInt(this.mSyncState); } }