All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codeslap.persistence.SqlPersistence Maven / Gradle / Ivy

/*
 * Copyright 2012 CodeSlap
 *
 * 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 com.codeslap.persistence;

import android.content.Context;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * @author cristian
 */
public class SqlPersistence {
    private final List> mSqliteList = new ArrayList>();
    private final List> mAutoIncrementList = new ArrayList>();
    private final List mManyToManyList = new ArrayList();
    private final List mHasManyList = new ArrayList();

    private final String mName;
    private final int mVersion;
    private final DbOpenHelper mOpenHelper;
    private final List mBeforeImporters = new ArrayList();
    private final List mAfterImporters = new ArrayList();

    public SqlPersistence(String name, int version, DbOpenHelper openHelper) {
        mName = name;
        mVersion = version;
        mOpenHelper = openHelper;
    }

    public String getName() {
        return mName;
    }

    public int getVersion() {
        return mVersion;
    }

    public DbOpenHelper getOpenHelper() {
        return mOpenHelper;
    }

    /**
     * Register one or more classes to be added to the Sqlite model. All classes should have an ID which will be treated
     * as autoincrement if possible. If your class has a field called id then it will be automatically
     * taken as an autoincrement primary key; if your primary key field has another name, use the {@link PrimaryKey}
     * which will also allow you to specify whether the field is autoincrement or not.
     *
     * @param classes a list of classes to register
     */
    public void match(Class... classes) {
        for (Class theClass : classes) {
            if (!mSqliteList.contains(theClass)) {
                mSqliteList.add(theClass);
                boolean isAutoincrement = true;
                Field pk = SQLHelper.getPrimaryKeyField(theClass);
                if (pk.getType() == String.class ||
                        pk.getType() == Boolean.class || pk.getType() == boolean.class ||
                        pk.getType() == Float.class || pk.getType() == float.class ||
                        pk.getType() == Double.class || pk.getType() == double.class) {
                    isAutoincrement = false;
                } else {
                    for (Field field : SQLHelper.getDeclaredFields(theClass)) {
                        PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
                        if (primaryKey != null && !primaryKey.autoincrement()) {
                            isAutoincrement = false;
                            break;
                        }
                    }
                }
                if (isAutoincrement && !mAutoIncrementList.contains(theClass)) {
                    mAutoIncrementList.add(theClass);
                }
            }
        }
    }

    /**
     * Use this to register classes that shall no have an autoincrement primary key. Use it only when you have
     * no control over that kind of class. If you do have control over the class, you shall use the normal
     * {@link SqlPersistence#match(Class[])} method and the annotation {@link PrimaryKey} with the autoincrement
     * argument set to false.
     *
     * @param classes the classes to register
     */
    public void matchNotAutoIncrement(Class... classes) {
        for (Class type : classes) {
            if (!mSqliteList.contains(type)) {
                mSqliteList.add(type);
            }
        }
    }

    /**
     * Use this to register many-to-many relationships. You shall no pass repeated relations (including those that
     * has the same classes but in different order). Classes in the relation will be passed to the
     * {@link SqlPersistence#match(Class[])} method, which means that, by default, they will be
     * treated as tables with an autoincrement primary key; if you want to avoid this behavior, use the
     * {@link PrimaryKey} annotation and customize your primary key. If you do not have control over the classes and
     * want to avoid the autoincrement, use the alternative method {@link SqlPersistence#matchNotAutoIncrement(ManyToMany)}.
     *
     * @param manyToMany an instance containing the many-to-many relation
     */
    public void match(ManyToMany manyToMany) {
        // make sure there are no inverted many-to-many relations
        for (ManyToMany mtm : mManyToManyList) {
            if ((mtm.getFirstRelation() == manyToMany.getSecondRelation() && mtm.getSecondRelation() == manyToMany.getFirstRelation()) ||
                    (mtm.getFirstRelation() == manyToMany.getFirstRelation() && mtm.getSecondRelation() == manyToMany.getSecondRelation())) {
                throw new IllegalStateException(String.format("Error adding '%s': there should not be two many-to-many relations with the same classes.", manyToMany));
            }
        }
        match(manyToMany.getFirstRelation(), manyToMany.getSecondRelation());
        if (!mManyToManyList.contains(manyToMany)) {
            mManyToManyList.add(manyToMany);
        }
    }

    /**
     * Use this to register many-to-many relationships. You shall no pass repeated relations (including those that
     * has the same classes but in different order). Classes in the relation will be passed to the
     * {@link SqlPersistence#matchNotAutoIncrement(Class[])} method. I recommend to use the
     * {@link SqlPersistence#match(ManyToMany)} and {@link PrimaryKey} annotation if you have control over the
     * classes to be registered.
     *
     * @param manyToMany an instance containing the many-to-many relation
     */
    public void matchNotAutoIncrement(ManyToMany manyToMany) {
        matchNotAutoIncrement(manyToMany.getFirstRelation(), manyToMany.getSecondRelation());
        if (!mManyToManyList.contains(manyToMany)) {
            mManyToManyList.add(manyToMany);
        }
    }

