Android Open Source - LitePal Data 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
 */*from   w  ww  .  j  a v  a  2s . 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.crud;

import static org.litepal.util.BaseUtility.changeCase;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.litepal.LitePalBase;
import org.litepal.crud.model.AssociationsInfo;
import org.litepal.exceptions.DataSupportException;
import org.litepal.exceptions.DatabaseGenerateException;
import org.litepal.util.BaseUtility;
import org.litepal.util.Const;
import org.litepal.util.DBUtility;

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

/**
 * This is the base class for CRUD component. All the common actions which can
 * be shared with each function in CURD component will be put here.
 * 
 * @author Tony Green
 * @since 1.1
 */
abstract class DataHandler extends LitePalBase {
  public static final String TAG = "DataHandler";

  /**
   * Instance of SQLiteDatabase, use to do the CRUD job.
   */
  SQLiteDatabase mDatabase;

  /**
   * Store empty model instance. In case to create each time when checking
   * field is with default value or not.
   */
  private DataSupport tempEmptyModel;

  /**
   * Holds the AssociationsInfo which foreign keys in the current model.
   */
  private List<AssociationsInfo> fkInCurrentModel;

  /**
   * Holds the AssociationsInfo which foreign keys in other models.
   */
  private List<AssociationsInfo> fkInOtherModel;

  /**
   * Query the table of the given model, returning a model list over the
   * result set.
   * 
   * @param modelClass
   *            The model to compile the query against.
   * @param columns
   *            A list of which columns to return. Passing null will return
   *            all columns, which is discouraged to prevent reading data from
   *            storage that isn't going to be used.
   * @param selection
   *            A filter declaring which rows to return, formatted as an SQL
   *            WHERE clause (excluding the WHERE itself). Passing null will
   *            return all rows for the given table.
   * @param selectionArgs
   *            You may include ?s in selection, which will be replaced by the
   *            values from selectionArgs, in order that they appear in the
   *            selection. The values will be bound as Strings.
   * @param groupBy
   *            A filter declaring how to group rows, formatted as an SQL
   *            GROUP BY clause (excluding the GROUP BY itself). Passing null
   *            will cause the rows to not be grouped.
   * @param having
   *            A filter declare which row groups to include in the cursor, if
   *            row grouping is being used, formatted as an SQL HAVING clause
   *            (excluding the HAVING itself). Passing null will cause all row
   *            groups to be included, and is required when row grouping is
   *            not being used.
   * @param orderBy
   *            How to order the rows, formatted as an SQL ORDER BY clause
   *            (excluding the ORDER BY itself). Passing null will use the
   *            default sort order, which may be unordered.
   * @param limit
   *            Limits the number of rows returned by the query, formatted as
   *            LIMIT clause. Passing null denotes no LIMIT clause.
   * @param foreignKeyAssociations
   *            Associated classes which have foreign keys in the current
   *            model's table.
   * @return A model list. The list may be empty.
   */
  @SuppressWarnings("unchecked")
  protected <T> List<T> query(Class<T> modelClass, String[] columns, String selection,
      String[] selectionArgs, String groupBy, String having, String orderBy, String limit,
      List<AssociationsInfo> foreignKeyAssociations) {
    List<T> dataList = new ArrayList<T>();
    Cursor cursor = null;
    try {
      List<Field> supportedFields = getSupportedFields(modelClass.getName());
      String tableName = getTableName(modelClass);
      String[] customizedColumns = getCustomizedColumns(columns, foreignKeyAssociations);
      cursor = mDatabase.query(tableName, customizedColumns, selection, selectionArgs,
          groupBy, having, orderBy, limit);
      if (cursor.moveToFirst()) {
        do {
          Constructor<?> constructor = findBestSuitConstructor(modelClass);
          T modelInstance = (T) constructor
              .newInstance(getConstructorParams(constructor));
          giveBaseObjIdValue((DataSupport) modelInstance,
              cursor.getLong(cursor.getColumnIndexOrThrow("id")));
          setValueToModel(modelInstance, supportedFields, foreignKeyAssociations, cursor);
          if (foreignKeyAssociations != null) {
            setAssociatedModel((DataSupport) modelInstance);
          }
          dataList.add(modelInstance);
        } while (cursor.moveToNext());
      }
      return dataList;
    } catch (Exception e) {
      throw new DataSupportException(e.getMessage());
    } finally {
      if (cursor != null) {
        cursor.close();
      }
    }
  }

