de.azapps.mirakel.model.list.ListMirakel.java Source code

Java tutorial

Introduction

Here is the source code for de.azapps.mirakel.model.list.ListMirakel.java

Source

/*******************************************************************************
 * Mirakel is an Android App for managing your ToDo-Lists
 *
 * Copyright (c) 2013-2014 Anatolij Zelenin, Georg Semmler.
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package de.azapps.mirakel.model.list;

import android.content.ContentValues;
import android.content.CursorLoader;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel;
import android.support.annotation.NonNull;

import com.google.common.base.Optional;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;

import de.azapps.mirakel.DefinitionsHelper.SYNC_STATE;
import de.azapps.mirakel.helper.MirakelCommonPreferences;
import de.azapps.mirakel.helper.MirakelModelPreferences;
import de.azapps.mirakel.helper.MirakelPreferences;
import de.azapps.mirakel.helper.UndoHistory;
import de.azapps.mirakel.model.DatabaseHelper;
import de.azapps.mirakel.model.MirakelInternalContentProvider;
import de.azapps.mirakel.model.ModelBase;
import de.azapps.mirakel.model.R;
import de.azapps.mirakel.model.account.AccountMirakel;
import de.azapps.mirakel.model.query_builder.MirakelQueryBuilder;
import de.azapps.mirakel.model.query_builder.MirakelQueryBuilder.Operation;
import de.azapps.mirakel.model.query_builder.MirakelQueryBuilder.Sorting;
import de.azapps.mirakel.model.task.Task;

import static com.google.common.base.Optional.absent;
import static com.google.common.base.Optional.of;

/**
 * @author az
 */
public class ListMirakel extends ListBase {

    public static class ListAlreadyExistsException extends Exception {
        public ListAlreadyExistsException(String detailMessage) {
            super(detailMessage);
        }
    }

    public static final String[] allColumns = { ModelBase.ID, ModelBase.NAME, SORT_BY_FIELD,
            DatabaseHelper.CREATED_AT, DatabaseHelper.UPDATED_AT, DatabaseHelper.SYNC_STATE_FIELD, LFT, RGT, COLOR,
            ACCOUNT_ID };

    public enum SORT_BY {
        OPT, DUE, PRIO, ID, REVERT_DEFAULT;

        public short getShort() {
            switch (this) {
            case OPT:
                return 0;
            case DUE:
                return 1;
            case PRIO:
                return 2;
            case ID:
                return 3;
            case REVERT_DEFAULT:
                return 4;
            default:
                throw new IllegalArgumentException("Unkown SORT_BY type " + this.toString());
            }
        }

        public static SORT_BY fromShort(final short s) {
            switch (s) {
            case 0:
                return OPT;
            case 1:
                return DUE;
            case 2:
                return PRIO;
            case 3:
                return ID;
            case 4:
                return REVERT_DEFAULT;
            default:
                throw new IllegalArgumentException("Cannot transform " + s + " to SORT_BY");
            }
        }
    }

    public static final Uri URI = MirakelInternalContentProvider.LIST_URI;

    // private static final String TAG = "ListMirakel";

    public static final String TABLE = "lists";

    private static final String TAG = "ListMirakel";

    protected Uri getUri() {
        return URI;
    }

    @NonNull
    public MirakelQueryBuilder addSortBy(final MirakelQueryBuilder qb) {
        return addSortBy(qb, getSortBy(), getId());
    }

    @NonNull
    public static MirakelQueryBuilder addSortBy(final MirakelQueryBuilder qb, final SORT_BY sorting,
            final long listId) {
        final String dueSort = "CASE WHEN (" + Task.DUE
                + " IS NULL) THEN datetime('now','+50 years') ELSE datetime(" + Task.DUE
                + ",'unixepoch','localtime','start of day') END ";
        switch (sorting) {
        case PRIO:
            qb.sort(Task.PRIORITY, Sorting.DESC);
            break;
        case OPT:
            qb.sort(Task.DONE, Sorting.ASC);
            qb.sort(dueSort, Sorting.ASC);
            qb.sort(Task.PRIORITY, Sorting.DESC);
            break;
        case DUE:
            qb.sort(Task.DONE, Sorting.ASC);
            qb.sort(dueSort, Sorting.ASC);
            break;
        case REVERT_DEFAULT:
            qb.sort(Task.PRIORITY, Sorting.DESC);
            qb.sort(dueSort, Sorting.ASC);
            //$FALL-THROUGH$
        default:
            qb.sort(Task.ID, Sorting.ASC);
        }
        if (listId < 0) {
            qb.sort(Task.LIST_ID, Sorting.ASC);
        }
        return qb;
    }

