org.totschnig.myexpenses.model.Category.java Source code

Java tutorial

Introduction

Here is the source code for org.totschnig.myexpenses.model.Category.java

Source

/*   This file is part of My Expenses.
 *   My Expenses 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
 *   (at your option) any later version.
 *
 *   My Expenses 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 My Expenses.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.totschnig.myexpenses.model;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.net.Uri;

import org.apache.commons.lang3.StringUtils;
import org.totschnig.myexpenses.provider.DbUtils;
import org.totschnig.myexpenses.provider.TransactionProvider;
import org.totschnig.myexpenses.util.AcraHelper;
import org.totschnig.myexpenses.util.Utils;

import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_LABEL;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_LABEL_NORMALIZED;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_PARENTID;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_ROWID;

//TODO implement complete DAO
//for the moment we only wrap calls to the content provider
public class Category extends Model {
    public final static String NO_CATEGORY_ASSIGNED_LABEL = ""; //emdash
    public String label;
    public Long parentId;

    /**
     * we currently do not need a full representation of a category as an object
     * when we create an instance with an id, we only want to alter its label
     * and are not interested in its parentId
     * when we create an instance with a parentId, it is a new instance
     * @param id
     * @param label
     * @param parentId
     */
    public Category(Long id, String label, Long parentId) {
        this.setId(id);
        this.label = StringUtils.strip(label);
        this.parentId = parentId;
    }

    public static final String[] PROJECTION = new String[] { KEY_ROWID, KEY_LABEL, KEY_PARENTID };
    public static final Uri CONTENT_URI = TransactionProvider.CATEGORIES_URI;

    /**
     * inserts a new category if id = 0, or alters an existing one if id != 0
     * @param id 0 if a new instance, database id otherwise
     * @param label
     * @param parentId a new instance is created under this parent, ignored for existing instances
     * @return id of new record, or -1, if it already exists
     */
    public static long write(long id, String label, Long parentId) {
        Uri uri = new Category(id, label, parentId).save();
        return uri == null ? -1 : Integer.valueOf(uri.getLastPathSegment());
    }

    /**
     * Looks for a cat with a label under a given parent
     * @param label
     * @param parentId
     * @return id or -1 if not found
     */
    public static long find(String label, Long parentId) {
        label = StringUtils.strip(label);
        String selection;
        String[] selectionArgs;
        if (parentId == null) {
            selection = KEY_PARENTID + " is null";
            selectionArgs = new String[] { label };
        } else {
            selection = KEY_PARENTID + " = ?";
            selectionArgs = new String[] { String.valueOf(parentId), label };
        }
        selection += " and " + KEY_LABEL + " = ?";
        Cursor mCursor = cr().query(CONTENT_URI, new String[] { KEY_ROWID }, selection, selectionArgs, null);
        if (mCursor.getCount() == 0) {
            mCursor.close();
            return -1;
        } else {
            mCursor.moveToFirst();
            long result = mCursor.getLong(0);
            mCursor.close();
            return result;
        }
    }

    public static boolean delete(long id) {
        return cr().delete(CONTENT_URI, KEY_PARENTID + " =  ?  OR " + KEY_ROWID + " = ?",
                new String[] { String.valueOf(id), String.valueOf(id) }) > 0;
    }

    @Override
    public Uri save() {
        ContentValues initialValues = new ContentValues();
        initialValues.put(KEY_LABEL, label);
        initialValues.put(KEY_LABEL_NORMALIZED, Utils.normalize(label));
        Uri uri;
        if (getId() == 0) {
            if (!isMain(parentId)) {
                uri = null;
                AcraHelper.report(new Exception("Attempt to store deep category hierarchy detected"));
            } else {
                initialValues.put(KEY_PARENTID, parentId);
                try {
                    uri = cr().insert(CONTENT_URI, initialValues);
                } catch (SQLiteConstraintException e) {
                    uri = null;
                }
            }
        } else {
            uri = CONTENT_URI.buildUpon().appendPath(String.valueOf(getId())).build();
            try {
                cr().update(CONTENT_URI.buildUpon().appendPath(String.valueOf(getId())).build(), initialValues,
                        null, null);
            } catch (SQLiteConstraintException e) {
                uri = null;
            }
        }
        return uri;
    }

    private static boolean isMain(Long parentId) {
        if (parentId == null) {
            return true;
        }
        Cursor mCursor = cr().query(CONTENT_URI, new String[] { KEY_PARENTID }, KEY_ROWID + " = ?",
                new String[] { String.valueOf(parentId) }, null);
        if (mCursor.getCount() == 0) {
            mCursor.close();
            return false;
        } else {
            mCursor.moveToFirst();
            long result = DbUtils.getLongOr0L(mCursor, 0);
            mCursor.close();
            return result == 0L;
        }
    }

    /**
     * How many subcategories under a given parent?
     * @param parentId
     * @return number of subcategories
     */
    public static int countSub(long parentId) {
        Cursor mCursor = cr().query(CONTENT_URI, new String[] { "count(*)" }, KEY_PARENTID + " = ?",
                new String[] { String.valueOf(parentId) }, null);
        if (mCursor.getCount() == 0) {
            mCursor.close();
            return 0;
        } else {
            mCursor.moveToFirst();
            int result = mCursor.getInt(0);
            mCursor.close();
            return result;
        }
    }

    public static boolean move(Long id, Long newParent) {
        if (id.equals(newParent)) {
            throw new IllegalStateException("Cannot move category to itself");
        }
        if (!isMain(newParent)) {
            throw new IllegalStateException("Cannot move to subcategory");
        }
        if (isMain(id) && countSub(id) > 0) {
            throw new IllegalStateException("Cannot move main category if it has children");
        }
        ContentValues values = new ContentValues();
        values.put(KEY_PARENTID, newParent);
        try {
            cr().update(CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).build(), values, null, null);
            return true;
        } catch (SQLiteConstraintException e) {
            return false;
        }
    }
}