  /**
   * Handles the math query of the given table.
   * 
   * @param tableName
   *            Which table to query from.
   * @param columns
   *            A list of which columns to return. Passing null will return
   *            all columns, which is discouraged to prevent reading data from
   *            storage that isn't going to be used.
   * @param conditions
   *            A filter declaring which rows to return, formatted as an SQL
   *            WHERE clause. Passing null will return all rows.
   * @param type
   *            The type of the based on column.
   * @return The result calculating by SQL.
   */
  @SuppressWarnings("unchecked")
  protected <T> T mathQuery(String tableName, String[] columns, String[] conditions, Class<T> type) {
    BaseUtility.checkConditionsCorrect(conditions);
    Cursor cursor = null;
    T result = null;
    try {
      cursor = mDatabase.query(tableName, columns, getWhereClause(conditions),
          getWhereArgs(conditions), null, null, null);
      if (cursor.moveToFirst()) {
        Class<?> cursorClass = cursor.getClass();
        Method method = cursorClass.getMethod(genGetColumnMethod(type), int.class);
        result = (T) method.invoke(cursor, 0);
      }
    } catch (Exception e) {
      throw new DataSupportException(e.getMessage());
    } finally {
      if (cursor != null) {
        cursor.close();
      }
    }
    return result;
  }

