Back to project page LitePal.
The source code is released under:
Apache License
If you think the Android project LitePal listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* * Copyright (C) Tony Green, Litepal Framework Open Source Project *//ww w . jav a 2 s .com * 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 org.litepal.tablemanager; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.litepal.exceptions.DatabaseGenerateException; import org.litepal.tablemanager.model.AssociationsModel; import org.litepal.util.BaseUtility; import org.litepal.util.Const; import org.litepal.util.DBUtility; import org.litepal.util.LogUtil; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; /** * When models have associations such as one2one, many2one or many2many, tables * should add foreign key column or create intermediate table to make the object * association mapping right. This process will be proceed automatically without * concerning by users. To make this happen, user just need to declare the * associations clearly in the models, and make sure all the mapping models are * added in the litepal.xml file. * * @author Tony Green * @since 1.0 */ public abstract class AssociationCreator extends Generator { protected abstract void createOrUpgradeTable(SQLiteDatabase db, boolean force); /** * {@link AssociationCreator} analyzes two things. Add associations * including add foreign key column to tables and create intermediate join * tables. */ @Override protected void addOrUpdateAssociation(SQLiteDatabase db, boolean force) { addAssociations(getAllAssociations(), db, force); } /** * Generate a create table SQL by the passed in parameters. Note that it * will always generate a SQL with id/_id column in it as primary key and * this id is auto increment as integer if the autoIncrementId is true, or * no primary key will be added. * * @param tableName * The table name. * @param columnsMap * A map with column name as key and column type as value. * @param autoIncrementId * Generate an auto increment id or not. * @return A generated create table SQL. */ protected String generateCreateTableSQL(String tableName, Map<String, String> columnsMap, boolean autoIncrementId) { Set<String> columnNames = columnsMap.keySet(); removeId(columnNames); StringBuilder createTableSQL = new StringBuilder("create table "); createTableSQL.append(tableName).append(" ("); if (autoIncrementId) { createTableSQL.append("id integer primary key autoincrement,"); } Iterator<String> i = columnNames.iterator(); if (!i.hasNext()) { createTableSQL.deleteCharAt(createTableSQL.length() - 1); } boolean needSeparator = false; while (i.hasNext()) { if (needSeparator) { createTableSQL.append(", "); } needSeparator = true; String columnName = i.next(); createTableSQL.append(columnName).append(" ").append(columnsMap.get(columnName)); } createTableSQL.append(")"); LogUtil.d(TAG, "add column sql is >> " + createTableSQL); return createTableSQL.toString(); } /** * Generate a SQL for dropping table. * * @param tableName * The table name. * @return A SQL to drop table. */ protected String generateDropTableSQL(String tableName) { return "drop table if exists " + tableName; } /** * Generate a SQL for add new column into the existing table. * * @param columnName * The new column name. * @param columnType * The new column type. * @return A SQL to add new column. */ protected String generateAddColumnSQL(String tableName, String columnName, String columnType) { StringBuilder addColumnSQL = new StringBuilder(); addColumnSQL.append("alter table ").append(tableName).append(" add column ") .append(columnName).append(" ").append(columnType); LogUtil.d(TAG, "add column sql is >> " + addColumnSQL); return addColumnSQL.toString(); } /** * Judge the passed in column is a foreign key column format or not. Each * column name ends with _id will be considered as foreign key column * format. * * @param columnName * The name of column. * @return Return true if it's foreign column format, otherwise return * false. */ protected boolean isForeignKeyColumnFormat(String columnName) { if (!TextUtils.isEmpty(columnName)) { return columnName.toLowerCase().endsWith("_id") && !columnName.equalsIgnoreCase("_id"); } return false; } /** * Once there's new table created. The table name will be saved into * table_schema as a copy. Each table name will be saved only once. * * @param tableName * The table name. * @param tableType * 0 means normal table, 1 means intermediate join table. * @param db * Instance of SQLiteDatabase. */ protected void giveTableSchemaACopy(String tableName, int tableType, SQLiteDatabase db) { StringBuilder sql = new StringBuilder("select * from "); sql.append(Const.TableSchema.TABLE_NAME); LogUtil.d(TAG, "giveTableSchemaACopy SQL is >> " + sql); Cursor cursor = null; try { cursor = db.rawQuery(sql.toString(), null); if (isNeedtoGiveACopy(cursor, tableName)) { ContentValues values = new ContentValues(); values.put(Const.TableSchema.COLUMN_NAME, BaseUtility.changeCase(tableName)); values.put(Const.TableSchema.COLUMN_TYPE, tableType); db.insert(Const.TableSchema.TABLE_NAME, null, values); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } } /** * Save the name of a created table into table_schema, but there're some * extra rules. Each table name should be only saved once, and special * tables will not be saved. * * @param cursor * The cursor used to iterator values in the table. * @param tableName * The table name. * @return If all rules are passed return true, any of them failed return * false. */ private boolean isNeedtoGiveACopy(Cursor cursor, String tableName) { return !isValueExists(cursor, tableName) && !isSpecialTable(tableName); } /** * Judge the table name has already exist in the table_schema or not. * * @param cursor * The cursor used to iterator values in the table. * @param tableName * The table name. * @return If value exists return true, or return false. */ private boolean isValueExists(Cursor cursor, String tableName) { boolean exist = false; if (cursor.moveToFirst()) { do { String name = cursor.getString(cursor .getColumnIndexOrThrow(Const.TableSchema.COLUMN_NAME)); if (name.equalsIgnoreCase(tableName)) { exist = true; break; } } while (cursor.moveToNext()); } return exist; } /** * Judge a table is a special table or not. Currently table_schema is a * special table. * * @param tableName * The table name. * @return Return true if it's special table. */ private boolean isSpecialTable(String tableName) { return Const.TableSchema.TABLE_NAME.equalsIgnoreCase(tableName); } /** * Analyzing all the association models in the collection. Judge their * association types. If it's one2one or many2one associations, add the * foreign key column to the associated table. If it's many2many * associations, create an intermediate join table. * * @param associatedModels * A collection contains all the association models.Use the * association models to get association type and associated * table names. * @param db * Instance of SQLiteDatabase. * @param force * Drop the table first if it already exists. */ private void addAssociations(Collection<AssociationsModel> associatedModels, SQLiteDatabase db, boolean force) { for (AssociationsModel associationModel : associatedModels) { if (Const.Model.MANY_TO_ONE == associationModel.getAssociationType() || Const.Model.ONE_TO_ONE == associationModel.getAssociationType()) { addForeignKeyColumn(associationModel.getTableName(), associationModel.getAssociatedTableName(), associationModel.getTableHoldsForeignKey(), db); } else if (Const.Model.MANY_TO_MANY == associationModel.getAssociationType()) { createIntermediateTable(associationModel.getTableName(), associationModel.getAssociatedTableName(), db, force); } } } /** * When it comes to many2many associations. Database need to create an * intermediate table for mapping this association. This method helps create * such a table, and the table name follows the concatenation of the two * target table names in alphabetical order with underline in the middle. * * @param tableName * The table name. * @param associatedTableName * The associated table name. * @param db * Instance of SQLiteDatabase. * @param force * Drop the table first if it already exists. */ private void createIntermediateTable(String tableName, String associatedTableName, SQLiteDatabase db, boolean force) { Map<String, String> columnsMap = new HashMap<String, String>(); columnsMap.put(tableName + "_id", "integer"); columnsMap.put(associatedTableName + "_id", "integer"); String intermediateTableName = DBUtility.getIntermediateTableName(tableName, associatedTableName); List<String> sqls = new ArrayList<String>(); if (DBUtility.isTableExists(intermediateTableName, db)) { if (force) { sqls.add(generateDropTableSQL(intermediateTableName)); sqls.add(generateCreateTableSQL(intermediateTableName, columnsMap, false)); } } else { sqls.add(generateCreateTableSQL(intermediateTableName, columnsMap, false)); } execute(sqls.toArray(new String[0]), db); giveTableSchemaACopy(intermediateTableName, Const.TableSchema.INTERMEDIATE_JOIN_TABLE, db); } /** * This method is used to add many to one association or one to one * association on tables. It will automatically build a SQL to add foreign * key to a table. If the passed in table name or associated table name * doesn't exist, it will throw an exception. * * @param tableName * The table name. * @param associatedTableName * The associated table name. * @param tableHoldsForeignKey * The table which holds the foreign key. * @param db * Instance of SQLiteDatabase. * * @throws DatabaseGenerateException */ private void addForeignKeyColumn(String tableName, String associatedTableName, String tableHoldsForeignKey, SQLiteDatabase db) { if (DBUtility.isTableExists(tableName, db)) { if (DBUtility.isTableExists(associatedTableName, db)) { String foreignKeyColumn = null; if (tableName.equals(tableHoldsForeignKey)) { foreignKeyColumn = getForeignKeyColumnName(associatedTableName); } else if (associatedTableName.equals(tableHoldsForeignKey)) { foreignKeyColumn = getForeignKeyColumnName(tableName); } if (!DBUtility.isColumnExists(foreignKeyColumn, tableHoldsForeignKey, db)) { String[] sqls = { generateAddColumnSQL(tableHoldsForeignKey, foreignKeyColumn, "integer") }; execute(sqls, db); } else { LogUtil.d(TAG, "column " + foreignKeyColumn + " is already exist, no need to add one"); } } else { throw new DatabaseGenerateException(DatabaseGenerateException.TABLE_DOES_NOT_EXIST + associatedTableName); } } else { throw new DatabaseGenerateException(DatabaseGenerateException.TABLE_DOES_NOT_EXIST + tableName); } } /** * After getting the id name, the id column must be removed from column * names collection. Avoid to generate two id columns. * * @param columnNames */ private void removeId(Set<String> columnNames) { String idName = ""; for (String columnName : columnNames) { if (isIdColumn(columnName)) { idName = columnName; break; } } if (!TextUtils.isEmpty(idName)) { columnNames.remove(idName); } } }