package com.mapzen.pelias; import com.mapzen.pelias.widget.AutoCompleteItem; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.database.Cursor; import android.database.MatrixCursor; import android.os.Parcel; import android.util.Log; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import static android.provider.BaseColumns._ID; /** * Used by {@link PeliasSearchView} to facilitate storing/retrieving saved search results. */ public final class SavedSearch { public static final String TAG = SavedSearch.class.getSimpleName(); public static final int DEFAULT_SIZE = 3; public static final int MAX_ENTRIES = 10; public static final String SEARCH_TERM = "search_term"; public static final String PAYLOAD = "payload"; public static final String[] COLUMNS = { _ID, SEARCH_TERM }; public static final String JSON_STRING_EMPTY_ARRAY = "[]"; private LinkedList<Member> store = new LinkedList<>(); /** * Helps with serializing/deserializing search terms. */ public class Member { private String term; private Parcel payload; /** * Constructs a new object with the given term and payload. */ public Member(String term, Parcel payload) { this.term = term; this.payload = payload; } /** * Constructs a new object from a search term. */ public Member(String term) { this.term = term; } /** * Returns the search term. */ public String getTerm() { return term; } /** * Returns the parcelled search term. */ public Parcel getPayload() { return payload; } /** * Returns a json representation of the search term. */ public JSONObject toJson() { JSONObject jsonObject = null; try { jsonObject = new JSONObject(); jsonObject.put(SEARCH_TERM, getTerm()); Parcel payload = getPayload(); if (payload != null) { jsonObject.put(PAYLOAD, new String(payload.marshall(), "ISO-8859-1")); } } catch (UnsupportedEncodingException | JSONException e) { Log.e(TAG, "Unable to convert member to JSON", e); } return jsonObject; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Member member = (Member) o; return term.equals(member.term); } @Override public int hashCode() { int result = term.hashCode(); if (payload != null) { result = 31 * result + payload.hashCode(); } return result; } } /** * Stores the term and payload. Creates a {@link Member} object from the term and payload and * inserts it at the beginning of the linked list. Also truncates the number of stored terms so * that it is not greater than {@link MAX_ENTRIES} */ public int store(String term, Parcel payload) { truncate(); Member member = new Member(term, payload); store.remove(member); store.addFirst(member); return 0; } /** * Stores the term. Creates a {@link Member} object from the term and inserts it at the beginning * of the linked list. Also truncates the number of stored terms so that it is not greater than * {@link MAX_ENTRIES} */ public int store(String term) { return store(term, null); } /** * Returns the search term at the given index. */ public Member get(int i) { return store.get(i); } /** * Returns the store's iterator. */ public Iterator<Member> getIterator() { return getSubIterator(DEFAULT_SIZE); } /** * Returns the store's iterator if the store's size is smaller than the requested size, otherwise * returns an iterator for a sublist of the store. */ public Iterator<Member> getSubIterator(int size) { if (store.size() == 0 || store.size() < size) { return store.iterator(); } return store.subList(0, size).iterator(); } /** * Clears the store. */ public void clear() { store.clear(); } /** * Returns whether or not the store is empty. */ public boolean isEmpty() { return store.size() == 0; } /** * Returns a json string for the store. */ public String serialize() { JSONArray jsonArray = new JSONArray(); for (Member member : store) { jsonArray.put(member.toJson()); } return jsonArray.toString(); } private boolean isEmptyArrayString(String serializedString) { return serializedString.equals(JSON_STRING_EMPTY_ARRAY); } /** * Populates the store with the values of the serialized json string. */ public void deserialize(String serializedSavedSearch) { if (serializedSavedSearch == null || serializedSavedSearch.isEmpty() || isEmptyArrayString( serializedSavedSearch)) { return; } store.clear(); JSONArray jsonArray; try { jsonArray = new JSONArray(serializedSavedSearch); for (int i = 0; i < jsonArray.length(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); String term = jsonObject.getString(SEARCH_TERM); Parcel payload = null; if (jsonObject.has(PAYLOAD)) { payload = Parcel.obtain(); String rawPayload = jsonObject.getString(PAYLOAD); payload.unmarshall(rawPayload.getBytes("ISO-8859-1"), 0, rawPayload.getBytes("ISO-8859-1").length); payload.setDataPosition(0); } store.add(new Member(term, payload)); } } catch (UnsupportedEncodingException | JSONException e) { Log.e(TAG, "Unable to deserialize saved search terms", e); } } /** * Returns a {@link Cursor} for the store. */ public Cursor getCursor() { final MatrixCursor cursor = new MatrixCursor(COLUMNS); for (int i = 0; i < store.size(); i++) { cursor.addRow(new Object[] { i, store.get(i).getTerm() }); } return cursor; } /** * Returns a list of saved search terms (text only). */ public List<String> getTerms() { final ArrayList<String> terms = new ArrayList<>(); for (Member member : store) { terms.add(member.getTerm()); } return terms; } /** * Returns a list of {@link AutoCompleteItem} objects with optional payload. */ public List<AutoCompleteItem> getItems() { final ArrayList<AutoCompleteItem> items = new ArrayList<>(); for (Member member : store) { final String term = member.getTerm(); final Parcel parcel = member.getPayload(); if (parcel != null) { items.add(new AutoCompleteItem(parcel)); } else { items.add(new AutoCompleteItem(term)); } } return items; } private void truncate() { if (store.size() >= MAX_ENTRIES) { store.removeLast(); } } /** * Returns the size of the store. */ public int size() { return store.size(); } }