  /**
   * Assign the generated id value to {@link DataSupport#baseObjId}. This
   * value will be used as identify of this model for system use.
   * 
   * @param baseObj
   *            The class of base object.
   * @param id
   *            The value of id.
   */
  protected void giveBaseObjIdValue(DataSupport baseObj, long id) throws SecurityException,
      NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
    if (id > 0) {
      DynamicExecutor.setField(baseObj, "baseObjId", id, DataSupport.class);
    }
  }

  /**
   * Iterate all the fields passed in. Each field calls
   * {@link #putFieldsValueDependsOnSaveOrUpdate(DataSupport, Field, ContentValues)}
   * if it's not id field.
   * 
   * @param baseObj
   *            Current model to persist or update.
   * @param supportedFields
   *            List of all supported fields.
   * @param values
   *            To store data of current model for persisting or updating.
   * @throws InvocationTargetException 
   * @throws IllegalAccessException 
   * @throws NoSuchMethodException 
   * @throws IllegalArgumentException 
   * @throws SecurityException 
   */
  protected void putFieldsValue(DataSupport baseObj, List<Field> supportedFields,
      ContentValues values) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    for (Field field : supportedFields) {
      if (!isIdColumn(field.getName())) {
        putFieldsValueDependsOnSaveOrUpdate(baseObj, field, values);
      }
    }
  }

  /**
   * This method deals with the putting values job into ContentValues. The
   * ContentValues has <b>put</b> method to set data. But we do not know we
   * should use which <b>put</b> method cause the field type isn't clear. So
   * the reflection API is necessary here to put values into ContentValues
   * with dynamically getting field type to put value.
   * 
   * @param baseObj
   *            The class of base object.
   * @param field
   *            Field to put into ContentValues.
   * @param values
   *            To store data of current model for persisting or updating.
   * @throws SecurityException
   * @throws NoSuchMethodException
   * @throws IllegalArgumentException
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   */
  protected void putContentValues(DataSupport baseObj, Field field, ContentValues values)
      throws SecurityException, IllegalArgumentException, NoSuchMethodException,
      IllegalAccessException, InvocationTargetException {
    Object fieldValue = takeGetMethodValueByField(baseObj, field);
    if ("java.util.Date".equals(field.getType().getName()) && fieldValue != null) {
      Date date = (Date) fieldValue;
      fieldValue = date.getTime();
    }
    Object[] parameters = new Object[] { changeCase(field.getName()), fieldValue };
    Class<?>[] parameterTypes = getParameterTypes(field, fieldValue, parameters);
    DynamicExecutor.send(values, "put", parameters, values.getClass(), parameterTypes);
  }

  /**
   * It finds the getter method by the field. For example, field name is age,
   * getter method name will be getAge. Then invoke the getter method and
   * return the value.
   * 
   * @param dataSupport
   *            The model to get method from.
   * @param field
   *            Use to generate getter method name.
   * @return The value returned by getter method.
   * @throws SecurityException
   * @throws NoSuchMethodException
   * @throws IllegalArgumentException
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   */
  protected Object takeGetMethodValueByField(DataSupport dataSupport, Field field)
      throws SecurityException, NoSuchMethodException, IllegalArgumentException,
      IllegalAccessException, InvocationTargetException {
    if (shouldGetOrSet(dataSupport, field)) {
      String getMethodName = makeGetterMethodName(field);
      return DynamicExecutor.send(dataSupport, getMethodName, null, dataSupport.getClass(),
          null);
    }
    return null;
  }

  /**
   * It finds the setter method by the field. For example, field name is age,
   * setter method name will be setAge. Then invoke the setter method with
   * necessary parameter.
   * 
   * @param dataSupport
   *            The model to set method to.
   * @param field
   *            Use to generate setter method name.
   * @param parameter
   *            The parameter to invoke setter method.
   * @throws SecurityException
   * @throws NoSuchMethodException
   * @throws IllegalArgumentException
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   */
  protected void putSetMethodValueByField(DataSupport dataSupport, Field field, Object parameter)
      throws SecurityException, NoSuchMethodException, IllegalArgumentException,
      IllegalAccessException, InvocationTargetException {
    if (shouldGetOrSet(dataSupport, field)) {
      String setMethodName = makeSetterMethodName(field);
      DynamicExecutor.send(dataSupport, setMethodName, new Object[] { parameter },
          dataSupport.getClass(), new Class[] { field.getType() });
    }
  }

  /**
   * Find all the associated models of currently model. Then add all the
   * associated models into {@link DataSupport#associatedModels} of baseObj.
   * 
   * @param baseObj
   *            The class of base object.
   * @param foreignKeyId
   *            The id value of foreign key.
   */
  protected void analyzeAssociatedModels(DataSupport baseObj,
      Collection<AssociationsInfo> associationInfos) {
    try {
      for (AssociationsInfo associationInfo : associationInfos) {
        if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE) {
          new Many2OneAnalyzer().analyze(baseObj, associationInfo);
        } else if (associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) {
          new One2OneAnalyzer().analyze(baseObj, associationInfo);
        } else if (associationInfo.getAssociationType() == Const.Model.MANY_TO_MANY) {
          new Many2ManyAnalyzer().analyze(baseObj, associationInfo);
        }
      }
    } catch (Exception e) {
      throw new DataSupportException(e.getMessage());
    }
  }

  /**
   * Create an empty instance of baseObj if it hasn't created one yet. If
   * there's already an empty model existed in {@link #tempEmptyModel}, no
   * need to create a new one.
   * 
   * @param baseObj
   *            Current model to update.
   * @return An empty instance of baseObj.
   */
  protected DataSupport getEmptyModel(DataSupport baseObj) {
    if (tempEmptyModel != null) {
      return tempEmptyModel;
    }
    String className = null;
    try {
      className = baseObj.getClassName();
      Class<?> modelClass = Class.forName(className);
      tempEmptyModel = (DataSupport) modelClass.newInstance();
      return tempEmptyModel;
    } catch (ClassNotFoundException e) {
      throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND
          + className);
    } catch (InstantiationException e) {
      throw new DataSupportException(className + DataSupportException.INSTANTIATION_EXCEPTION);
    } catch (Exception e) {
      throw new DataSupportException(e.getMessage());
    }
  }

  /**
   * Get the WHERE clause to apply when updating or deleting multiple rows.
   * 
   * @param conditions
   *            A string array representing the WHERE part of an SQL
   *            statement.
   * @return The WHERE clause to apply when updating or deleting multiple
   *         rows.
   */
  protected String getWhereClause(String... conditions) {
    if (isAffectAllLines((Object) conditions)) {
      return null;
    }
    if (conditions != null && conditions.length > 0) {
      return conditions[0];
    }
    return null;
  }

  /**
   * Get the WHERE arguments to fill into where clause when updating or
   * deleting multiple rows.
   * 
   * @param conditions
   *            A string array representing the WHERE part of an SQL
   *            statement.
   * @return The WHERE arguments to fill into where clause when updating or
   *         deleting multiple rows.
   */
  protected String[] getWhereArgs(String... conditions) {
    if (isAffectAllLines((Object) conditions)) {
      return null;
    }
    if (conditions != null && conditions.length > 1) {
      String[] whereArgs = new String[conditions.length - 1];
      System.arraycopy(conditions, 1, whereArgs, 0, conditions.length - 1);
      return whereArgs;
    }
    return null;
  }

  /**
   * Check the passing conditions represent to affect all lines or not. <br>
   * Do not pass anything to the conditions parameter means affect all lines.
   * 
   * @param conditions
   *            An array representing the WHERE part of an SQL statement.
   * @return Affect all lines or not.
   */
  protected boolean isAffectAllLines(Object... conditions) {
    if (conditions != null && conditions.length == 0) {
      return true;
    }
    return false;
  }

  /**
   * Get the where clause by the passed in id collection to apply multiple
   * rows.
   * 
   * @param ids
   *            The id collection.
   * @return The where clause to execute.
   */
  protected String getWhereOfIdsWithOr(Collection<Long> ids) {
    StringBuilder whereClause = new StringBuilder();
    boolean needOr = false;
    for (long id : ids) {
      if (needOr) {
        whereClause.append(" or ");
      }
      needOr = true;
      whereClause.append("id = ");
      whereClause.append(id);
    }
    return changeCase(whereClause.toString());
  }

  /**
   * Get the where clause by the passed in id array to apply multiple rows.
   * 
   * @param ids
   *            The id collection.
   * @return The where clause to execute.
   */
  protected String getWhereOfIdsWithOr(long... ids) {
    StringBuilder whereClause = new StringBuilder();
    boolean needOr = false;
    for (long id : ids) {
      if (needOr) {
        whereClause.append(" or ");
      }
      needOr = true;
      whereClause.append("id = ");
      whereClause.append(id);
    }
    return changeCase(whereClause.toString());
  }

  /**
   * Do not suggest use this method to find DataSupport class from hierarchy.
   * Try to use DataSupport.class directly.
   * 
   * Detect the baseObj is an instance of DataSupport or not. If true, return
   * the class of DataSupport. Otherwise throw an exception of
   * DataSupportException to tell user baseObj is not an instance of
   * DataSupport.
   * 
   * @param baseObj
   *            The base object model.
   * @return The class of DataSupport or throw DataSupportException.
   * @throws DataSupportException
   */
  @Deprecated
  protected Class<?> findDataSupportClass(DataSupport baseObj) {
    Class<?> superClass = null;
    while (true) {
      superClass = baseObj.getClass().getSuperclass();
      if (superClass == null || DataSupport.class == superClass) {
        break;
      }
    }
    if (superClass == null) {
      throw new DataSupportException(baseObj.getClass().getName()
          + DataSupportException.MODEL_IS_NOT_AN_INSTANCE_OF_DATA_SUPPORT);
    }
    return superClass;
  }

  /**
   * When executing {@link #takeGetMethodValueByField(DataSupport, Field)} or
   * {@link #putSetMethodValueByField(DataSupport, Field, Object)}, the
   * dataSupport and field passed in should be protected from null value.
   * 
   * @param dataSupport
   *            The object to execute set or get method.
   * @param field
   *            The field of generating set and get methods.
   * @return True if dataSupport and field are not null, false otherwise.
   */
  protected boolean shouldGetOrSet(DataSupport dataSupport, Field field) {
    return dataSupport != null && field != null;
  }

  /**
   * Get the name of intermediate join table.
   * 
   * @param baseObj
   *            Current model.
   * @param associatedTableName
   *            The name of associated table.
   * @return The name of intermediate join table.
   */
  protected String getIntermediateTableName(DataSupport baseObj, String associatedTableName) {
    return changeCase(DBUtility.getIntermediateTableName(baseObj.getTableName(),
        associatedTableName));
  }

  /**
   * Get the simple name of modelClass. Then change the case by the setting
   * rule in litepal.xml as table name.
   * 
   * @param modelClass
   *            Class of model to get table name from.
   * @return The table name of model.
   */
  protected String getTableName(Class<?> modelClass) {
    return BaseUtility.changeCase(modelClass.getSimpleName());
  }

  /**
   * Finds the best suit constructor for creating an instance of a class. The
   * principle is that constructor with least parameters will be the best suit
   * one to create instance. So this method will find the constructor with
   * least parameters of the class passed in.
   * 
   * @param modelClass
   *            To get constructors from.
   * @return The best suit constructor with least parameters.
   */
  protected Constructor<?> findBestSuitConstructor(Class<?> modelClass) {
    Constructor<?> finalConstructor = null;
    Constructor<?>[] constructors = modelClass.getConstructors();
    for (Constructor<?> constructor : constructors) {
      if (finalConstructor == null) {
        finalConstructor = constructor;
      } else {
        int finalParamLength = finalConstructor.getParameterTypes().length;
        int newParamLength = constructor.getParameterTypes().length;
        if (newParamLength < finalParamLength) {
          finalConstructor = constructor;
        }
      }
    }
    finalConstructor.setAccessible(true);
    return finalConstructor;
  }

  /**
   * Depends on the passed in constructor, creating a parameters array with
   * initialized values for the constructor.
   * 
   * @param constructor
   *            The constructor to get parameters for it.
   * 
   * @return A parameters array with initialized values.
   */
  protected Object[] getConstructorParams(Constructor<?> constructor) {
    Class<?>[] paramTypes = constructor.getParameterTypes();
    Object[] params = new Object[paramTypes.length];
    for (int i = 0; i < paramTypes.length; i++) {
      params[i] = getInitParamValue(paramTypes[i]);
    }
    return params;
  }

  /**
   * Get value from database by cursor, then set the value into modelInstance.
   * 
   * @param modelInstance
   *            The model to set into.
   * @param supportedFields
   *            Corresponding to each column in database.
   * @param foreignKeyAssociations
   *            Associated classes which have foreign keys in the current
   *            model's table.
   * @param cursor
   *            Use to get value from database.
   * @throws SecurityException
   * @throws IllegalArgumentException
   * @throws NoSuchMethodException
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   */
  protected void setValueToModel(Object modelInstance, List<Field> supportedFields,
      List<AssociationsInfo> foreignKeyAssociations, Cursor cursor) throws SecurityException,
      IllegalArgumentException, NoSuchMethodException, IllegalAccessException,
      InvocationTargetException {
    for (Field field : supportedFields) {
      String getMethodName = genGetColumnMethod(field);
      String columnName = isIdColumn(field.getName()) ? "id" : field.getName();
      int columnIndex = cursor.getColumnIndex(BaseUtility.changeCase(columnName));
      if (columnIndex != -1) {
        Class<?> cursorClass = cursor.getClass();
        Method method = cursorClass.getMethod(getMethodName, int.class);
        Object value = method.invoke(cursor, columnIndex);
        if (isIdColumn(field.getName())) {
          DynamicExecutor.setField(modelInstance, field.getName(), value,
              modelInstance.getClass());
        } else {
          if (field.getType() == boolean.class || field.getType() == Boolean.class) {
            if ("0".equals(String.valueOf(value))) {
              value = false;
            } else if ("1".equals(String.valueOf(value))) {
              value = true;
            }
          } else if (field.getType() == char.class || field.getType() == Character.class) {
            value = ((String) value).charAt(0);
          } else if (field.getType() == Date.class) {
            long date = (Long) value;
            if (date <= 0) {
              value = null;
            } else {
              value = new Date(date);
            }
          }
          putSetMethodValueByField((DataSupport) modelInstance, field, value);
        }
      }
    }
    if (foreignKeyAssociations != null) {
      for (AssociationsInfo associationInfo : foreignKeyAssociations) {
        String foreignKeyColumn = getForeignKeyColumnName(DBUtility
            .getTableNameByClassName(associationInfo.getAssociatedClassName()));
        int columnIndex = cursor.getColumnIndex(foreignKeyColumn);
        if (columnIndex != -1) {
          long associatedClassId = cursor.getLong(columnIndex);
          try {
            DataSupport associatedObj = (DataSupport) DataSupport.find(
                Class.forName(associationInfo.getAssociatedClassName()),
                associatedClassId);
            if (associatedObj != null) {
              putSetMethodValueByField((DataSupport) modelInstance,
                  associationInfo.getAssociateOtherModelFromSelf(), associatedObj);
            }
          } catch (ClassNotFoundException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }

  /**
   * Get the foreign key associations of the specified class.
   * 
   * @param className
   *            The full class name.
   * @param isEager
   *            True to load the associated models, false not.
   * @return The foreign key associations of the specified class
   */
  protected List<AssociationsInfo> getForeignKeyAssociations(String className, boolean isEager) {
    if (isEager) {
      analyzeAssociations(className);
      return fkInCurrentModel;
    }
    return null;
  }

  /**
   * Get the types of parameters for {@link ContentValues#put}. Need two
   * parameters. First is String type for key. Second is depend on field for
   * value.
   * 
   * @param field
   *            The field to get parameter type.
   * @param fieldValue
   *            Value of the field. Only used to convert to String when the
   *            field is char.
   * @param parameters
   *            If the field is char, convert the value to String at index 1.
   * @return The types of parameters for {@link ContentValues#put}.
   */
  private Class<?>[] getParameterTypes(Field field, Object fieldValue, Object[] parameters) {
    Class<?>[] parameterTypes;
    if (isCharType(field)) {
      parameters[1] = String.valueOf(fieldValue);
      parameterTypes = new Class[] { String.class, String.class };
    } else {
      if (field.getType().isPrimitive()) {
        parameterTypes = new Class[] { String.class, getObjectType(field.getType()) };
      } else if ("java.util.Date".equals(field.getType().getName())) {
        parameterTypes = new Class[] { String.class, Long.class };
      } else {
        parameterTypes = new Class[] { String.class, field.getType() };
      }
    }
    return parameterTypes;
  }

  /**
   * Each primitive type has a corresponding object type. For example int and
   * Integer, boolean and Boolean. This method gives a way to turn primitive
   * type into object type.
   * 
   * @param primitiveType
   *            The class of primitive type.
   * @return If the passed in parameter is primitive type, return a
   *         corresponding object type. Otherwise return null.
   */
  private Class<?> getObjectType(Class<?> primitiveType) {
    if (primitiveType != null) {
      if (primitiveType.isPrimitive()) {
        String basicTypeName = primitiveType.getName();
        if ("int".equals(basicTypeName)) {
          return Integer.class;
        } else if ("short".equals(basicTypeName)) {
          return Short.class;
        } else if ("long".equals(basicTypeName)) {
          return Long.class;
        } else if ("float".equals(basicTypeName)) {
          return Float.class;
        } else if ("double".equals(basicTypeName)) {
          return Double.class;
        } else if ("boolean".equals(basicTypeName)) {
          return Boolean.class;
        } else if ("char".equals(basicTypeName)) {
          return Character.class;
        }
      }
    }
    return null;
  }

  /**
   * Gives the passed in parameter an initialized value. If the parameter is
   * basic data type or the corresponding object data type, return the default
   * data. Or return null.
   * 
   * @param paramType
   *            Parameter to get initialized value.
   * @return Default data of basic data type or null.
   */
  private Object getInitParamValue(Class<?> paramType) {
    String paramTypeName = paramType.getName();
    if ("boolean".equals(paramTypeName) || "java.lang.Boolean".equals(paramTypeName)) {
      return false;
    }
    if ("float".equals(paramTypeName) || "java.lang.Float".equals(paramTypeName)) {
      return 0f;
    }
    if ("double".equals(paramTypeName) || "java.lang.Double".equals(paramTypeName)) {
      return 0.0;
    }
    if ("int".equals(paramTypeName) || "java.lang.Integer".equals(paramTypeName)) {
      return 0;
    }
    if ("long".equals(paramTypeName) || "java.lang.Long".equals(paramTypeName)) {
      return 0l;
    }
    if ("short".equals(paramTypeName) || "java.lang.Short".equals(paramTypeName)) {
      return 0;
    }
    if ("char".equals(paramTypeName) || "java.lang.Character".equals(paramTypeName)) {
      return ' ';
    }
    if ("java.lang.String".equals(paramTypeName)) {
      return "";
    }
    return null;
  }

  /**
   * Judge if the field is char or Character type.
   * 
   * @param field
   *            Field to judge type.
   * @return Return true if it's char or Character. Otherwise return false.
   */
  private boolean isCharType(Field field) {
    String type = field.getType().getName();
    return type.equals("char") || type.endsWith("Character");
  }

  /**
   * Judge a field is a primitive boolean type or not. Cause it's a little
   * special when use IDE to generate getter and setter method. The primitive
   * boolean type won't be like <b>getXxx</b>, it's something like
   * <b>isXxx</b>.
   * 
   * @param field
   *            Use field to get field type.
   * @return If it's primitive boolean type return true, else return false.
   */
  private boolean isPrimitiveBooleanType(Field field) {
    Class<?> fieldType = field.getType();
    if ("boolean".equals(fieldType.getName())) {
      return true;
    }
    return false;
  }

  /**
   * Put the value of field into ContentValues if current action is saving.
   * Check the value of field is default value or not if current action is
   * updating. If it's not default value, put it into ContentValues. Otherwise
   * ignore it.
   * 
   * @param baseObj
   *            Current model to persist or update.
   * @param field
   *            With value to put into ContentValues.
   * @param values
   *            To store data of current model for persisting or updating.
   * @throws SecurityException
   * @throws IllegalArgumentException
   * @throws NoSuchMethodException
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   */
  private void putFieldsValueDependsOnSaveOrUpdate(DataSupport baseObj, Field field,
      ContentValues values) throws SecurityException, IllegalArgumentException,
      NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    if (isUpdating()) {
      if (!isFieldWithDefaultValue(baseObj, field)) {
        putContentValues(baseObj, field, values);
      }
    } else if (isSaving()) {
      putContentValues(baseObj, field, values);
    }
  }

  /**
   * Current action is updating or not. Note that update the record by saving
   * the already saved record again belongs to save action.
   * 
   * @return If current action is updating return true. Otherwise return
   *         false.
   */
  private boolean isUpdating() {
    return UpdateHandler.class.getName().equals(getClass().getName());
  }

  /**
   * Current action is saving or not. Note that update the record by saving
   * the already saved record again belongs to save action.
   * 
   * @return If current action is saving return true. Otherwise return false.
   */
  private boolean isSaving() {
    return SaveHandler.class.getName().equals(getClass().getName());
  }

  /**
   * Analyze the passed in field. Judge if this field is with default value.
   * The baseObj need a default constructor or {@link DataSupportException}
   * will be thrown.
   * 
   * @param baseObj
   *            Current model to update.
   * @param field
   *            To judge if with default value.
   * @return If the field is with default value, return true. Otherwise return
   *         false.
   * @throws IllegalAccessException
   * @throws SecurityException
   * @throws IllegalArgumentException
   * @throws NoSuchMethodException
   * @throws InvocationTargetException
   * @throws DatabaseGenerateException
   * @throws DataSupportException
   */
  private boolean isFieldWithDefaultValue(DataSupport baseObj, Field field)
      throws IllegalAccessException, SecurityException, IllegalArgumentException,
      NoSuchMethodException, InvocationTargetException {
    DataSupport emptyModel = getEmptyModel(baseObj);
    Object realReturn = takeGetMethodValueByField(baseObj, field);
    Object defaultReturn = takeGetMethodValueByField(emptyModel, field);
    if (realReturn != null && defaultReturn != null) {
      String realFieldValue = takeGetMethodValueByField(baseObj, field).toString();
      String defaultFieldValue = takeGetMethodValueByField(emptyModel, field).toString();
      return realFieldValue.equals(defaultFieldValue);
    }
    return realReturn == defaultReturn;
  }

  /**
   * Generate the getter method name by field, following the eclipse rule.
   * 
   * @param field
   *            The field to generate getter method from.
   * @return The generated getter method name.
   */
  private String makeGetterMethodName(Field field) {
    String getterMethodPrefix;
    String fieldName = field.getName();
    if (isPrimitiveBooleanType(field)) {
      if (fieldName.matches("^is[A-Z]{1}.*$")) {
        fieldName = fieldName.substring(2);
      }
      getterMethodPrefix = "is";
    } else {
      getterMethodPrefix = "get";
    }
    if (fieldName.matches("^[a-z]{1}[A-Z]{1}.*")) {
      return getterMethodPrefix + fieldName;
    } else {
      return getterMethodPrefix + BaseUtility.capitalize(fieldName);
    }
  }

  /**
   * Generate the setter method name by field, following the eclipse rule.
   * 
   * @param field
   *            The field to generate setter method from.
   * @return The generated setter method name.
   */
  private String makeSetterMethodName(Field field) {
    String setterMethodName;
    String setterMethodPrefix = "set";
    if (isPrimitiveBooleanType(field) && field.getName().matches("^is[A-Z]{1}.*$")) {
      setterMethodName = setterMethodPrefix + field.getName().substring(2);
    } else if (field.getName().matches("^[a-z]{1}[A-Z]{1}.*")) {
      setterMethodName = setterMethodPrefix + field.getName();
    } else {
      setterMethodName = setterMethodPrefix + BaseUtility.capitalize(field.getName());
    }
    return setterMethodName;
  }

  /**
   * Generates the getType method for cursor based on field. There're two
   * unusual conditions. If field type is boolean, generate getInt method. If
   * field type is char, generate getString method.
   * 
   * @param field
   *            To generate getType method for cursor.
   * @return The getType method for cursor.
   */
  private String genGetColumnMethod(Field field) {
    return genGetColumnMethod(field.getType());
  }

  /**
   * Generates the getType method for cursor based on field. There're two
   * unusual conditions. If field type is boolean, generate getInt method. If
   * field type is char, generate getString method.
   * 
   * @param fieldType
   *            To generate getType method for cursor.
   * @return The getType method for cursor.
   */
  private String genGetColumnMethod(Class<?> fieldType) {
    String typeName;
    if (fieldType.isPrimitive()) {
      typeName = BaseUtility.capitalize(fieldType.getName());
    } else {
      typeName = fieldType.getSimpleName();
    }
    String methodName = "get" + typeName;
    if ("getBoolean".equals(methodName)) {
      methodName = "getInt";
    } else if ("getChar".equals(methodName)) {
      methodName = "getString";
    } else if ("getDate".equals(methodName)) {
      methodName = "getLong";
    }
    return methodName;
  }

  /**
   * Customize the passed in columns. If the columns contains an id column
   * already, just return it. If contains an _id column, rename it to id. If
   * not, an add id column then return.
   * 
   * @param columns
   *            The original columns that passed in.
   * @param foreignKeyAssociations
   *            Associated classes which have foreign keys in the current
   *            model's table.
   * @return Customized columns with id column always.
   */
  private String[] getCustomizedColumns(String[] columns,
      List<AssociationsInfo> foreignKeyAssociations) {
    if (columns != null) {
      if (foreignKeyAssociations != null && foreignKeyAssociations.size() > 0) {
        String[] tempColumns = new String[columns.length + foreignKeyAssociations.size()];
        System.arraycopy(columns, 0, tempColumns, 0, columns.length);
        for (int i = 0; i < foreignKeyAssociations.size(); i++) {
          String associatedTable = DBUtility
              .getTableNameByClassName(foreignKeyAssociations.get(i)
                  .getAssociatedClassName());
          tempColumns[columns.length + i] = getForeignKeyColumnName(associatedTable);
        }
        columns = tempColumns;
      }
      for (int i = 0; i < columns.length; i++) {
        String columnName = columns[i];
        if (isIdColumn(columnName)) {
          if ("_id".equalsIgnoreCase(columnName)) {
            columns[i] = BaseUtility.changeCase("id");
          }
          return columns;
        }
      }
      String[] customizedColumns = new String[columns.length + 1];
      System.arraycopy(columns, 0, customizedColumns, 0, columns.length);
      customizedColumns[columns.length] = BaseUtility.changeCase("id");
      return customizedColumns;
    }
    return null;
  }

  /**
   * Analyze the associations for the specified class.
   * 
   * @param className
   *            The full class name.
   */
  private void analyzeAssociations(String className) {
    Collection<AssociationsInfo> associationInfos = getAssociationInfo(className);
    if (fkInCurrentModel == null) {
      fkInCurrentModel = new ArrayList<AssociationsInfo>();
    } else {
      fkInCurrentModel.clear();
    }
    if (fkInOtherModel == null) {
      fkInOtherModel = new ArrayList<AssociationsInfo>();
    } else {
      fkInOtherModel.clear();
    }
    for (AssociationsInfo associationInfo : associationInfos) {
      if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE
          || associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) {
        if (associationInfo.getClassHoldsForeignKey().equals(className)) {
          fkInCurrentModel.add(associationInfo);
        } else {
          fkInOtherModel.add(associationInfo);
        }
      } else if (associationInfo.getAssociationType() == Const.Model.MANY_TO_MANY) {
        fkInOtherModel.add(associationInfo);
      }
    }
  }

  /**
   * Finds the associated models of baseObj, then set them into baseObj.
   * 
   * @param baseObj
   *            The class of base object.
   */
  @SuppressWarnings({ "rawtypes", "unchecked" })
  private void setAssociatedModel(DataSupport baseObj) {
    if (fkInOtherModel == null) {
      return;
    }
    for (AssociationsInfo info : fkInOtherModel) {
      Cursor cursor = null;
      String associatedClassName = info.getAssociatedClassName();
      boolean isM2M = info.getAssociationType() == Const.Model.MANY_TO_MANY ? true : false;
      try {
        List<Field> supportedFields = getSupportedFields(associatedClassName);
        if (isM2M) {
          String tableName = baseObj.getTableName();
          String associatedTableName = DBUtility
              .getTableNameByClassName(associatedClassName);
          String intermediateTableName = DBUtility.getIntermediateTableName(tableName,
              associatedTableName);
          StringBuilder sql = new StringBuilder();
          sql.append("select * from ").append(associatedTableName)
              .append(" a inner join ").append(intermediateTableName)
              .append(" b on a.id = b.").append(associatedTableName + "_id")
              .append(" where b.").append(tableName).append("_id = ?");
          cursor = DataSupport.findBySQL(BaseUtility.changeCase(sql.toString()),
              String.valueOf(baseObj.getBaseObjId()));
        } else {
          String foreignKeyColumn = getForeignKeyColumnName(DBUtility
              .getTableNameByClassName(info.getSelfClassName()));
          String associatedTableName = DBUtility
              .getTableNameByClassName(associatedClassName);
          cursor = mDatabase.query(BaseUtility.changeCase(associatedTableName), null,
              foreignKeyColumn + "=?",
              new String[] { String.valueOf(baseObj.getBaseObjId()) }, null, null,
              null, null);
        }
        if (cursor.moveToFirst()) {
          do {
            Constructor<?> constructor = findBestSuitConstructor(Class
                .forName(associatedClassName));
            DataSupport modelInstance = (DataSupport) constructor
                .newInstance(getConstructorParams(constructor));
            giveBaseObjIdValue(modelInstance,
                cursor.getLong(cursor.getColumnIndexOrThrow("id")));
            setValueToModel(modelInstance, supportedFields, null, cursor);
            if (info.getAssociationType() == Const.Model.MANY_TO_ONE || isM2M) {
              Collection collection = (Collection) takeGetMethodValueByField(baseObj,
                  info.getAssociateOtherModelFromSelf());
              collection.add(modelInstance);
            } else if (info.getAssociationType() == Const.Model.ONE_TO_ONE) {
              putSetMethodValueByField(baseObj,
                  info.getAssociateOtherModelFromSelf(), modelInstance);
            }
          } while (cursor.moveToNext());
        }
      } catch (Exception e) {
        throw new DataSupportException(e.getMessage());
      } finally {
        if (cursor != null) {
          cursor.close();
        }
      }
    }
  }

}




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