    /**
     * Get all Lists in the Database
     *
     * @return List of Lists
     */
    @NonNull
    public static List<ListMirakel> all() {
        return all(true);
    }

    @NonNull
    public static List<ListMirakel> all(final boolean withSpecial) {
        final List<ListMirakel> lists = new ArrayList<>();
        if (withSpecial) {
            lists.addAll(SpecialList.allSpecial());
        }
        String[] cols = new String[allColumns.length + 1];
        for (int i = 0; i < allColumns.length; i++) {
            cols[i] = "n." + allColumns[i];
        }
        cols[allColumns.length] = "COUNT(*)-1 AS level";
        final Cursor cursor = query(
                MirakelInternalContentProvider.LISTS_SORT_URI, cols, "n." + LFT + " BETWEEN p." + LFT + " AND p."
                        + RGT + " " + " AND NOT n." + DatabaseHelper.SYNC_STATE_FIELD + "=" + SYNC_STATE.DELETE,
                null, "n." + LFT);
        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            lists.add(new ListMirakel(cursor));
            cursor.moveToNext();
        }
        cursor.close();
        return lists;
    }

    public static List<ListMirakel> cursorToList(Cursor c) {
        final List<ListMirakel> ret = new ArrayList<>();
        if (c.moveToFirst()) {
            do {
                ret.add(new ListMirakel(c));
            } while (c.moveToNext());
        }
        c.close();
        return ret;
    }

    @NonNull
    private static MirakelQueryBuilder getBasicMQB() {
        return new MirakelQueryBuilder(context)
                .and(DatabaseHelper.SYNC_STATE_FIELD, Operation.NOT_EQ, SYNC_STATE.DELETE.toString())
                .sort(LFT, Sorting.ASC);
    }

    @NonNull
    public static CursorLoader allWithSpecialCursorLoader() {
        return new MirakelQueryBuilder(context)
                .toCursorLoader(MirakelInternalContentProvider.LIST_WITH_SPECIAL_URI);
    }

    @NonNull
    public static android.support.v4.content.CursorLoader allWithSpecialSupportCursorLoader() {
        return new MirakelQueryBuilder(context)
                .toSupportCursorLoader(MirakelInternalContentProvider.LIST_WITH_SPECIAL_URI);
    }

    @NonNull
    public static Cursor getAllCursor() {
        return getBasicMQB().query(MirakelInternalContentProvider.LIST_URI);
    }

    public static long count() {
        return new MirakelQueryBuilder(context).count(URI);
    }

    /**
     * Create a List from a Cursor
     *
     * @param c cursor
     */
    public ListMirakel(final Cursor c) {
        super(c.getLong(c.getColumnIndex(ID)), c.getString(c.getColumnIndex(NAME)),
                SORT_BY.fromShort(c.getShort(c.getColumnIndex(SORT_BY_FIELD))),
                c.getString(c.getColumnIndex(DatabaseHelper.CREATED_AT)),
                c.getString(c.getColumnIndex(DatabaseHelper.UPDATED_AT)),
                SYNC_STATE.valueOf(c.getShort(c.getColumnIndex(DatabaseHelper.SYNC_STATE_FIELD))),
                c.getInt(c.getColumnIndex(LFT)), c.getInt(c.getColumnIndex(RGT)), c.getInt(c.getColumnIndex(COLOR)),
                c.getInt(c.getColumnIndex(ACCOUNT_ID)));
    }

    @NonNull
    public static Optional<ListMirakel> findByName(final String name) {
        return findByName(name, null);
    }

    @NonNull
    public static Optional<ListMirakel> findByName(final String name, final AccountMirakel account) {
        MirakelQueryBuilder qb = new MirakelQueryBuilder(context).and(NAME, Operation.EQ, name);
        if (account != null) {
            qb.and(ACCOUNT_ID, Operation.EQ, account);
        }
        return qb.get(ListMirakel.class);
    }

    // Static Methods

    @NonNull
    public static ListMirakel getInboxList(final AccountMirakel account) {
        final Optional<ListMirakel> l = new MirakelQueryBuilder(context)
                .and(NAME, Operation.EQ, context.getString(R.string.inbox)).and(ACCOUNT_ID, Operation.EQ, account)
                .get(ListMirakel.class);
        if (l.isPresent()) {
            return l.get();
        }
        try {
            return newList(context.getString(R.string.inbox), SORT_BY.OPT, account);
        } catch (final ListAlreadyExistsException e) {
            // WTF? This could never happen (theoretically)
            throw new RuntimeException("getInboxList() failed somehow", e);
        }
    }

    @NonNull
    public static Optional<ListMirakel> get(final long listId) {
        if (listId < 0L) {
            final Optional<SpecialList> specialList = SpecialList.getSpecial(-listId);
            if (specialList.isPresent()) {
                return of((ListMirakel) specialList.get());
            } else {
                return absent();
            }
        }
        return new MirakelQueryBuilder(context).get(ListMirakel.class, listId);
    }

    public static void setDefaultAccount(@NonNull final AccountMirakel account) {
        final ContentValues v = new ContentValues();
        v.put(ACCOUNT_ID, account.getId());
        update(URI, v, null, null);
    }

    public static List<ListMirakel> getListsForAccount(final AccountMirakel account) {
        if ((account == null) || !account.isEnabled()) {
            return new ArrayList<>(0);
        }
        return new MirakelQueryBuilder(context)
                .and(DatabaseHelper.SYNC_STATE_FIELD, Operation.NOT_EQ, SYNC_STATE.DELETE.toInt())
                .and(ACCOUNT_ID, Operation.EQ, account).getList(ListMirakel.class);
    }

    @NonNull
    private static ListMirakel safeNewList(String name, final int iteration) {
        final ListMirakel listMirakel;
        try {
            if (iteration > 0) {
                name += "_" + iteration;
            }
            listMirakel = ListMirakel.newList(name, SORT_BY.OPT);
        } catch (final ListMirakel.ListAlreadyExistsException e) {
            return safeNewList(name, iteration + 1);
        }
        return listMirakel;
    }

    @NonNull
    public static ListMirakel saveNewList(final String name) {
        return safeNewList(name, 0);
    }

    /**
     * Create and insert a new List
     *
     * @param name
     *            Name of the List
     * @param sort_by
     *            the default sorting
     * @return new List
     */
    @NonNull
    private static ListMirakel newList(final String name, final SORT_BY sort_by) throws ListAlreadyExistsException {
        return newList(name, sort_by, MirakelModelPreferences.getDefaultAccount());
    }

    @NonNull
    public static ListMirakel newList(final String name, final SORT_BY sort_by, final AccountMirakel account)
            throws ListAlreadyExistsException {
        final String now = new SimpleDateFormat(context.getString(R.string.dateTimeFormat), Locale.US)
                .format(new Date());
        final ListMirakel l = new ListMirakel(0, name, sort_by, now, now, SYNC_STATE.ADD, 0, 0, 0, account);
        final ListMirakel newList = l.create();
        UndoHistory.logCreate(newList, context);
        return newList;
    }

    @NonNull
    public static Optional<ListMirakel> getByName(final String name, final AccountMirakel accountMirakel) {
        return new MirakelQueryBuilder(context).and(ListBase.NAME, Operation.EQ, name)
                .and(ListMirakel.ACCOUNT_ID, Operation.EQ, accountMirakel).get(ListMirakel.class);
    }

    @NonNull
    private ListMirakel create() throws ListAlreadyExistsException {
        final Optional<ListMirakel> listMirakel = getByName(getName(), getAccount());
        if (listMirakel.isPresent()) {
            throw new ListAlreadyExistsException(
                    "List" + listMirakel.get().getName() + " already exists:" + listMirakel.get().getId());
        }
        final ContentValues values = new ContentValues();
        values.put(ModelBase.NAME, getName());
        values.put(ACCOUNT_ID, getAccount().getId());
        values.put(SORT_BY_FIELD, getSortBy().getShort());
        values.put(DatabaseHelper.SYNC_STATE_FIELD, SYNC_STATE.ADD.toInt());
        values.put(DatabaseHelper.CREATED_AT,
                new SimpleDateFormat(context.getString(R.string.dateTimeFormat), Locale.US).format(new Date()));
        values.put(DatabaseHelper.UPDATED_AT,
                new SimpleDateFormat(context.getString(R.string.dateTimeFormat), Locale.US).format(new Date()));
        values.put(RGT, 0);
        values.put(LFT, 0);
        MirakelInternalContentProvider.withTransaction(new MirakelInternalContentProvider.DBTransaction() {
            @Override
            public void exec() {
                setId(insert(URI, values));
                // Dirty workaround
                final Cursor c = new MirakelQueryBuilder(context).select("MAX(" + RGT + ')').query(URI);
                c.moveToFirst();
                final int maxRGT = c.getInt(0);
                final ContentValues cv = new ContentValues();
                cv.put(LFT, maxRGT + 1);
                cv.put(RGT, maxRGT + 2);
                update(URI, cv, ModelBase.ID + '=' + getId(), null);
                c.close();
            }
        });
        final ListMirakel newList = get(getId()).get();
        UndoHistory.logCreate(newList, context);
        return newList;
    }

    /**
     * Use this function only if you know what you are doing
     * @param el Json Object
     * @return List
     */
    @NonNull
    public static ListMirakel unsafeParseJson(final JsonObject el) {
        ListMirakel t = null;
        final JsonElement id = el.get("id");
        if (id != null) {
            // use old List from db if existing
            t = ListMirakel.get(id.getAsInt()).or(new ListMirakel());
        }
        JsonElement j = el.get("name");
        if (j != null) {
            t.setName(j.getAsString());
        }
        j = el.get("lft");
        if (j != null) {
            t.setLft(j.getAsInt());
        }
        j = el.get("rgt");
        if (j != null) {
            t.setRgt(j.getAsInt());
        }
        j = el.get("lft");
        if (j != null) {
            t.setLft(j.getAsInt());
        }
        j = el.get("updated_at");
        if (j != null) {
            t.setUpdatedAt(j.getAsString().replace(":", ""));
        }
        j = el.get("sort_by");
        if (j != null && j.getAsJsonPrimitive().isNumber()) {
            t.setSortBy(SORT_BY.fromShort(j.getAsShort()));
        }
        j = el.get("sync_state");
        if ((j != null) && j.getAsJsonPrimitive().isNumber()) {
            t.setSyncState(SYNC_STATE.valueOf((short) j.getAsFloat()));
        }
        j = el.get("color");
        if ((j != null) && j.getAsJsonPrimitive().isNumber()) {
            t.setColor(j.getAsInt());
        }
        return t;
    }

    @NonNull
    public static ListMirakel safeFirst() {
        Optional<ListMirakel> s = new MirakelQueryBuilder(context)
                .and(DatabaseHelper.SYNC_STATE_FIELD, Operation.NOT_EQ, SYNC_STATE.DELETE.toInt())
                .sort(LFT, Sorting.ASC).get(ListMirakel.class);
        if (!s.isPresent()) {
            return getInboxList(MirakelModelPreferences.getDefaultAccount());
        } else {
            return s.get();
        }
    }

    @NonNull
    public static ListMirakel safeGet(final int listId) {
        Optional<ListMirakel> l = get(listId);
        if (!l.isPresent()) {
            return safeFirst();
        } else {
            return l.get();
        }
    }

    protected ListMirakel() {
        super();
    }

    public ListMirakel(final long id, @NonNull final String name, @NonNull final SORT_BY sort_by,
            @NonNull final String created_at, @NonNull final String updated_at,
            @NonNull final SYNC_STATE sync_state, final int lft, final int rgt, final int color,
            @NonNull final AccountMirakel account) {
        super(id, name, sort_by, created_at, updated_at, sync_state, lft, rgt, color, account);
    }

    protected ListMirakel(final long id, @NonNull final String name, @NonNull final SORT_BY sort_by,
            @NonNull final String created_at, @NonNull final String updated_at,
            @NonNull final SYNC_STATE sync_state, final int lft, final int rgt, final int color,
            final int account) {
        super(id, name, sort_by, created_at, updated_at, sync_state, lft, rgt, color, account);
    }

    /**
     * Count the tasks of that list
     *
     * @return
     */
    public long countTasks() {
        MirakelQueryBuilder qb;
        if (getId() < 0) {
            try {
                qb = this.getWhereQueryForTasks();
            } catch (ClassCastException e) {
                Optional<SpecialList> specialList = SpecialList.getSpecial(getId());
                qb = specialList.isPresent() ? specialList.get().getWhereQueryForTasks()
                        : new MirakelQueryBuilder(context);
            }
        } else {
            qb = new MirakelQueryBuilder(context).and(Task.LIST_ID, Operation.EQ, this);
        }
        qb.and(Task.DONE, Operation.EQ, false);
        return Task.addBasicFiler(qb).count(Task.URI);
    }

    public void destroy() {
        destroy(false);
    }

    /**
     * Delete a List from the Database
     *
     * @param force, do not respect sync_state
     */
    public void destroy(final boolean force) {
        if (!force) {
            UndoHistory.updateLog(this, context);
        }
        final long id = getId();
        if (id <= 0L) {
            final SpecialList slist = (SpecialList) this;
            slist.destroy();
            return;
        }
        MirakelInternalContentProvider.withTransaction(new MirakelInternalContentProvider.DBTransaction() {
            @Override
            public void exec() {
                if ((getSyncState() == SYNC_STATE.ADD) || force) {
                    delete(MirakelInternalContentProvider.TASK_URI, Task.LIST_ID + " = " + id, null);
                    delete(URI, ModelBase.ID + " = " + id, null);
                } else {
                    final ContentValues values = new ContentValues();
                    values.put(DatabaseHelper.SYNC_STATE_FIELD, SYNC_STATE.DELETE.toInt());
                    update(MirakelInternalContentProvider.TASK_URI, values, Task.LIST_ID + " = " + id, null);
                    update(URI, values, ModelBase.ID + '=' + id, null);
                }
                final ContentValues cv = new ContentValues();
                cv.put("TABLE", TABLE);
                update(MirakelInternalContentProvider.UPDATE_LIST_ORDER_URI, cv, LFT + '>' + getLft(), null);
            }
        });
    }

    public Optional<Task> getFirstTask() {
        return addSortBy(Task.addBasicFiler(getWhereQueryForTasks())).get(Task.class);
    }

    public void save() {
        save(true);
    }

    /**
     * Update the List in the Database
     *
     * @param log, save a undo_log
     *            The List
     */
    public void save(final boolean log) {
        final SharedPreferences.Editor editor = MirakelPreferences.getEditor();
        // TODO implement for specialLists
        if (getId() > 0) {
            MirakelInternalContentProvider.withTransaction(new MirakelInternalContentProvider.DBTransaction() {
                @Override
                public void exec() {
                    setSyncState(((getSyncState() == SYNC_STATE.ADD) || (getSyncState() == SYNC_STATE.IS_SYNCED))
                            ? getSyncState()
                            : SYNC_STATE.NEED_SYNC);
                    setUpdatedAt(
                            new SimpleDateFormat(context.getString(R.string.dateTimeFormat), Locale.getDefault())
                                    .format(new Date()));
                    ContentValues values = getContentValues();
                    if (log) {
                        UndoHistory.updateLog(ListMirakel.get(getId()).get(), context);
                    }
                    update(URI, values, ModelBase.ID + " = " + getId(), null);
                    values = new ContentValues();
                    values.put(DatabaseHelper.UPDATED_AT, new GregorianCalendar().getTimeInMillis() / 1000L);
                    values.put(DatabaseHelper.SYNC_STATE_FIELD, SYNC_STATE.NEED_SYNC.toInt());
                    update(MirakelInternalContentProvider.TASK_URI, values, Task.LIST_ID + "=?",
                            new String[] { String.valueOf(getId()) });
                }
            });
        }
        editor.commit();
    }

    /**
     * Get all Tasks
     *
     * @return
     */
    @NonNull
    public List<Task> tasks() {
        return Task.getTasks(this, getSortBy(), false);
    }

    /**
     * Get all Tasks
     *
     * @param showDone
     * @return
     */
    @NonNull
    public List<Task> tasks(final boolean showDone) {
        return Task.getTasks(this, getSortBy(), showDone);
    }

    @NonNull
    public MirakelQueryBuilder getTasksQueryBuilder() {
        if (getId() < 0L) {
            // We look like a List but we are better than one MUHAHA
            final Optional<SpecialList> specialListOptional = SpecialList.getSpecial(getId());
            if (specialListOptional.isPresent()) {
                final SpecialList specialList = specialListOptional.get();
                final MirakelQueryBuilder mirakelQueryBuilder = specialList.getWhereQueryForTasks();
                addSortBy(mirakelQueryBuilder);
                return mirakelQueryBuilder;
            } else {
                throw new RuntimeException("No such special list");
            }
        } else {
            return Task.getMirakelQueryBuilder(of(this));
        }
    }

    @NonNull
    public CursorLoader getTasksCursorLoader() {
        return getTasksQueryBuilder().toCursorLoader(MirakelInternalContentProvider.TASK_URI);
    }

    @NonNull
    public android.support.v4.content.CursorLoader getTasksSupportCursorLoader() {
        return getTasksQueryBuilder().toSupportCursorLoader(MirakelInternalContentProvider.TASK_URI);
    }

    @NonNull
    public String toJson() {
        String json = "{";
        json += "\"name\":\"" + getName() + "\",";
        json += "\"id\":" + getId() + ',';
        json += "\"created_at\":\"" + getCreatedAt() + "\",";
        json += "\"updated_at\":\"" + getName() + "\",";
        json += "\"lft\":" + getLft() + ',';
        json += "\"rgt\":" + getRgt() + ',';
        json += "\"sort_by\":" + getSortBy().getShort() + ',';
        json += "\"sync_state\":" + getSyncState().toInt() + ',';
        json += "\"color\":" + getColor();
        json += "}";
        return json;
    }

    @NonNull
    public MirakelQueryBuilder getWhereQueryForTasks() {
        final MirakelQueryBuilder qb = Task
                .addBasicFiler(new MirakelQueryBuilder(context).and(Task.LIST_ID, Operation.EQ, this));
        if (!MirakelCommonPreferences.showDoneMain()) {
            qb.and(Task.DONE, Operation.EQ, false);
        }
        return qb;
    }

    // Parcelable stuff

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(final Parcel dest, final int flags) {
        dest.writeInt(this.sortBy.ordinal());
        dest.writeString(this.createdAt);
        dest.writeString(this.updatedAt);
        dest.writeInt(this.syncState.ordinal());
        dest.writeInt(this.lft);
        dest.writeInt(this.rgt);
        dest.writeInt(this.color);
        dest.writeLong(this.accountID);
        dest.writeByte(isSpecial ? (byte) 1 : (byte) 0);
        dest.writeLong(this.getId());
        dest.writeString(this.getName());
    }

    private ListMirakel(Parcel in) {
        final int tmpSortBy = in.readInt();
        this.sortBy = SORT_BY.values()[tmpSortBy];
        this.createdAt = in.readString();
        this.updatedAt = in.readString();
        final int tmpSyncState = in.readInt();
        this.syncState = SYNC_STATE.values()[tmpSyncState];
        this.lft = in.readInt();
        this.rgt = in.readInt();
        this.color = in.readInt();
        this.accountID = in.readLong();
        this.isSpecial = in.readByte() != 0;
        this.setId(in.readLong());
        this.setName(in.readString());
    }

    public static final Creator<ListMirakel> CREATOR = new Creator<ListMirakel>() {
        @Override
        public ListMirakel createFromParcel(final Parcel source) {
            return new ListMirakel(source);
        }

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

}