// // Copyright (c) 2014 VK.com // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // package com.vk.sdk.api.model; import android.os.Parcel; import android.os.Parcelable; import com.vk.sdk.VKSdk; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.ListIterator; import java.util.regex.Pattern; /** * Universal data list for VK API. * This class is not thread-safe. * @param <T> type of stored values. * @see <a href="http://vk.com/dev/list">http://vk.com/dev/list</a> */ @SuppressWarnings({"unchecked", "UnusedDeclaration"}) public class VKList<T extends VKApiModel & Parcelable & Identifiable> extends VKApiModel implements java.util.List<T>,Parcelable { /** * The server did not return the count field. */ private final static int NO_COUNT = -1; /** * Decorated list */ private ArrayList<T> items = new ArrayList<T>(); /** * Field {@code count} which returned by server. */ private int count = NO_COUNT; /** * Creates empty list. */ public VKList() { } /** * Creates list and fills it according with given data. */ public VKList(java.util.List<? extends T> data) { assert data != null; items = new ArrayList<T>(data); } /** * Creates list and fills it according with data in {@code from}. * @param from an object that represents a list adopted in accordance with VK API format. You can use null. * @param clazz class represents a model that has a public constructor with {@link org.json.JSONObject} argument. */ public VKList(JSONObject from, Class<? extends T> clazz) { fill(from, clazz); } /** * Creates list and fills it according with data in {@code from}. * @param from an array of items in the list. You can use null. * @param clazz class represents a model that has a public constructor with {@link org.json.JSONObject} argument. */ public VKList(JSONArray from, Class<? extends T> clazz) { fill(from, clazz); } /** * Creates list and fills it according with data in {@code from}. * @param from an object that represents a list adopted in accordance with VK API format. You can use null. * @param creator interface implementation to parse objects. */ public VKList(JSONObject from, Parser<T> creator) { fill(from, creator); } /** * Creates list and fills it according with data in {@code from}. * @param from an array of items in the list. You can use null. * @param creator interface implementation to parse objects. */ public VKList(JSONArray from, Parser<T> creator) { fill(from, creator); } /** * Fills list according with data in {@code from}. * @param from an object that represents a list adopted in accordance with VK API format. You can use null. * @param clazz class represents a model that has a public constructor with {@link org.json.JSONObject} argument. */ public void fill(JSONObject from, Class<? extends T> clazz) { if (from.has("response")) { JSONArray array = from.optJSONArray("response"); if (array != null) { fill(array, clazz); } else { fill(from.optJSONObject("response"), clazz); } } else { fill(from, new ReflectParser<T>(clazz)); } } /** * Creates list and fills it according with data in {@code from}. * @param from an array of items in the list. You can use null. * @param clazz class represents a model that has a public constructor with {@link org.json.JSONObject} argument. */ public void fill(JSONArray from, Class<? extends T> clazz) { fill(from, new ReflectParser<T>(clazz)); } /** * Fills list according with data in {@code from}. * @param from an object that represents a list adopted in accordance with VK API format. You can use null. * @param creator interface implementation to parse objects. */ public void fill(JSONObject from, Parser<? extends T> creator) { if(from != null) { fill(from.optJSONArray("items"), creator); count = from.optInt("count", count); } } /** * Fills list according with data in {@code from}. * @param from an array of items in the list. You can use null. * @param creator interface implementation to parse objects. */ public void fill(JSONArray from, Parser<? extends T> creator) { if(from != null) { for(int i = 0; i < from.length(); i++) { try { T object = creator.parseObject(from.getJSONObject(i)); if(object != null) { items.add(object); } } catch (Exception e) { if (VKSdk.DEBUG) e.printStackTrace(); } } } } /** * Adds the element before the element with the specified id. * If an element with the specified id is not found, adds an element to the end of the list. * @param id element identifier to add element before it. * @param data element to add */ public void addBefore(int id, T data) { int size = size(); for(int i = 0; i < size; i++) { if(get(i).getId() > id || i == size - 1) { add(i, data); break; } } } /** * Adds the element after the element with the specified id. * If an element with the specified id is not found, adds an element to the end of the list. * @param id element identifier to add element after it. * @param data element to add */ public void addAfter(int id, T data) { int size = size(); for(int i = 0; i < size; i++) { if(get(i).getId() > id || i == size - 1) { add(i + 1, data); break; } } } /** * Returns element according with id. * If nothing found, returns null. */ public T getById(int id) { for(T item: this) { if(item.getId() == id) { return item; } } return null; } /** * Searches through the list of available items. <br /> * <br /> * The search will be carried out not by the content of characters per line, and the content of them in separate words. <br /> * <br /> * Search is not case sensitive. <br /> * <br /> * To support search class {@code T} must have overridden method {@link #toString()}, * search will be carried out exactly according to the result of calling this method. <br /> * <br /> * <br /> * Suppose there are elements in the list of contents: * <code><pre> * - Hello world * - Hello test * </pre></code> * In this case, the matches will be on search phrases {@code 'Hel'}, {@code 'Hello'}, {@code 'test'}, but not on {@code 'llo'}, {@code 'llo world'} * * @param query search query can not be equal to {@code null}, but can be an empty string. * @return created based on the search results new list. If no matches are found, the list will be empty. */ public VKList<T> search(String query) { VKList<T> result = new VKList<T>(); final Pattern pattern = Pattern.compile("(?i).*\\b" + query + ".*"); for (T item : this) { if (pattern.matcher(item.toString()).find()) { result.add(item); } } return result; } /** * Returns the return value of the field VK API {@code count}, if it has been returned, and the size of the list, if not. */ public int getCount() { return count != NO_COUNT ? count : size(); } @Override public void add(int location, T object) { items.add(location, object); } @Override public boolean add(T object) { return items.add(object); } @Override public boolean addAll(int location, Collection<? extends T> collection) { return items.addAll(location, collection); } @Override public boolean addAll(Collection<? extends T> collection) { return items.addAll(collection); } @Override public void clear() { items.clear(); } @Override public boolean contains(Object object) { return items.contains(object); } @Override public boolean containsAll(Collection<?> collection) { assert collection != null; return items.containsAll(collection); } @Override public boolean equals(Object object) { return ((Object) this).getClass().equals(object.getClass()) && items.equals(object); } @Override public T get(int location) { return items.get(location); } @Override public int indexOf(Object object) { return items.indexOf(object); } @Override public boolean isEmpty() { return items.isEmpty(); } @Override public Iterator<T> iterator() { return items.iterator(); } @Override public int lastIndexOf(Object object) { return items.lastIndexOf(object); } @Override public ListIterator<T> listIterator() { return items.listIterator(); } @Override public ListIterator<T> listIterator(int location) { return items.listIterator(location); } @Override public T remove(int location) { return items.remove(location); } @Override public boolean remove(Object object) { return items.remove(object); } @Override public boolean removeAll(Collection<?> collection) { assert collection != null; return items.removeAll(collection); } @Override public boolean retainAll(Collection<?> collection) { return items.retainAll(collection); } @Override public T set(int location, T object) { return items.set(location, object); } @Override public int size() { return items.size(); } @Override public java.util.List<T> subList(int start, int end) { return items.subList(start, end); } @Override public Object[] toArray() { return items.toArray(); } @Override public <T1> T1[] toArray(T1[] array) { assert array != null; return items.toArray(array); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(items.size()); for(T item: this) { dest.writeParcelable(item, flags); } dest.writeInt(this.count); } /** * Creates list from Parcel */ public VKList(Parcel in) { int size = in.readInt(); for(int i = 0; i < size; i++) { items.add( ((T) in.readParcelable(((Object) this).getClass().getClassLoader()))); } this.count = in.readInt(); } public static Creator<VKList> CREATOR = new Creator<VKList>() { public VKList createFromParcel(Parcel source) { return new VKList(source); } public VKList[] newArray(int size) { return new VKList[size]; } }; /** * Used when parsing the list objects as interator created from {@link org.json.JSONArray} a instances of items of the list. * @param <D> list item type. */ public static interface Parser<D> { /** * Creates a list item of its representation return VK API from {@link org.json.JSONArray} * @param source representation of the object in the format returned by VK API. * @return created element to add to the list. * @throws Exception if the exception is thrown, the element iterated this method will not be added to the list. */ D parseObject(JSONObject source) throws Exception; } /** * Parser list items using reflection mechanism. * To use an object class must have a public constructor that accepts {@link org.json.JSONObject}. * If, during the creation of the object constructor will throw any exception, the element will not be added to the list. * @param <D> list item type. */ public final static class ReflectParser<D extends VKApiModel> implements Parser<D> { private final Class<? extends D> clazz; public ReflectParser(Class<? extends D> clazz) { this.clazz = clazz; } @Override public D parseObject(JSONObject source) throws Exception { try { Constructor<? extends D> jsonConstructor = clazz.getConstructor(JSONObject.class); if (jsonConstructor != null) { return jsonConstructor.newInstance(source); } } catch (Exception ignored) { //Ignored. Try default constructor } return (D) clazz.newInstance().parse(source); } } @Override public VKApiModel parse(JSONObject response) throws JSONException { throw new JSONException("Operation is not supported while class is generic"); } }