Java tutorial
/* 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; } } }