Android Open Source - LitePal Save Handler






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
 *//w w w .j  a v  a 2 s  .co m
 * 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.crud;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.litepal.crud.model.AssociationsInfo;
import org.litepal.exceptions.DataSupportException;

import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;

/**
 * This is a component under DataSupport. It deals with the saving stuff as
 * primary task. All the implementation based on the java reflection API and
 * Android SQLiteDatabase API. It will persist the model class into table. If
 * there're some associated models already persisted, it will build the
 * associations in database automatically between the current model and the
 * associated models.
 * 
 * @author Tony Green
 * @since 1.1
 */
class SaveHandler extends DataHandler {

  /**
   * Initialize {@link DataHandler#mDatabase} for operating database. Do not
   * allow to create instance of SaveHandler out of CRUD package.
   * 
   * @param db
   *            The instance of SQLiteDatabase.
   */
  SaveHandler(SQLiteDatabase db) {
    mDatabase = db;
  }

  /**
   * The open interface for other classes in CRUD package to save a model. It
   * is called when a model class calls the save method. First of all, the
   * passed in baseObj will be saved into database. Then LitePal will analyze
   * the associations. If there're associated models detected, each associated
   * model which is persisted will build association with current model in
   * database.
   * 
   * @param baseObj
   *            Current model to persist.
   * @throws InvocationTargetException
   * @throws IllegalAccessException
   * @throws NoSuchMethodException
   * @throws IllegalArgumentException
   * @throws SecurityException
   */
  void onSave(DataSupport baseObj) throws SecurityException, IllegalArgumentException,
      NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    String className = baseObj.getClassName();
    List<Field> supportedFields = getSupportedFields(className);
    Collection<AssociationsInfo> associationInfos = getAssociationInfo(className);
    if (!baseObj.isSaved()) {
      analyzeAssociatedModels(baseObj, associationInfos);
      doSaveAction(baseObj, supportedFields);
      analyzeAssociatedModels(baseObj, associationInfos);
    } else {
      analyzeAssociatedModels(baseObj, associationInfos);
      doUpdateAction(baseObj, supportedFields);
    }
  }

  /**
   * The open interface for other classes in CRUD package to save a model
   * collection. It is called when developer calls
   * {@link DataSupport#saveAll(Collection)}. Each model in the collection
   * will be persisted. If there're associated models detected, each
   * associated model which is persisted will build association with current
   * model in database.
   * 
   * @param collection
   *            Holds all models to persist.
   * @throws InvocationTargetException
   * @throws IllegalAccessException
   * @throws NoSuchMethodException
   * @throws IllegalArgumentException
   * @throws SecurityException
   */
  <T extends DataSupport> void onSaveAll(Collection<T> collection) throws SecurityException,
      IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
      InvocationTargetException {
    if (collection != null && collection.size() > 0) {
      DataSupport[] array = collection.toArray(new DataSupport[0]);
      DataSupport firstObj = array[0];
      String className = firstObj.getClassName();
      List<Field> supportedFields = getSupportedFields(className);
      Collection<AssociationsInfo> associationInfos = getAssociationInfo(className);
      for (DataSupport baseObj : array) {
        if (!baseObj.isSaved()) {
          analyzeAssociatedModels(baseObj, associationInfos);
          doSaveAction(baseObj, supportedFields);
          analyzeAssociatedModels(baseObj, associationInfos);
        } else {
          analyzeAssociatedModels(baseObj, associationInfos);
          doUpdateAction(baseObj, supportedFields);
        }
        baseObj.clearAssociatedData();
      }
    }
  }

  /**
   * Persisting model class into database happens here. The ultimate way to
   * save data calls {@link #saveAssociatedModels(DataSupport)}. But first
   * {@link #beforeSave(DataSupport, List, ContentValues)} will be called to
   * put the values for ContentValues. When the model is saved,
   * {@link #afterSave(DataSupport, List, long)} will be called to do stuffs
   * after model is saved. Note that SaveSupport won't help with id. Any
   * developer who wants to set value to id will be ignored here. The value of
   * id will be generated by SQLite automatically.
   * 
   * @param baseObj
   *            Current model to persist.
   * @param supportedFields
   *            List of all supported fields.
   * @throws InvocationTargetException
   * @throws IllegalAccessException
   * @throws NoSuchMethodException
   * @throws IllegalArgumentException
   * @throws SecurityException
   */
  private void doSaveAction(DataSupport baseObj, List<Field> supportedFields)
      throws SecurityException, IllegalArgumentException, NoSuchMethodException,
      IllegalAccessException, InvocationTargetException {
    ContentValues values = new ContentValues();
    beforeSave(baseObj, supportedFields, values);
    long id = saving(baseObj, values);
    afterSave(baseObj, supportedFields, id);
  }

  /**
   * Before the self model is saved, it will be analyzed first. Put all the
   * data contained by the model into ContentValues, including the fields
   * value and foreign key value.
   * 
   * @param baseObj
   *            Current model to persist.
   * @param supportedFields
   *            List of all supported fields.
   * @param values
   *            To store data of current model for persisting.
   * @throws InvocationTargetException
   * @throws IllegalAccessException
   * @throws NoSuchMethodException
   * @throws IllegalArgumentException
   * @throws SecurityException
   */
  private void beforeSave(DataSupport baseObj, List<Field> supportedFields, ContentValues values)
      throws SecurityException, IllegalArgumentException, NoSuchMethodException,
      IllegalAccessException, InvocationTargetException {
    putFieldsValue(baseObj, supportedFields, values);
    putForeignKeyValue(values, baseObj);
  }

  /**
   * Calling {@link SQLiteDatabase#insert(String, String, ContentValues)} to
   * persist the current model.
   * 
   * @param baseObj
   *            Current model to persist.
   * @param values
   *            To store data of current model for persisting.
   * @return The row ID of the newly inserted row, or -1 if an error occurred.
   */
  private long saving(DataSupport baseObj, ContentValues values) {
    return mDatabase.insert(baseObj.getTableName(), null, values);
  }

  /**
   * After the model is saved, do the extra work that need to do.
   * 
   * @param baseObj
   *            Current model that is persisted.
   * @param supportedFields
   *            List of all supported fields.
   * @param id
   *            The current model's id.
   */
  private void afterSave(DataSupport baseObj, List<Field> supportedFields, long id) {
    throwIfSaveFailed(id);
    assignIdValue(baseObj, getIdField(supportedFields), id);
    updateAssociatedTableWithFK(baseObj);
    insertIntermediateJoinTableValue(baseObj, false);
  }

  /**
   * When a model is associated with two different models.
   * 
   * @param baseObj
   *            The class of base object.
   * @throws InvocationTargetException
   * @throws IllegalAccessException
   * @throws NoSuchMethodException
   * @throws IllegalArgumentException
   * @throws SecurityException
   */
  private void doUpdateAction(DataSupport baseObj, List<Field> supportedFields)
      throws SecurityException, IllegalArgumentException, NoSuchMethodException,
      IllegalAccessException, InvocationTargetException {
    ContentValues values = new ContentValues();
    beforeUpdate(baseObj, supportedFields, values);
    updating(baseObj, values);
    afterUpdate(baseObj);
  }

  /**
   * Before updating model, it will be analyzed first. Put all the data
   * contained by the model into ContentValues, including the fields value and
   * foreign key value. If the associations between models has been removed.
   * The foreign key value in database should be cleared too.
   * 
   * @param baseObj
   *            Current model to update.
   * @param supportedFields
   *            List of all supported fields.
   * @param values
   *            To store data of current model for updating.
   * @throws InvocationTargetException
   * @throws IllegalAccessException
   * @throws NoSuchMethodException
   * @throws IllegalArgumentException
   * @throws SecurityException
   */
  private void beforeUpdate(DataSupport baseObj, List<Field> supportedFields, ContentValues values)
      throws SecurityException, IllegalArgumentException, NoSuchMethodException,
      IllegalAccessException, InvocationTargetException {
    putFieldsValue(baseObj, supportedFields, values);
    putForeignKeyValue(values, baseObj);
    for (String fkName : baseObj.getListToClearSelfFK()) {
      values.putNull(fkName);
    }
  }

  /**
   * Calling
   * {@link SQLiteDatabase#update(String, ContentValues, String, String[])} to
   * update the current model.
   * 
   * @param baseObj
   *            Current model to update.
   * @param values
   *            To store data of current model for updating.
   */
  private void updating(DataSupport baseObj, ContentValues values) {
    mDatabase.update(baseObj.getTableName(), values, "id = ?",
        new String[] { String.valueOf(baseObj.getBaseObjId()) });
  }

  /**
   * After the model is updated, do the extra work that need to do.
   * 
   * @param baseObj
   *            Current model that is updated.
   */
  private void afterUpdate(DataSupport baseObj) {
    updateAssociatedTableWithFK(baseObj);
    insertIntermediateJoinTableValue(baseObj, true);
    clearFKValueInAssociatedTable(baseObj);
  }

  /**
   * Get the id field by the passed in field list.
   * 
   * @param supportedFields
   *            The field list to find from.
   * @return The id field. If not found one return null.
   */
  private Field getIdField(List<Field> supportedFields) {
    for (Field field : supportedFields) {
      if (isIdColumn(field.getName())) {
        return field;
      }
    }
    return null;
  }

  /**
   * If the model saved failed, throw an exception.
   * 
   * @param id
   *            The id returned by SQLite. -1 means saved failed.
   */
  private void throwIfSaveFailed(long id) {
    if (id == -1) {
      throw new DataSupportException(DataSupportException.SAVE_FAILED);
    }
  }

  /**
   * Assign the generated id value to the model. The
   * {@link DataSupport#baseObjId} will be assigned anyway. If the model has a
   * field named id or _id, LitePal will assign it too. The
   * {@link DataSupport#baseObjId} will be used as identify of this model for
   * system use. The id or _id field will help developers for their own
   * purpose.
   * 
   * @param baseObj
   *            Current model that is persisted.
   * @param idField
   *            The field of id.
   * @param id
   *            The value of id.
   */
  private void assignIdValue(DataSupport baseObj, Field idField, long id) {
    try {
      giveBaseObjIdValue(baseObj, id);
      if (idField != null) {
        giveModelIdValue(baseObj, idField.getName(), idField.getType(), id);
      }
    } catch (Exception e) {
      throw new DataSupportException(e.getMessage());
    }
  }

  /**
   * After saving a model, the id for this model will be returned. Assign this
   * id to the model's id or _id field if it exists.
   * 
   * @param baseObj
   *            The class of base object.
   * @param idName
   *            The name of id. Only id or _id is valid.
   * @param idType
   *            The type of id. Only int or long is valid.
   * @param id
   *            The value of id.
   * @throws SecurityException
   * @throws NoSuchFieldException
   * @throws IllegalArgumentException
   * @throws IllegalAccessException
   */
  private void giveModelIdValue(DataSupport baseObj, String idName, Class<?> idType, long id)
      throws SecurityException, NoSuchFieldException, IllegalArgumentException,
      IllegalAccessException {
    if (shouldGiveModelIdValue(idName, idType, id)) {
      Object value = null;
      if (idType == int.class || idType == Integer.class) {
        value = (int) id;
      } else if (idType == long.class || idType == Long.class) {
        value = id;
      } else {
        throw new DataSupportException(DataSupportException.ID_TYPE_INVALID_EXCEPTION);
      }
      DynamicExecutor.setField(baseObj, idName, value, baseObj.getClass());
    }
  }

  /**
   * If the table for this model have a foreign key column, the value of
   * foreign key id should be saved too.
   * 
   * @param values
   *            The instance of ContentValues to put foreign key value.
   * @param foreignKeyName
   *            Foreign key column name.
   * @param foreignKeyValue
   *            Foreign key column value.
   */
  private void putForeignKeyValue(ContentValues values, DataSupport baseObj) {
    Map<String, Long> associatedModelMap = baseObj.getAssociatedModelsMapWithoutFK();
    for (String associatedTableName : associatedModelMap.keySet()) {
      values.put(getForeignKeyColumnName(associatedTableName),
          associatedModelMap.get(associatedTableName));
    }
  }

  /**
   * Update the foreign keys in the associated model's table.
   * 
   * @param baseObj
   *            Current model that is persisted.
   */
  private void updateAssociatedTableWithFK(DataSupport baseObj) {
    Map<String, Set<Long>> associatedModelMap = baseObj.getAssociatedModelsMapWithFK();
    ContentValues values = new ContentValues();
    for (String associatedTableName : associatedModelMap.keySet()) {
      values.clear();
      String fkName = getForeignKeyColumnName(baseObj.getTableName());
      values.put(fkName, baseObj.getBaseObjId());
      Set<Long> ids = associatedModelMap.get(associatedTableName);
      if (ids != null && !ids.isEmpty()) {
        mDatabase.update(associatedTableName, values, getWhereOfIdsWithOr(ids), null);
      }
    }
  }

  /**
   * When the associations breaks between current model and associated models,
   * clear all the associated models' foreign key value if it exists.
   * 
   * @param baseObj
   *            Current model that is persisted.
   */
  private void clearFKValueInAssociatedTable(DataSupport baseObj) {
    List<String> associatedTableNames = baseObj.getListToClearAssociatedFK();
    for (String associatedTableName : associatedTableNames) {
      String fkColumnName = getForeignKeyColumnName(baseObj.getTableName());
      ContentValues values = new ContentValues();
      values.putNull(fkColumnName);
      String whereClause = fkColumnName + " = " + baseObj.getBaseObjId();
      mDatabase.update(associatedTableName, values, whereClause, null);
    }
  }

  /**
   * Insert values into intermediate join tables for self model and associated
   * models.
   * 
   * @param baseObj
   *            Current model that is persisted.
   * @param isUpdate
   *            The current action is update or not.
   */
  private void insertIntermediateJoinTableValue(DataSupport baseObj, boolean isUpdate) {
    Map<String, Set<Long>> associatedIdsM2M = baseObj.getAssociatedModelsMapForJoinTable();
    ContentValues values = new ContentValues();
    for (String associatedTableName : associatedIdsM2M.keySet()) {
      String joinTableName = getIntermediateTableName(baseObj, associatedTableName);
      if (isUpdate) {
        mDatabase.delete(joinTableName, getWhereForJoinTableToDelete(baseObj),
            new String[] { String.valueOf(baseObj.getBaseObjId()) });
      }
      Set<Long> associatedIdsM2MSet = associatedIdsM2M.get(associatedTableName);
      for (long associatedId : associatedIdsM2MSet) {
        values.clear();
        values.put(getForeignKeyColumnName(baseObj.getTableName()), baseObj.getBaseObjId());
        values.put(getForeignKeyColumnName(associatedTableName), associatedId);
        mDatabase.insert(joinTableName, null, values);
      }
    }
  }

  /**
   * Get the where clause to delete intermediate join table's value for
   * updating.
   * 
   * @param baseObj
   *            Current model that is persisted.
   * @return The where clause to execute.
   */
  private String getWhereForJoinTableToDelete(DataSupport baseObj) {
    StringBuilder where = new StringBuilder();
    where.append(getForeignKeyColumnName(baseObj.getTableName()));
    where.append(" = ?");
    return where.toString();
  }

  /**
   * Judge should assign id value to model's id field. The principle is that
   * if id name is not null, id type is not null and id is greater than 0,
   * then should assign id value to it.
   * 
   * @param idName
   *            The name of id field.
   * @param idType
   *            The type of id field.
   * @param id
   *            The value of id.
   * @return If id name is not null, id type is not null and id is greater
   *         than 0, return true. Otherwise return false.
   */
  private boolean shouldGiveModelIdValue(String idName, Class<?> idType, long id) {
    return idName != null && idType != null && id > 0;
  }
}




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