    /**
     * Registers a has-many relation. This will register the individual classes using the
     * {@link SqlPersistence#match(Class[])} method which means that those classes will be treated as autoincrement.
     * If you want to avoid this behavior, use the {@link PrimaryKey} annotation; if you do not have control
     * over the registered classes, use the {@link SqlPersistence#matchNotAutoIncrement(ManyToMany)}
     *
     * @param hasMany the has-many relationship to register.
     */
    public void match(HasMany hasMany) {
        Class containerClass = hasMany.getContainerClass();
        Class containedClass = hasMany.getContainedClass();
        // make sure there are no inverted has many relations
        for (HasMany hasManyRelation : mHasManyList) {
            Class currentContainerClass = hasManyRelation.getContainerClass();
            Class currentContainedClass = hasManyRelation.getContainedClass();
            if (currentContainerClass == containedClass && currentContainedClass == containerClass) {
                throw new IllegalStateException("There should not be two has-many relations with the same classes. Use Many-To-Many");
            }
        }
        match(containedClass, containerClass);
        // add the has many relation to the list
        if (!mHasManyList.contains(hasMany)) {
            mHasManyList.add(hasMany);
        }
    }

    /**
     * Registers a has-many relation. This will register the individual classes using the
     * {@link SqlPersistence#matchNotAutoIncrement(Class[])} method which means that those classes do not have an
     * autoincrement primary key. If you have control over the classes to register, I recommend to use the
     * {@link SqlPersistence#match(HasMany)} method and the {@link PrimaryKey} annotation.
     *
     * @param hasMany the has-many relationship to register.
     */
    public void matchNotAutoIncrement(HasMany hasMany) {
        Class containerClass = hasMany.getContainerClass();
        Class containedClass = hasMany.getContainedClass();
        matchNotAutoIncrement(containerClass, containedClass);
        // make sure there are no inverted has many relations
        for (HasMany hasManyRelation : mHasManyList) {
            Class currentContainerClass = hasManyRelation.getContainerClass();
            Class currentContainedClass = hasManyRelation.getContainedClass();
            if (currentContainerClass == containedClass &&
                    currentContainedClass == containerClass) {
                throw new IllegalStateException("There should not be two has-many relations with the same classes. Use Many-To-Many");
            }
        }
        // add the has many relation to the list
        if (!mHasManyList.contains(hasMany)) {
            mHasManyList.add(hasMany);
        }
    }

    /**
     * Returns the relationship of the specified classes
     *
     * @param theClass        a class
     * @param collectionClass another class
     * @return the type of relationship between two classes
     */
    Relationship getRelationship(Class theClass, Class collectionClass) {
        for (HasMany hasMany : mHasManyList) {
            Class containerClass = hasMany.getContainerClass();
            Class containedClass = hasMany.getContainedClass();
            if (containerClass == theClass && containedClass == collectionClass) {
                return Relationship.HAS_MANY;
            }
        }

        for (ManyToMany manyToMany : mManyToManyList) {
            if ((manyToMany.getFirstRelation() == theClass && manyToMany.getSecondRelation() == collectionClass) ||
                    (manyToMany.getSecondRelation() == theClass && manyToMany.getFirstRelation() == collectionClass)) {
                return Relationship.MANY_TO_MANY;
            }
        }
        return Relationship.UNKNOWN;
    }

    /**
     * Returns a relationship of the specified class. If it has two relations, it will return the
     * {@link Relationship#HAS_MANY}
     *
     * @param theClass a class
     * @return the type of relationship between this class and any other
     */
    Relationship getRelationship(Class theClass) {
        for (HasMany hasMany : mHasManyList) {
            Class containerClass = hasMany.getContainerClass();
            if (containerClass == theClass) {
                return Relationship.HAS_MANY;
            }
        }

        for (ManyToMany manyToMany : mManyToManyList) {
            if (manyToMany.getFirstRelation() == theClass || manyToMany.getSecondRelation() == theClass) {
                return Relationship.MANY_TO_MANY;
            }
        }
        return Relationship.UNKNOWN;
    }

    /**
     * @param clazz the class that we are checking whether belongs to another class
     * @return the class that clazz belongs to or null if not such relation
     */
    HasMany belongsTo(Class clazz) {
        for (HasMany hasMany : mHasManyList) {
            Class containedClass = hasMany.getContainedClass();
            if (containedClass == clazz) {
                return hasMany;
            }
        }
        return null;
    }

