com.money.manager.ex.MmxContentProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.money.manager.ex.MmxContentProvider.java

Source

/*
 * Copyright (C) 2012-2016 The Android Money Manager Ex Project Team
 *
 * 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 (at your option) 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 com.money.manager.ex;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
//import net.sqlcipher.database.SQLiteDatabase;
//import net.sqlcipher.database.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.util.SparseArrayCompat;
import android.text.TextUtils;

import com.money.manager.ex.budget.BudgetQuery;
import com.money.manager.ex.currency.CurrencyRepository;
import com.money.manager.ex.database.Dataset;
import com.money.manager.ex.database.DatasetType;
import com.money.manager.ex.database.MmxOpenHelper;
import com.money.manager.ex.database.QueryAccountBills;
import com.money.manager.ex.database.QueryAllData;
import com.money.manager.ex.database.QueryBillDeposits;
import com.money.manager.ex.database.QueryCategorySubCategory;
import com.money.manager.ex.database.QueryReportIncomeVsExpenses;
import com.money.manager.ex.database.SQLDataSet;
import com.money.manager.ex.database.ViewMobileData;
import com.money.manager.ex.datalayer.AccountRepository;
import com.money.manager.ex.datalayer.AccountTransactionRepository;
import com.money.manager.ex.datalayer.AssetClassRepository;
import com.money.manager.ex.datalayer.AssetClassStockRepository;
import com.money.manager.ex.datalayer.BudgetEntryRepository;
import com.money.manager.ex.datalayer.BudgetRepository;
import com.money.manager.ex.datalayer.CategoryRepository;
import com.money.manager.ex.datalayer.PayeeRepository;
import com.money.manager.ex.datalayer.RecurringTransactionRepository;
import com.money.manager.ex.datalayer.SplitCategoriesRepository;
import com.money.manager.ex.datalayer.SplitRecurringCategoriesRepository;
import com.money.manager.ex.datalayer.StockRepository;
import com.money.manager.ex.datalayer.SubcategoryRepository;
import com.money.manager.ex.datalayer.StockHistoryRepository;
import com.money.manager.ex.sync.SyncManager;

import java.util.Arrays;
import java.util.List;

import javax.inject.Inject;

import dagger.Lazy;
import timber.log.Timber;

/**
 * MmxContentProvider is the extension of the base class of Android
 * ContentProvider. Its purpose is to implement the read access and modify the
 * application data
 */
public class MmxContentProvider extends ContentProvider {

    // object definition for the call to check the content
    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    // object map for the definition of the objects referenced in the URI
    private static SparseArrayCompat<Object> mapContent = new SparseArrayCompat<>();
    private static String mAuthority;

    public MmxContentProvider() {
        super();

    }

    @Inject
    Lazy<MmxOpenHelper> openHelper;

    public static String getAuthority() {
        return mAuthority;
    }

    public static void setAuthority(String mAuthority) {
        MmxContentProvider.mAuthority = mAuthority;
    }

    @Override
    public boolean onCreate() {
        Context context = getContext();
        if (context == null)
            return false;

        setAuthority(context.getApplicationContext().getPackageName() + ".provider");

        List<Dataset> objMoneyManager = Arrays.asList(new AccountRepository(context),
                new AccountTransactionRepository(context), new AssetClassRepository(context),
                new AssetClassStockRepository(context), new BudgetEntryRepository(context),
                new BudgetRepository(context), new CategoryRepository(context), new CurrencyRepository(context),
                //            new InfoRepositorySql(context),
                new PayeeRepository(context), new RecurringTransactionRepository(context),
                new SplitCategoriesRepository(context), new SplitRecurringCategoriesRepository(context),
                new StockRepository(context), new StockHistoryRepository(context),
                new SubcategoryRepository(context), new QueryAccountBills(context),
                new QueryCategorySubCategory(context), new QueryAllData(context), new QueryBillDeposits(context),
                new QueryReportIncomeVsExpenses(context), new BudgetQuery(context), new ViewMobileData(context),
                new SQLDataSet());

        // Cycle all data sets for the composition of UriMatcher
        for (int i = 0; i < objMoneyManager.size(); i++) {
            // add URI
            sUriMatcher.addURI(getAuthority(), objMoneyManager.get(i).getBasepath(), i);
            // put map in the object being added in UriMatcher
            mapContent.put(i, objMoneyManager.get(i));
        }
        return false;
    }

    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        try {
            return query_internal(uri, projection, selection, selectionArgs, sortOrder);
        } catch (Exception e) {
            Timber.e(e, "content provider.query %s", uri);
        }
        return null;
    }

    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        Timber.d("Insert Uri: %s", uri);

        // find object from uri
        Object ret = getObjectFromUri(uri);
        long id = Constants.NOT_SET;
        String parse;

        if (Dataset.class.isInstance(ret)) {
            Dataset dataset = ((Dataset) ret);
            switch (dataset.getType()) {
            case TABLE:
                logTableInsert(dataset, values);

                //database.beginTransaction();
                try {
                    initializeDependencies();

                    id = openHelper.get().getWritableDatabase().insertOrThrow(dataset.getSource(), null, values);
                    //database.setTransactionSuccessful();
                } catch (Exception e) {
                    Timber.e(e, "inserting: %s", "insert");
                }
                parse = dataset.getBasepath() + "/" + id;
                break;
            default:
                throw new IllegalArgumentException("Type of dataset not supported for update");
            }
        } else {
            throw new IllegalArgumentException("Object ret of mapContent is not instance of dataset");
        }

        if (id > 0) {
            notifyChange(uri);
        }

        // return Uri with the primary key of the inserted record.
        return Uri.parse(parse);
    }

    @Override
    public int update(@NonNull Uri uri, ContentValues values, String whereClause, String[] whereArgs) {
        Timber.d("Update Uri: %s", uri);

        Object ret = getObjectFromUri(uri);

        initializeDependencies();

        SQLiteDatabase database = openHelper.get().getWritableDatabase();

        int rowsUpdate = 0;

        if (Dataset.class.isInstance(ret)) {
            Dataset dataset = ((Dataset) ret);
            switch (dataset.getType()) {
            case TABLE:
                logUpdate(dataset, values, whereClause, whereArgs);

                try {
                    rowsUpdate = database.update(dataset.getSource(), values, whereClause, whereArgs);
                } catch (Exception ex) {
                    Timber.e(ex, "updating: %s", "update");
                }
                break;
            default:
                throw new IllegalArgumentException("Type of dataset not supported for update");
            }
        } else {
            throw new IllegalArgumentException("Object ret of mapContent is not istance of dataset");
        }

        if (rowsUpdate > 0) {
            notifyChange(uri);
        }

        // return rows modified
        return rowsUpdate;
    }

    @Override
    public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
        Timber.d("Delete URI: %s", uri);

        // find object from uri
        Object ret = getObjectFromUri(uri);
        // safety control of having the where if not clean the table
        if (TextUtils.isEmpty(selection)) {
            throw new IllegalArgumentException("Delete not permitted because not define where clause");
            //            Log.e(LOGCAT, "Delete not permitted because not define where clause");
            //            return 0;
        }
        // take a database reference
        int rowsDelete = 0;

        if (Dataset.class.isInstance(ret)) {
            Dataset dataset = ((Dataset) ret);
            switch (dataset.getType()) {
            case TABLE:
                logDelete(dataset, selection, selectionArgs);
                try {
                    initializeDependencies();

                    rowsDelete = openHelper.get().getWritableDatabase().delete(dataset.getSource(), selection,
                            selectionArgs);

                    // committed
                    //if (BuildConfig.DEBUG) Log.d(LOGCAT, "database set transaction successful");
                    //database.setTransactionSuccessful();
                } catch (Exception e) {
                    Timber.e(e, "insert");
                }
                break;
            default:
                throw new IllegalArgumentException("Type of dataset not supported for delete");
            }
        } else {
            throw new IllegalArgumentException("Object ret of mapContent is not istance of dataset");
        }

        if (rowsDelete > 0)
            notifyChange(uri);

        return rowsDelete;
    }

    /**
     * Prepare statement SQL from data set object
     *
     * @param query SQL query
     * @param projection ?
     * @param selection ?
     * @param sortOrder field name for sort order
     * @return statement
     */
    public String prepareQuery(String query, String[] projection, String selection, String sortOrder) {
        String selectList, from, where = "", sort = "";

        // todo: use builder?
        //        SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
        //        SQLiteQueryBuilder.buildQueryString(false, )

        // compose select list
        if (projection == null) {
            selectList = "SELECT *";
        } else {
            selectList = "SELECT ";

            for (int i = 0; i < projection.length; i++) {
                if (i > 0) {
                    selectList += ", ";
                }
                selectList += projection[i];
            }
        }
        // FROM
        from = "FROM (" + query + ") T";
        // WHERE
        if (!TextUtils.isEmpty(selection)) {
            //            if (!selection.contains("WHERE")) {
            if (!selection.startsWith("WHERE")) {
                where += "WHERE";
            }
            where += " " + selection;
        }
        // compose sort
        if (!TextUtils.isEmpty(sortOrder)) {
            if (!sortOrder.contains("ORDER BY")) {
                sort += "ORDER BY ";
            }
            sort += " " + sortOrder;
        }
        // compose statement to return
        query = selectList + " " + from;
        // check where or sort not empty
        if (!TextUtils.isEmpty(where)) {
            query += " " + where;
        }
        if (!TextUtils.isEmpty(sort)) {
            query += " " + sort;
        }

        return query;
    }

    public Object getObjectFromUri(Uri uri) {
        int uriMatch = sUriMatcher.match(uri);
        //        Timber.d("Uri Match Result: %s", Integer.toString(uriMatch));

        // find key into hash map
        Object objectRet = mapContent.get(uriMatch);
        if (objectRet == null) {
            throw new IllegalArgumentException("Unknown URI for Update: " + uri);
        }

        return objectRet;
    }

    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    public void resetDatabase() {
        if (openHelper != null) {
            openHelper.get().close();
        }

        openHelper = null;
        initializeDependencies();
    }

    // Private

    private void initializeDependencies() {
        if (openHelper != null)
            return;

        MoneyManagerApplication.getApp().iocComponent.inject(this);
    }

    private void logTableInsert(Dataset dataset, ContentValues values) {
        String log = "INSERT INTO " + dataset.getSource();
        if (values != null) {
            log += " VALUES ( " + values.toString() + ")";
        }
        Timber.d(log);
    }

    private Cursor query_internal(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        Timber.d("Querying URI: %s", uri);

        // find object from uri
        Object sourceObject = getObjectFromUri(uri);

        initializeDependencies();

        SQLiteDatabase database = openHelper.get().getReadableDatabase();
        if (database == null) {
            Timber.e("Database could not be opened");
            return null;
        }

        Cursor cursor;

        // check type of instance data set
        if (Dataset.class.isInstance(sourceObject)) {
            Dataset dataset = ((Dataset) sourceObject);

            //            logQuery(dataset, projection, selection, selectionArgs, sortOrder);

            switch (dataset.getType()) {
            case QUERY:
                String query = prepareQuery(dataset.getSource(), projection, selection, sortOrder);
                cursor = database.rawQuery(query, selectionArgs);
                break;
            case SQL:
                cursor = database.rawQuery(selection, selectionArgs);
                break;
            case TABLE:
            case VIEW:
                SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
                queryBuilder.setTables(dataset.getSource());
                cursor = queryBuilder.query(database, projection, selection, selectionArgs, null, null, sortOrder);
                break;
            default:
                throw new IllegalArgumentException("Type of dataset not defined");
            }
        } else {
            throw new IllegalArgumentException("Object sourceObject of mapContent is not instance of dataset");
        }

        // notify listeners waiting for the data is ready
        cursor.setNotificationUri(getContext().getContentResolver(), uri);

        if (!cursor.isClosed()) {
            Timber.d("Rows returned: %d", cursor.getCount());
        }

        return cursor;
    }

    private void logQuery(Dataset dataset, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        // compose log verbose instruction
        String log;
        // compose log
        if (dataset.getType() == DatasetType.SQL) {
            log = selection;
        } else {
            if (projection != null) {
                log = "SELECT " + Arrays.asList(projection).toString();
            } else {
                log = "SELECT *";
            }
            log += " FROM " + dataset.getSource();
            if (!TextUtils.isEmpty(selection)) {
                log += " WHERE " + selection;
            }
            if (!TextUtils.isEmpty(sortOrder)) {
                log += " ORDER BY " + sortOrder;
            }
            if (selectionArgs != null) {
                log += "; ARGS=" + Arrays.asList(selectionArgs).toString();
            }
        }
        // log
        Timber.d(log);
    }

    private void logUpdate(Dataset dataset, ContentValues values, String whereClause, String[] whereArgs) {
        String log = "UPDATE " + dataset.getSource();
        // compose log verbose
        if (values != null) {
            log += " SET " + values.toString();
        }
        if (!TextUtils.isEmpty(whereClause)) {
            log += " WHERE " + whereClause;
        }
        if (whereArgs != null) {
            log += "; ARGS=" + Arrays.asList(whereArgs).toString();
        }

        // open transaction
        //database.beginTransaction();
        //        if (BuildConfig.DEBUG) Log.d(LOGCAT, "database begin transaction");

        Timber.d(log);
    }

    private void logDelete(Dataset dataset, String selection, String[] selectionArgs) {
        String log = "DELETE FROM " + dataset.getSource();
        // compose log verbose
        if (!TextUtils.isEmpty(selection)) {
            log += " WHERE " + selection;
        }
        if (selectionArgs != null) {
            log += "; ARGS=" + Arrays.asList(selectionArgs).toString();
        }
        // open transaction
        //database.beginTransaction();
        //                    if (BuildConfig.DEBUG) Log.d(LOGCAT, "database begin transaction");
        Timber.d(log);
    }

    private void notifyChange(Uri uri) {
        if (getContext() == null)
            return;

        // notify update. todo Do this also after changes via sqlite.
        getContext().getContentResolver().notifyChange(uri, null);
        // notify the sync that database has changed.
        new SyncManager(getContext()).dataChanged();
    }
}