ollie.Ollie.java Source code

Java tutorial

Introduction

Here is the source code for ollie.Ollie.java

Source

/*
 * Copyright (C) 2014 Michael Pardo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ollie;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.*;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.os.Build;
import android.provider.BaseColumns;
import android.support.v4.util.LruCache;
import android.util.Log;
import ollie.internal.AdapterHolder;
import ollie.internal.ModelAdapter;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;

public final class Ollie {
    public static final int DEFAULT_CACHE_SIZE = 1024;

    private static final String TAG = "Ollie";

    private static Context sContext;
    private static AdapterHolder sAdapterHolder;
    private static DatabaseHelper sDatabaseHelper;
    private static SQLiteDatabase sSQLiteDatabase;
    private static LruCache<String, Model> sCache;
    private static LogLevel sLogLevel = LogLevel.NONE;
    private static boolean sInitialized = false;

    /**
     * Controls the level of logging.
     */
    public enum LogLevel {
        /**
         * No logging.
         */
        NONE,
        /**
         * Log basic events.
         */
        BASIC,
        /**
         * Log all queries.
         */
        FULL;

        public boolean log(LogLevel logLevel) {
            return this.ordinal() >= logLevel.ordinal();
        }
    }

    private Ollie() {
    }

    // Public methods

    public static Builder with(Context context) {
        return new Builder(context);
    }

    /**
     * Initialize the database. Must be called before interacting with the database.
     *
     * @param context Context
     * @param name    The database name.
     * @param version The database version.
     */
    public static void init(Context context, String name, int version) {
        init(context, name, version, DEFAULT_CACHE_SIZE, LogLevel.NONE);
    }

    /**
     * Initialize the database. Must be called before interacting with the database.
     *
     * @param context   Context
     * @param name      The database name.
     * @param version   The database version.
     * @param cacheSize The cache size.
     */
    public static void init(Context context, String name, int version, int cacheSize) {
        init(context, name, version, cacheSize, LogLevel.NONE);
    }

    /**
     * Initialize the database. Must be called before interacting with the database.
     *
     * @param context  Context
     * @param name     The database name.
     * @param version  The database version.
     * @param logLevel The logging level.
     */
    public static void init(Context context, String name, int version, LogLevel logLevel) {
        init(context, name, version, DEFAULT_CACHE_SIZE, logLevel);
    }

    /**
     * Initialize the database. Must be called before interacting with the database.
     *
     * @param context   Context
     * @param name      The database name.
     * @param version   The database version.
     * @param cacheSize The cache size.
     * @param logLevel  The logging level.
     */
    public static void init(Context context, String name, int version, int cacheSize, LogLevel logLevel) {
        sLogLevel = logLevel;

        if (sInitialized) {
            if (sLogLevel.log(LogLevel.BASIC)) {
                Log.d(TAG, "Already initialized.");
            }
            return;
        }

        try {
            Class adapterClass = Class.forName(AdapterHolder.IMPL_CLASS_FQCN);
            sAdapterHolder = (AdapterHolder) adapterClass.newInstance();
        } catch (Exception e) {
            if (sLogLevel.log(LogLevel.BASIC)) {
                Log.e(TAG, "Failed to initialize.", e);
            }
        }

        sContext = context.getApplicationContext();
        sDatabaseHelper = new DatabaseHelper(sContext, name, version);
        sSQLiteDatabase = sDatabaseHelper.getWritableDatabase();
        sCache = new LruCache<String, Model>(cacheSize);

        sInitialized = true;
    }

    public static Context getContext() {
        return sContext;
    }

    public static SQLiteDatabase getDatabase() {
        return sSQLiteDatabase;
    }

    public static <T extends Model> String getTableName(Class<T> cls) {
        return sAdapterHolder.getModelAdapter(cls).getTableName();
    }

    // Convenience methods

    /**
     * Iterate over a cursor and load entities.
     *
     * @param cls    The model class.
     * @param cursor The result cursor.
     * @return The list of entities.
     */
    public static <T extends Model> List<T> processCursor(Class<T> cls, Cursor cursor) {
        final List<T> entities = new ArrayList<T>();
        try {
            Constructor<T> entityConstructor = cls.getConstructor();
            if (cursor.moveToFirst()) {
                do {
                    T entity = getEntity(cls, cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)));
                    if (entity == null) {
                        entity = entityConstructor.newInstance();
                    }

                    entity.load(cursor);
                    entities.add(entity);
                } while (cursor.moveToNext());
            }
        } catch (Exception e) {
            Log.e(TAG, "Failed to process cursor.", e);
        }

        return entities;
    }

    /**
     * Iterate over a cursor and load entities. Closes the cursor when finished.
     *
     * @param cls    The model class.
     * @param cursor The result cursor.
     * @return The list of entities.
     */
    public static <T extends Model> List<T> processAndCloseCursor(Class<T> cls, Cursor cursor) {
        List<T> entities = processCursor(cls, cursor);
        cursor.close();
        return entities;
    }

    // Finder methods

    static <D, S> TypeAdapter<D, S> getTypeAdapter(Class<D> cls) {
        return (TypeAdapter<D, S>) sAdapterHolder.getTypeAdapter(cls);
    }

    static List<? extends ModelAdapter> getModelAdapters() {
        return sAdapterHolder.getModelAdapters();
    }

    // Cache methods

    static synchronized <T extends Model> void putEntity(T entity) {
        if (entity.id != null) {
            sCache.put(getEntityIdentifier(entity.getClass(), entity.id), entity);
        }
    }

    static synchronized <T extends Model> T getEntity(Class<T> cls, long id) {
        return (T) sCache.get(getEntityIdentifier(cls, id));
    }

    static synchronized <T extends Model> void removeEntity(T entity) {
        sCache.remove(getEntityIdentifier(entity.getClass(), entity.id));
    }

    static synchronized <T extends Model> T getOrFindEntity(Class<T> cls, long id) {
        T entity = Ollie.getEntity(cls, id);
        if (entity == null) {
            entity = Model.find(cls, id);
        }
        return entity;
    }

    // Model adapter methods

    static synchronized <T extends Model> void load(T entity, Cursor cursor) {
        sAdapterHolder.getModelAdapter(entity.getClass()).load(entity, cursor);
    }

    static synchronized <T extends Model> Long save(T entity) {
        return sAdapterHolder.getModelAdapter(entity.getClass()).save(entity, sSQLiteDatabase);
    }

    static synchronized <T extends Model> void delete(T entity) {
        sAdapterHolder.getModelAdapter(entity.getClass()).delete(entity, sSQLiteDatabase);
    }

    // Private methods

    private static String getEntityIdentifier(Class<? extends Model> cls, long id) {
        return cls.getName() + "@" + id;
    }

    // Public classes

    public static final class Builder {
        private Context mContext;
        private String mName;
        private int mVersion;
        private int mCacheSize;
        private LogLevel mLogLevel;

        public Builder(Context context) {
            mContext = context;
            mName = context.getPackageName();
            mVersion = 1;
            mCacheSize = DEFAULT_CACHE_SIZE;
            mLogLevel = LogLevel.NONE;
        }

        public Builder setName(String name) {
            mName = name;
            return this;
        }

        public Builder setVersion(int version) {
            mVersion = version;
            return this;
        }

        public Builder setCacheSize(int cacheSize) {
            mCacheSize = cacheSize;
            return this;
        }

        public Builder setLogLevel(LogLevel logLevel) {
            mLogLevel = logLevel;
            return this;
        }

        public void init() {
            Ollie.init(mContext, mName, mVersion, mCacheSize, mLogLevel);
        }
    }

    // Private classes

    private static final class DatabaseHelper extends SQLiteOpenHelper {
        public DatabaseHelper(Context context, String name, int version) {
            super(context, name, sLogLevel.log(LogLevel.FULL) ? new LoggingCursorAdapter() : null, version);
        }

        @Override
        public void onOpen(SQLiteDatabase db) {
            executePragmas(db);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            executePragmas(db);
            executeCreate(db);
            executeMigrations(db, -1, db.getVersion());
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            executePragmas(db);
            executeCreate(db);
            executeMigrations(db, oldVersion, newVersion);
        }

        private void executePragmas(SQLiteDatabase db) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
                db.execSQL("PRAGMA foreign_keys=ON;");
            }
        }

        private void executeCreate(SQLiteDatabase db) {
            final List<String> tableDefinitions = new ArrayList<String>();
            for (ModelAdapter modelAdapter : sAdapterHolder.getModelAdapters()) {
                tableDefinitions.add(modelAdapter.getSchema());
            }

            db.beginTransaction();
            try {
                for (String tableDefinition : tableDefinitions) {
                    db.execSQL(tableDefinition);
                }
                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
            }
        }

        private boolean executeMigrations(SQLiteDatabase db, int oldVersion, int newVersion) {
            boolean migrationExecuted = false;
            final List<? extends Migration> migrations = sAdapterHolder.getMigrations();

            db.beginTransaction();
            try {
                for (Migration migration : migrations) {
                    if (migration.getVersion() > oldVersion && migration.getVersion() <= newVersion) {
                        for (String statement : migration.getStatements()) {
                            db.execSQL(statement);
                        }
                        migrationExecuted = true;
                    }
                }
            } finally {
                db.setTransactionSuccessful();
            }
            db.endTransaction();

            return migrationExecuted;
        }
    }

    private static final class LoggingCursorAdapter implements CursorFactory {
        @Override
        public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {
            Log.v(TAG, query.toString());
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                return new SQLiteCursor(db, driver, editTable, query);
            } else {
                return new SQLiteCursor(driver, editTable, query);
            }
        }
    }
}