    /**
     * @param theClass the class to check
     * @return true if the class is registered as autoincrement
     */
    boolean isAutoincrement(Class theClass) {
        return mAutoIncrementList.contains(theClass);
    }

    /**
     * @param theClass the table to search for
     * @return a list with the many-to-many relations of this table
     */
    List getManyToMany(Class theClass) {
        List manyToManyList = new ArrayList();
        for (ManyToMany manyToMany : mManyToManyList) {
            if (manyToMany.getFirstRelation() == theClass || manyToMany.getSecondRelation() == theClass) {
                manyToManyList.add(manyToMany);
            }
        }
        return manyToManyList;
    }

    /**
     * @param clazz the class that we are checking whether has another
     * @return the class that clazz has or null if not such relation
     */
    HasMany has(Class clazz) {
        for (HasMany hasMany : mHasManyList) {
            Class containerClass = hasMany.getContainerClass();
            if (containerClass == clazz) {
                return hasMany;
            }
        }
        return null;
    }

    /**
     * Adds one or more importers from the file paths specified. This is executed before tables are created.
     *
     * @param context used to get the content of the assets
     * @param paths   one or more file paths relative to the Assets folder
     */
    public void beforeCreateImportFromAssets(Context context, String... paths) {
        if (paths.length == 0) {
            throw new IllegalStateException("You should specify at lease one path");
        }
        for (String path : paths) {
            mBeforeImporters.add(new AssetsImporter(context, path));
        }
    }

    /**
     * Adds an importer from a stream. This is executed before tables are created.
     *
     * @param inputStream the input stream must not be null and must point to sqlite statements to execute
     */
    public void beforeCreateImportFromStream(InputStream inputStream) {
        mBeforeImporters.add(new StreamImporter(inputStream));
    }

    /**
     * Execute sqlite statements before tables are created.
     * @param sqlStatements the statements to execute
     */
    public void beforeCreateImportFromString(String sqlStatements) {
        mBeforeImporters.add(new RawImporter(sqlStatements));
    }

    /**
     * Adds one or more importers from the file paths specified. This is executed before tables are created.
     * Executes the specified sql statements after tables are created.
     *
     * @param context used to get the content of the assets
     * @param paths   one or more file paths relative to the Assets folder
     */
    public void afterCreateImportFromAssets(Context context, String... paths) {
        if (paths.length == 0) {
            throw new IllegalStateException("You should specify at lease one path");
        }
        for (String path : paths) {
            mAfterImporters.add(new AssetsImporter(context, path));
        }
    }

    /**
     * Adds an importer from a stream. This is executed after tables are created.
     *
     * @param inputStream the input stream must not be null and must point to sqlite statements to execute
     */
    public void afterCreateImportFromStream(InputStream inputStream) {
        mAfterImporters.add(new StreamImporter(inputStream));
    }

    /**
     * Execute sqlite statements after tables are created.
     * @param sqlStatements the statements to execute
     */
    public void afterCreateImportFromString(String sqlStatements) {
        mAfterImporters.add(new RawImporter(sqlStatements));
    }

    List getAfterImporters() {
        return mAfterImporters;
    }

    List getBeforeImporters() {
        return mBeforeImporters;
    }

    enum PersistenceType {SQLITE, PREFERENCES, UNKNOWN}

    enum Relationship {MANY_TO_MANY, HAS_MANY, UNKNOWN}

    List> getSqliteClasses() {
        return mSqliteList;
    }

    List getSqliteManyToMany() {
        return mManyToManyList;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        SqlPersistence that = (SqlPersistence) o;

        if (mVersion != that.mVersion) return false;
        if (mAutoIncrementList != null ? !mAutoIncrementList.equals(that.mAutoIncrementList) : that.mAutoIncrementList != null)
            return false;
        if (mHasManyList != null ? !mHasManyList.equals(that.mHasManyList) : that.mHasManyList != null)
            return false;
        if (mManyToManyList != null ? !mManyToManyList.equals(that.mManyToManyList) : that.mManyToManyList != null)
            return false;
        if (mSqliteList != null ? !mSqliteList.equals(that.mSqliteList) : that.mSqliteList != null) return false;
        if (mName != null ? !mName.equals(that.mName) : that.mName != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = mSqliteList != null ? mSqliteList.hashCode() : 0;
        result = 31 * result + (mAutoIncrementList != null ? mAutoIncrementList.hashCode() : 0);
        result = 31 * result + (mManyToManyList != null ? mManyToManyList.hashCode() : 0);
        result = 31 * result + (mHasManyList != null ? mHasManyList.hashCode() : 0);
        result = 31 * result + (mName != null ? mName.hashCode() : 0);
        result = 31 * result + mVersion;
        return result;
    }

    @Override
    public String toString() {
        return "SqlPersistence{" +
                "name='" + mName + '\'' +
                ", version=" + mVersion +
                '}';
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy