Android Open Source - LitePal Association Creator






From Project

Back to project page LitePal.

License

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.

Java Source Code

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

}




Java Source Code List

org.litepal.LitePalApplication.java
org.litepal.LitePalBase.java
org.litepal.crud.AssociationsAnalyzer.java
org.litepal.crud.ClusterQuery.java
org.litepal.crud.DataHandler.java
org.litepal.crud.DataSupport.java
org.litepal.crud.DeleteHandler.java
org.litepal.crud.DynamicExecutor.java
org.litepal.crud.Many2ManyAnalyzer.java
org.litepal.crud.Many2OneAnalyzer.java
org.litepal.crud.One2OneAnalyzer.java
org.litepal.crud.QueryHandler.java
org.litepal.crud.SaveHandler.java
org.litepal.crud.UpdateHandler.java
org.litepal.crud.model.AssociationsInfo.java
org.litepal.exceptions.DataSupportException.java
org.litepal.exceptions.DatabaseGenerateException.java
org.litepal.exceptions.GlobalException.java
org.litepal.exceptions.InvalidAttributesException.java
org.litepal.exceptions.ParseConfigurationFileException.java
org.litepal.litepalsample.activity.AggregateActivity.java
org.litepal.litepalsample.activity.AverageSampleActivity.java
org.litepal.litepalsample.activity.CRUDActivity.java
org.litepal.litepalsample.activity.CountSampleActivity.java
org.litepal.litepalsample.activity.DeleteSampleActivity.java
org.litepal.litepalsample.activity.MainActivity.java
org.litepal.litepalsample.activity.ManageTablesActivity.java
org.litepal.litepalsample.activity.MaxSampleActivity.java
org.litepal.litepalsample.activity.MinSampleActivity.java
org.litepal.litepalsample.activity.ModelListActivity.java
org.litepal.litepalsample.activity.ModelStructureActivity.java
org.litepal.litepalsample.activity.QuerySampleActivity.java
org.litepal.litepalsample.activity.SaveSampleActivity.java
org.litepal.litepalsample.activity.SumSampleActivity.java
org.litepal.litepalsample.activity.TableListActivity.java
org.litepal.litepalsample.activity.TableStructureActivity.java
org.litepal.litepalsample.activity.UpdateSampleActivity.java
org.litepal.litepalsample.adapter.DataArrayAdapter.java
org.litepal.litepalsample.adapter.StringArrayAdapter.java
org.litepal.litepalsample.model.Album.java
org.litepal.litepalsample.model.Singer.java
org.litepal.litepalsample.model.Song.java
org.litepal.litepalsample.util.Utility.java
org.litepal.model.Table_Schema.java
org.litepal.parser.LitePalAttr.java
org.litepal.parser.LitePalContentHandler.java
org.litepal.parser.LitePalParser.java
org.litepal.tablemanager.AssociationCreator.java
org.litepal.tablemanager.AssociationUpdater.java
org.litepal.tablemanager.Connector.java
org.litepal.tablemanager.Creator.java
org.litepal.tablemanager.Dropper.java
org.litepal.tablemanager.Generator.java
org.litepal.tablemanager.LitePalOpenHelper.java
org.litepal.tablemanager.Upgrader.java
org.litepal.tablemanager.model.AssociationsModel.java
org.litepal.tablemanager.model.TableModel.java
org.litepal.tablemanager.typechange.BooleanOrm.java
org.litepal.tablemanager.typechange.DateOrm.java
org.litepal.tablemanager.typechange.DecimalOrm.java
org.litepal.tablemanager.typechange.NumericOrm.java
org.litepal.tablemanager.typechange.OrmChange.java
org.litepal.tablemanager.typechange.TextOrm.java
org.litepal.util.BaseUtility.java
org.litepal.util.Const.java
org.litepal.util.DBUtility.java
org.litepal.util.LogUtil.java
org.litepal.util.SharedUtil.java