/*
* Copyright (C) Tony Green, Litepal Framework Open Source Project
*
* 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.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.litepal.exceptions.DataSupportException;
import org.litepal.tablemanager.Connector;
import org.litepal.util.BaseUtility;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
/**
* DataSupport connects classes to SQLite database tables to establish an almost
* zero-configuration persistence layer for applications. In the context of an
* application, these classes are commonly referred to as models. Models can
* also be connected to other models.<br>
* DataSupport relies heavily on naming in that it uses class and association
* names to establish mappings between respective database tables and foreign
* key columns.<br>
* Automated mapping between classes and tables, attributes and columns.
*
* <pre>
* public class Person extends DataSupport {
* private int id;
* private String name;
* private int age;
* }
*
* The Person class is automatically mapped to the table named "person",
* which might look like this:
*
* CREATE TABLE person (
* id integer primary key autoincrement,
* age integer,
* name text
* );
* </pre>
*
* @author Tony Green
* @since 1.1
*/
public class DataSupport {
/**
* The identify of each model. LitePal will generate the value
* automatically. Do not try to assign or modify it.
*/
private long baseObjId;
/**
* A map contains all the associated models' id with M2O or O2O
* associations. Each corresponding table of these models contains a foreign
* key column.
*/
private Map<String, Set<Long>> associatedModelsMapWithFK;
/**
* A map contains all the associated models' id with M2O or O2O association.
* Each corresponding table of these models doesn't contain foreign key
* column. Instead self model has a foreign key column in the corresponding
* table.
*/
private Map<String, Long> associatedModelsMapWithoutFK;
/**
* A map contains all the associated models' id with M2M association.
*/
private Map<String, Set<Long>> associatedModelsMapForJoinTable;
/**
* When updating a model and the associations breaks between current model
* and others, if current model holds a foreign key, it need to be cleared.
* This list holds all the foreign key names that need to clear.
*/
private List<String> listToClearSelfFK;
/**
* When updating a model and the associations breaks between current model
* and others, clear all the associated models' foreign key value if it
* exists. This list holds all the associated table names that need to
* clear.
*/
private List<String> listToClearAssociatedFK;
/**
* A list holds all the field names which need to be updated into default
* value of model.
*/
private List<String> fieldsToSetToDefault;
/**
* Declaring to query which columns in table.
*
* <pre>
* DataSupport.select("name", "age").find(Person.class);
* </pre>
*
* This will find all rows with name and age columns in Person table.
*
* @param columns
* A String array of which columns to return. Passing null will
* return all columns.
*
* @return A ClusterQuery instance.
*/
public static synchronized ClusterQuery select(String... columns) {
ClusterQuery cQuery = new ClusterQuery();
cQuery.mColumns = columns;
return cQuery;
}
/**
* Declaring to query which rows in table.
*
* <pre>
* DataSupport.where("name = ? or age > ?", "Tom", "14").find(Person.class);
* </pre>
*
* This will find rows which name is Tom or age greater than 14 in Person
* table.
*
* @param conditions
* A filter declaring which rows to return, formatted as an SQL
* WHERE clause. Passing null will return all rows.
* @return A ClusterQuery instance.
*/
public static synchronized ClusterQuery where(String... conditions) {
ClusterQuery cQuery = new ClusterQuery();
cQuery.mConditions = conditions;
return cQuery;
}
/**
* Declaring how to order the rows queried from table.
*
* <pre>
* DataSupport.order("name desc").find(Person.class);
* </pre>
*
* This will find all rows in Person table sorted by name with inverted
* order.
*
* @param column
* How to order the rows, formatted as an SQL ORDER BY clause.
* Passing null will use the default sort order, which may be
* unordered.
* @return A ClusterQuery instance.
*/
public static synchronized ClusterQuery order(String column) {
ClusterQuery cQuery = new ClusterQuery();
cQuery.mOrderBy = column;
return cQuery;
}
/**
* Limits the number of rows returned by the query.
*
* <pre>
* DataSupport.limit(2).find(Person.class);
* </pre>
*
* This will find the top 2 rows in Person table.
*
* @param value
* Limits the number of rows returned by the query, formatted as
* LIMIT clause.
* @return A ClusterQuery instance.
*/
public static synchronized ClusterQuery limit(int value) {
ClusterQuery cQuery = new ClusterQuery();
cQuery.mLimit = String.valueOf(value);
return cQuery;
}
/**
* Declaring the offset of rows returned by the query. This method must be
* used with {@link #limit(int)}, or nothing will return.
*
* <pre>
* DataSupport.limit(1).offset(2).find(Person.class);
* </pre>
*
* This will find the third row in Person table.
*
* @param value
* The offset amount of rows returned by the query.
* @return A ClusterQuery instance.
*/
public static synchronized ClusterQuery offset(int value) {
ClusterQuery cQuery = new ClusterQuery();
cQuery.mOffset = String.valueOf(value);
return cQuery;
}
/**
* Count the records.
*
* <pre>
* DataSupport.count(Person.class);
* </pre>
*
* This will count all rows in person table.<br>
* You can also specify a where clause when counting.
*
* <pre>
* DataSupport.where("age > ?", "15").count(Person.class);
* </pre>
*
* @param modelClass
* Which table to query from by class.
* @return Count of the specified table.
*/
public static synchronized int count(Class<?> modelClass) {
return count(BaseUtility.changeCase(modelClass.getSimpleName()));
}
/**
* Count the records.
*
* <pre>
* DataSupport.count("person");
* </pre>
*
* This will count all rows in person table.<br>
* You can also specify a where clause when counting.
*
* <pre>
* DataSupport.where("age > ?", "15").count("person");
* </pre>
*
* @param tableName
* Which table to query from.
* @return Count of the specified table.
*/
public static synchronized int count(String tableName) {
ClusterQuery cQuery = new ClusterQuery();
return cQuery.count(tableName);
}
/**
* Calculates the average value on a given column.
*
* <pre>
* DataSupport.average(Person.class, "age");
* </pre>
*
* You can also specify a where clause when calculating.
*
* <pre>
* DataSupport.where("age > ?", "15").average(Person.class, "age");
* </pre>
*
* @param modelClass
* Which table to query from by class.
* @param column
* The based on column to calculate.
* @return The average value on a given column.
*/
public static synchronized double average(Class<?> modelClass, String column) {
return average(BaseUtility.changeCase(modelClass.getSimpleName()), column);
}
/**
* Calculates the average value on a given column.
*
* <pre>
* DataSupport.average("person", "age");
* </pre>
*
* You can also specify a where clause when calculating.
*
* <pre>
* DataSupport.where("age > ?", "15").average("person", "age");
* </pre>
*
* @param tableName
* Which table to query from.
* @param column
* The based on column to calculate.
* @return The average value on a given column.
*/
public static synchronized double average(String tableName, String column) {
ClusterQuery cQuery = new ClusterQuery();
return cQuery.average(tableName, column);
}
/**
* Calculates the maximum value on a given column. The value is returned
* with the same data type of the column.
*
* <pre>
* DataSupport.max(Person.class, "age", int.class);
* </pre>
*
* You can also specify a where clause when calculating.
*
* <pre>
* DataSupport.where("age > ?", "15").max(Person.class, "age", Integer.TYPE);
* </pre>
*
* @param modelClass
* Which table to query from by class.
* @param columnName
* The based on column to calculate.
* @param columnType
* The type of the based on column.
* @return The maximum value on a given column.
*/
public static synchronized <T> T max(Class<?> modelClass, String columnName, Class<T> columnType) {
return max(BaseUtility.changeCase(modelClass.getSimpleName()), columnName, columnType);
}
/**
* Calculates the maximum value on a given column. The value is returned
* with the same data type of the column.
*
* <pre>
* DataSupport.max("person", "age", int.class);
* </pre>
*
* You can also specify a where clause when calculating.
*
* <pre>
* DataSupport.where("age > ?", "15").max("person", "age", Integer.TYPE);
* </pre>
*
* @param tableName
* Which table to query from.
* @param columnName
* The based on column to calculate.
* @param columnType
* The type of the based on column.
* @return The maximum value on a given column.
*/
public static synchronized <T> T max(String tableName, String columnName, Class<T> columnType) {
ClusterQuery cQuery = new ClusterQuery();
return cQuery.max(tableName, columnName, columnType);
}
/**
* Calculates the minimum value on a given column. The value is returned
* with the same data type of the column.
*
* <pre>
* DataSupport.min(Person.class, "age", int.class);
* </pre>
*
* You can also specify a where clause when calculating.
*
* <pre>
* DataSupport.where("age > ?", "15").min(Person.class, "age", Integer.TYPE);
* </pre>
*
* @param modelClass
* Which table to query from by class.
* @param columnName
* The based on column to calculate.
* @param columnType
* The type of the based on column.
* @return The minimum value on a given column.
*/
public static synchronized <T> T min(Class<?> modelClass, String columnName, Class<T> columnType) {
return min(BaseUtility.changeCase(modelClass.getSimpleName()), columnName, columnType);
}
/**
* Calculates the minimum value on a given column. The value is returned
* with the same data type of the column.
*
* <pre>
* DataSupport.min("person", "age", int.class);
* </pre>
*
* You can also specify a where clause when calculating.
*
* <pre>
* DataSupport.where("age > ?", "15").min("person", "age", Integer.TYPE);
* </pre>
*
* @param tableName
* Which table to query from.
* @param columnName
* The based on column to calculate.
* @param columnType
* The type of the based on column.
* @return The minimum value on a given column.
*/
public static synchronized <T> T min(String tableName, String columnName, Class<T> columnType) {
ClusterQuery cQuery = new ClusterQuery();
return cQuery.min(tableName, columnName, columnType);
}
/**
* Calculates the sum of values on a given column. The value is returned
* with the same data type of the column.
*
* <pre>
* DataSupport.sum(Person.class, "age", int.class);
* </pre>
*
* You can also specify a where clause when calculating.
*
* <pre>
* DataSupport.where("age > ?", "15").sum(Person.class, "age", Integer.TYPE);
* </pre>
*
* @param modelClass
* Which table to query from by class.
* @param columnName
* The based on column to calculate.
* @param columnType
* The type of the based on column.
* @return The sum value on a given column.
*/
public static synchronized <T> T sum(Class<?> modelClass, String columnName, Class<T> columnType) {
return sum(BaseUtility.changeCase(modelClass.getSimpleName()), columnName, columnType);
}
/**
* Calculates the sum of values on a given column. The value is returned
* with the same data type of the column.
*
* <pre>
* DataSupport.sum("person", "age", int.class);
* </pre>
*
* You can also specify a where clause when calculating.
*
* <pre>
* DataSupport.where("age > ?", "15").sum("person", "age", Integer.TYPE);
* </pre>
*
* @param tableName
* Which table to query from.
* @param columnName
* The based on column to calculate.
* @param columnType
* The type of the based on column.
* @return The sum value on a given column.
*/
public static synchronized <T> T sum(String tableName, String columnName, Class<T> columnType) {
ClusterQuery cQuery = new ClusterQuery();
return cQuery.sum(tableName, columnName, columnType);
}
/**
* Finds the record by a specific id.
*
* <pre>
* Person p = DataSupport.find(Person.class, 1);
* </pre>
*
* The modelClass determines which table to query and the object type to
* return. If no record can be found, then return null. <br>
*
* Note that the associated models won't be loaded by default considering
* the efficiency, but you can do that by using
* {@link DataSupport#find(Class, long, boolean)}.
*
* @param modelClass
* Which table to query and the object type to return.
* @param id
* Which record to query.
* @return An object with found data from database, or null.
*/
public static synchronized <T> T find(Class<T> modelClass, long id) {
return find(modelClass, id, false);
}
/**
* It is mostly same as {@link DataSupport#find(Class, long)} but an isEager
* parameter. If set true the associated models will be loaded as well.
*
* @param modelClass
* Which table to query and the object type to return.
* @param id
* Which record to query.
* @param isEager
* True to load the associated models, false not.
* @return An object with found data from database, or null.
*/
public static synchronized <T> T find(Class<T> modelClass, long id, boolean isEager) {
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
return queryHandler.onFind(modelClass, id, isEager);
}
/**
* Finds the first record of a single table.
*
* <pre>
* Person p = DataSupport.findFirst(Person.class);
* </pre>
*
* Note that the associated models won't be loaded by default considering
* the efficiency, but you can do that by using
* {@link DataSupport#findFirst(Class, boolean)}.
*
* @param modelClass
* Which table to query and the object type to return.
* @return An object with data of first row, or null.
*/
public static synchronized <T> T findFirst(Class<T> modelClass) {
return findFirst(modelClass, false);
}
/**
* It is mostly same as {@link DataSupport#findFirst(Class)} but an isEager
* parameter. If set true the associated models will be loaded as well.
*
* @param modelClass
* Which table to query and the object type to return.
* @param isEager
* True to load the associated models, false not.
* @return An object with data of first row, or null.
*/
public static synchronized <T> T findFirst(Class<T> modelClass, boolean isEager) {
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
return queryHandler.onFindFirst(modelClass, isEager);
}
/**
* Finds the last record of a single table.
*
* <pre>
* Person p = DataSupport.findLast(Person.class);
* </pre>
*
* Note that the associated models won't be loaded by default considering
* the efficiency, but you can do that by using
* {@link DataSupport#findLast(Class, boolean)}.
*
* @param modelClass
* Which table to query and the object type to return.
* @return An object with data of last row, or null.
*/
public static synchronized <T> T findLast(Class<T> modelClass) {
return findLast(modelClass, false);
}
/**
* It is mostly same as {@link DataSupport#findLast(Class)} but an isEager
* parameter. If set true the associated models will be loaded as well.
*
* @param modelClass
* Which table to query and the object type to return.
* @param isEager
* True to load the associated models, false not.
* @return An object with data of last row, or null.
*/
public static synchronized <T> T findLast(Class<T> modelClass, boolean isEager) {
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
return queryHandler.onFindLast(modelClass, isEager);
}
/**
* Finds multiple records by an id array.
*
* <pre>
* List<Person> people = DataSupport.findAll(Person.class, 1, 2, 3);
*
* long[] bookIds = { 10, 18 };
* List<Book> books = DataSupport.findAll(Book.class, bookIds);
* </pre>
*
* Of course you can find all records by passing nothing to the ids
* parameter.
*
* <pre>
* List<Book> allBooks = DataSupport.findAll(Book.class);
* </pre>
*
* Note that the associated models won't be loaded by default considering
* the efficiency, but you can do that by using
* {@link DataSupport#findAll(Class, boolean, long...)}.
*
* The modelClass determines which table to query and the object type to
* return.
*
* @param modelClass
* Which table to query and the object type to return as a list.
* @param ids
* Which records to query. Or do not pass it to find all records.
* @return An object list with found data from database, or an empty list.
*/
public static synchronized <T> List<T> findAll(Class<T> modelClass, long... ids) {
return findAll(modelClass, false, ids);
}
/**
* It is mostly same as {@link DataSupport#findAll(Class, long...)} but an
* isEager parameter. If set true the associated models will be loaded as
* well.
*
* @param modelClass
* Which table to query and the object type to return as a list.
* @param isEager
* True to load the associated models, false not.
* @param ids
* Which records to query. Or do not pass it to find all records.
* @return An object list with found data from database, or an empty list.
*/
public static synchronized <T> List<T> findAll(Class<T> modelClass, boolean isEager,
long... ids) {
QueryHandler queryHandler = new QueryHandler(Connector.getDatabase());
return queryHandler.onFindAll(modelClass, isEager, ids);
}
/**
* Runs the provided SQL and returns a Cursor over the result set. You may
* include ?s in where clause in the query, which will be replaced by the
* second to the last parameters, such as:
*
* <pre>
* Cursor cursor = DataSupport.findBySQL("select * from person where name=? and age=?", "Tom", "14");
* </pre>
*
* @param sql
* First parameter is the SQL clause to apply. Second to the last
* parameters will replace the place holders.
* @return A Cursor object, which is positioned before the first entry. Note
* that Cursors are not synchronized, see the documentation for more
* details.
*/
public static synchronized Cursor findBySQL(String... sql) {
BaseUtility.checkConditionsCorrect(sql);
if (sql == null) {
return null;
}
if (sql.length <= 0) {
return null;
}
String[] selectionArgs;
if (sql.length == 1) {
selectionArgs = null;
} else {
selectionArgs = new String[sql.length - 1];
System.arraycopy(sql, 1, selectionArgs, 0, sql.length - 1);
}
return Connector.getDatabase().rawQuery(sql[0], selectionArgs);
}
/**
* Deletes the record in the database by id.<br>
* The data in other tables which is referenced with the record will be
* removed too.
*
* <pre>
* DataSupport.delete(Person.class, 1);
* </pre>
*
* This means that the record 1 in person table will be removed.
*
* @param modelClass
* Which table to delete from by class.
* @param id
* Which record to delete.
* @return The number of rows affected. Including cascade delete rows.
*/
public static synchronized int delete(Class<?> modelClass, long id) {
int rowsAffected = 0;
SQLiteDatabase db = Connector.getDatabase();
db.beginTransaction();
try {
DeleteHandler deleteHandler = new DeleteHandler(db);
rowsAffected = deleteHandler.onDelete(modelClass, id);
db.setTransactionSuccessful();
return rowsAffected;
} finally {
db.endTransaction();
}
}
/**
* Deletes all records with details given if they match a set of conditions
* supplied. This method constructs a single SQL DELETE statement and sends
* it to the database.
*
* <pre>
* DataSupport.deleteAll(Person.class, "name = ? and age = ?", "Tom", "14");
* </pre>
*
* This means that all the records which name is Tom and age is 14 will be
* removed.<br>
*
* Note that this method won't delete the referenced data in other tables.
* You should remove those values by your own.
*
* @param modelClass
* Which table to delete from by class.
* @param conditions
* A string array representing the WHERE part of an SQL
* statement. First parameter is the WHERE clause to apply when
* deleting. The way of specifying place holders is to insert one
* or more question marks in the SQL. The first question mark is
* replaced by the second element of the array, the next question
* mark by the third, and so on. Passing empty string will update
* all rows.
* @return The number of rows affected.
*/
public static synchronized int deleteAll(Class<?> modelClass, String... conditions) {
return deleteAll(BaseUtility.changeCase(modelClass.getSimpleName()), conditions);
}
/**
* Deletes all records with details given if they match a set of conditions
* supplied. This method constructs a single SQL DELETE statement and sends
* it to the database.
*
* <pre>
* DataSupport.deleteAll("person", "name = ? and age = ?", "Tom", "14");
* </pre>
*
* This means that all the records which name is Tom and age is 14 will be
* removed.<br>
*
* Note that this method won't delete the referenced data in other tables.
* You should remove those values by your own.
*
* @param tableName
* Which table to delete from.
* @param conditions
* A string array representing the WHERE part of an SQL
* statement. First parameter is the WHERE clause to apply when
* deleting. The way of specifying place holders is to insert one
* or more question marks in the SQL. The first question mark is
* replaced by the second element of the array, the next question
* mark by the third, and so on. Passing empty string will update
* all rows.
* @return The number of rows affected.
*/
public static synchronized int deleteAll(String tableName, String... conditions) {
DeleteHandler deleteHandler = new DeleteHandler(Connector.getDatabase());
return deleteHandler.onDeleteAll(tableName, conditions);
}
/**
* Updates the corresponding record by id with ContentValues. Returns the
* number of affected rows.
*
* <pre>
* ContentValues cv = new ContentValues();
* cv.put("name", "Jim");
* DataSupport.update(Person.class, cv, 1);
* </pre>
*
* This means that the name of record 1 will be updated into Jim.<br>
*
* @param modelClass
* Which table to update by class.
* @param values
* A map from column names to new column values. null is a valid
* value that will be translated to NULL.
* @param id
* Which record to update.
* @return The number of rows affected.
*/
public static synchronized int update(Class<?> modelClass, ContentValues values, long id) {
UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase());
return updateHandler.onUpdate(modelClass, id, values);
}
/**
* Updates all records with details given if they match a set of conditions
* supplied. This method constructs a single SQL UPDATE statement and sends
* it to the database.
*
* <pre>
* ContentValues cv = new ContentValues();
* cv.put("name", "Jim");
* DataSupport.update(Person.class, cv, "name = ?", "Tom");
* </pre>
*
* This means that all the records which name is Tom will be updated into
* Jim.
*
* @param modelClass
* Which table to update by class.
* @param values
* A map from column names to new column values. null is a valid
* value that will be translated to NULL.
* @param conditions
* A string array representing the WHERE part of an SQL
* statement. First parameter is the WHERE clause to apply when
* updating. The way of specifying place holders is to insert one
* or more question marks in the SQL. The first question mark is
* replaced by the second element of the array, the next question
* mark by the third, and so on. Passing empty string will update
* all rows.
* @return The number of rows affected.
*/
public static synchronized int updateAll(Class<?> modelClass, ContentValues values,
String... conditions) {
return updateAll(BaseUtility.changeCase(modelClass.getSimpleName()), values, conditions);
}
/**
* Updates all records with details given if they match a set of conditions
* supplied. This method constructs a single SQL UPDATE statement and sends
* it to the database.
*
* <pre>
* ContentValues cv = new ContentValues();
* cv.put("name", "Jim");
* DataSupport.update("person", cv, "name = ?", "Tom");
* </pre>
*
* This means that all the records which name is Tom will be updated into
* Jim.
*
* @param tableName
* Which table to update.
* @param values
* A map from column names to new column values. null is a valid
* value that will be translated to NULL.
* @param conditions
* A string array representing the WHERE part of an SQL
* statement. First parameter is the WHERE clause to apply when
* updating. The way of specifying place holders is to insert one
* or more question marks in the SQL. The first question mark is
* replaced by the second element of the array, the next question
* mark by the third, and so on. Passing empty string will update
* all rows.
* @return The number of rows affected.
*/
public static synchronized int updateAll(String tableName, ContentValues values,
String... conditions) {
UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase());
return updateHandler.onUpdateAll(tableName, values, conditions);
}
/**
* Saves the collection into database. <br />
*
* <pre>
* DataSupport.saveAll(people);
* </pre>
*
* If the model in collection is a new record gets created in the database,
* otherwise the existing record gets updated.<br />
* If saving process failed by any accident, the whole action will be
* cancelled and your database will be <b>rolled back</b>. <br />
* This method acts the same result as the below way, but <b>much more
* efficient</b>.
*
* <pre>
* for (Person person : people) {
* person.save();
* }
* </pre>
*
* So when your collection holds huge of models,
* {@link #saveAll(Collection)} is the better choice.
*
* @param collection
* Holds all models to save.
*/
public static synchronized <T extends DataSupport> void saveAll(Collection<T> collection) {
SQLiteDatabase db = Connector.getDatabase();
db.beginTransaction();
try {
SaveHandler saveHandler = new SaveHandler(db);
saveHandler.onSaveAll(collection);
db.setTransactionSuccessful();
} catch (Exception e) {
throw new DataSupportException(e.getMessage());
} finally {
db.endTransaction();
}
}
/**
* Deletes the record in the database. The record must be saved already.<br>
* The data in other tables which is referenced with the record will be
* removed too.
*
* <pre>
* Person person;
* ....
* if (person.isSaved()) {
* person.delete();
* }
* </pre>
*
* @return The number of rows affected. Including cascade delete rows.
*/
public synchronized int delete() {
SQLiteDatabase db = Connector.getDatabase();
db.beginTransaction();
try {
DeleteHandler deleteHandler = new DeleteHandler(db);
int rowsAffected = deleteHandler.onDelete(this);
db.setTransactionSuccessful();
return rowsAffected;
} finally {
db.endTransaction();
}
}
/**
* Updates the corresponding record by id. Use setXxx to decide which
* columns to update.
*
* <pre>
* Person person = new Person();
* person.setName("Jim");
* person.update(1);
* </pre>
*
* This means that the name of record 1 will be updated into Jim.<br>
*
* <b>Note: </b> 1. If you set a default value to a field, the corresponding
* column won't be updated. Use {@link #setToDefault(String)} to update
* columns into default value. 2. This method couldn't update foreign key in
* database. So do not use setXxx to set associations between models.
*
* @param id
* Which record to update.
* @return The number of rows affected.
*/
public synchronized int update(long id) {
try {
UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase());
int rowsAffected = updateHandler.onUpdate(this, id);
getFieldsToSetToDefault().clear();
return rowsAffected;
} catch (Exception e) {
throw new DataSupportException(e.getMessage());
}
}
/**
* Updates all records with details given if they match a set of conditions
* supplied. This method constructs a single SQL UPDATE statement and sends
* it to the database.
*
* <pre>
* Person person = new Person();
* person.setName("Jim");
* person.updateAll("name = ?", "Tom");
* </pre>
*
* This means that all the records which name is Tom will be updated into
* Jim.<br>
*
* <b>Note: <b> 1. If you set a default value to a field, the corresponding
* column won't be updated. Use {@link #setToDefault(String)} to update
* columns into default value. 2. This method couldn't update foreign key in
* database. So do not use setXxx to set associations between models.
*
* @param conditions
* A string array representing the WHERE part of an SQL
* statement. First parameter is the WHERE clause to apply when
* updating. The way of specifying place holders is to insert one
* or more question marks in the SQL. The first question mark is
* replaced by the second element of the array, the next question
* mark by the third, and so on. Passing empty string will update
* all rows.
* @return The number of rows affected.
*/
public synchronized int updateAll(String... conditions) {
try {
UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase());
int rowsAffected = updateHandler.onUpdateAll(this, conditions);
getFieldsToSetToDefault().clear();
return rowsAffected;
} catch (Exception e) {
throw new DataSupportException(e.getMessage());
}
}
/**
* Saves the model. <br />
*
* <pre>
* Person person = new Person();
* person.setName("Tom");
* person.setAge(22);
* person.save();
* </pre>
*
* If the model is a new record gets created in the database, otherwise the
* existing record gets updated.<br />
* If saving process failed by any accident, the whole action will be
* cancelled and your database will be <b>rolled back</b>. <br />
* If the model has a field named id or _id and field type is int or long,
* the id value generated by database will assign to it after the model is
* saved.<br />
* Note that if the associated models of this model is already saved. The
* associations between them will be built automatically in database after
* it saved.
*
* @return If the model is saved successfully, return true. Any exception
* happens, return false.
*/
public synchronized boolean save() {
try {
saveThrows();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* Saves the model. <br />
*
* <pre>
* Person person = new Person();
* person.setName("Tom");
* person.setAge(22);
* person.saveThrows();
* </pre>
*
* If the model is a new record gets created in the database, otherwise the
* existing record gets updated.<br />
* If saving process failed by any accident, the whole action will be
* cancelled and your database will be <b>rolled back</b> and throws
* {@link DataSupportException}<br />
* If the model has a field named id or _id and field type is int or long,
* the id value generated by database will assign to it after the model is
* saved.<br />
* Note that if the associated models of this model is already saved. The
* associations between them will be built automatically in database after
* it saved.
*
* @throws DataSupportException
*/
public synchronized void saveThrows() {
SQLiteDatabase db = Connector.getDatabase();
db.beginTransaction();
try {
SaveHandler saveHandler = new SaveHandler(db);
saveHandler.onSave(this);
clearAssociatedData();
db.setTransactionSuccessful();
} catch (Exception e) {
throw new DataSupportException(e.getMessage());
} finally {
db.endTransaction();
}
}
/**
* Current model is saved or not.
*
* @return If saved return true, or return false.
*/
public boolean isSaved() {
return baseObjId > 0;
}
/**
* When updating database with {@link DataSupport#update(long)}, you must
* use this method to update a field into default value. Use setXxx with
* default value of the model won't update anything. <br>
*
* @param fieldName
* The name of field to update into default value.
*/
public void setToDefault(String fieldName) {
getFieldsToSetToDefault().add(fieldName);
}
/**
* Disable developers to create instance of DataSupport directly. They
* should inherit this class with subclasses and operate on them.
*/
protected DataSupport() {
}
/**
* Get the baseObjId of this model if it's useful for developers. It's for
* system use usually. Do not try to assign or modify it.
*
* @return The base object id.
*/
protected long getBaseObjId() {
return baseObjId;
}
/**
* Get the full class name of self.
*
* @return The full class name of self.
*/
protected String getClassName() {
return getClass().getName();
}
/**
* Get the corresponding table name of current model.
*
* @return The corresponding table name of current model.
*/
protected String getTableName() {
return BaseUtility.changeCase(getClass().getSimpleName());
}
/**
* Get the list which holds all field names to update them into default
* value of model in database.
*
* @return List holds all the field names which need to be updated into
* default value of model.
*/
List<String> getFieldsToSetToDefault() {
if (fieldsToSetToDefault == null) {
fieldsToSetToDefault = new ArrayList<String>();
}
return fieldsToSetToDefault;
}
/**
* Add the id of an associated model into self model's associatedIdsWithFK
* map. The associated model has a foreign key column in the corresponding
* table.
*
* @param associatedTableName
* The table name of associated model.
* @param associatedId
* The {@link #baseObjId} of associated model after it is saved.
*/
void addAssociatedModelWithFK(String associatedTableName, long associatedId) {
Set<Long> associatedIdsWithFKSet = getAssociatedModelsMapWithFK().get(associatedTableName);
if (associatedIdsWithFKSet == null) {
associatedIdsWithFKSet = new HashSet<Long>();
associatedIdsWithFKSet.add(associatedId);
associatedModelsMapWithFK.put(associatedTableName, associatedIdsWithFKSet);
} else {
associatedIdsWithFKSet.add(associatedId);
}
}
/**
* Get the associated model's map of self model. It can be used for
* associations actions of CRUD. The key is the name of associated model.
* The value is a List of id of associated models.
*
* @return An associated model's map to update all the foreign key columns
* of associated models' table with self model's id.
*/
Map<String, Set<Long>> getAssociatedModelsMapWithFK() {
if (associatedModelsMapWithFK == null) {
associatedModelsMapWithFK = new HashMap<String, Set<Long>>();
}
return associatedModelsMapWithFK;
}
/**
* Add the id of an associated model into self model's associatedIdsM2M map.
*
* @param associatedModelName
* The name of associated model.
* @param associatedId
* The id of associated model.
*/
void addAssociatedModelForJoinTable(String associatedModelName, long associatedId) {
Set<Long> associatedIdsM2MSet = getAssociatedModelsMapForJoinTable().get(
associatedModelName);
if (associatedIdsM2MSet == null) {
associatedIdsM2MSet = new HashSet<Long>();
associatedIdsM2MSet.add(associatedId);
associatedModelsMapForJoinTable.put(associatedModelName, associatedIdsM2MSet);
} else {
associatedIdsM2MSet.add(associatedId);
}
}
/**
* Add an empty Set into {@link #associatedModelsMapForJoinTable} with
* associated model name as key. Might be useful when comes to update
* intermediate join table.
*
* @param associatedModelName
* The name of associated model.
*/
void addEmptyModelForJoinTable(String associatedModelName) {
Set<Long> associatedIdsM2MSet = getAssociatedModelsMapForJoinTable().get(
associatedModelName);
if (associatedIdsM2MSet == null) {
associatedIdsM2MSet = new HashSet<Long>();
associatedModelsMapForJoinTable.put(associatedModelName, associatedIdsM2MSet);
}
}
/**
* Get the associated model's map for intermediate join table. It is used to
* save values into intermediate join table. The key is the name of
* associated model. The value is the id of associated model.
*
* @return An associated model's map to save values into intermediate join
* table
*/
Map<String, Set<Long>> getAssociatedModelsMapForJoinTable() {
if (associatedModelsMapForJoinTable == null) {
associatedModelsMapForJoinTable = new HashMap<String, Set<Long>>();
}
return associatedModelsMapForJoinTable;
}
/**
* Add the id of an associated model into self model's association
* collection. The associated model doesn't have a foreign key column in the
* corresponding table. Instead self model has a foreign key column in the
* corresponding table.
*
* @param associatedTableName
* The simple class name of associated model.
* @param associatedId
* The {@link #baseObjId} of associated model after it is saved.
*/
void addAssociatedModelWithoutFK(String associatedTableName, long associatedId) {
getAssociatedModelsMapWithoutFK().put(associatedTableName, associatedId);
}
/**
* Get the associated model's map of self model. It can be used for
* associations actions of CRUD. The key is the name of associated model's
* table. The value is the id of associated model.
*
* @return An associated model's map to save self model with foreign key.
*/
Map<String, Long> getAssociatedModelsMapWithoutFK() {
if (associatedModelsMapWithoutFK == null) {
associatedModelsMapWithoutFK = new HashMap<String, Long>();
}
return associatedModelsMapWithoutFK;
}
/**
* Add a foreign key name into the clear list.
*
* @param foreignKeyName
* The name of foreign key.
*/
void addFKNameToClearSelf(String foreignKeyName) {
List<String> list = getListToClearSelfFK();
if (!list.contains(foreignKeyName)) {
list.add(foreignKeyName);
}
}
/**
* Get the foreign key name list to clear foreign key value in current
* model's table.
*
* @return The list of foreign key names to clear in current model's table.
*/
List<String> getListToClearSelfFK() {
if (listToClearSelfFK == null) {
listToClearSelfFK = new ArrayList<String>();
}
return listToClearSelfFK;
}
/**
* Add an associated table name into the list to clear.
*
* @param associatedTableName
* The name of associated table.
*/
void addAssociatedTableNameToClearFK(String associatedTableName) {
List<String> list = getListToClearAssociatedFK();
if (!list.contains(associatedTableName)) {
list.add(associatedTableName);
}
}
/**
* Get the associated table names list which need to clear their foreign key
* values.
*
* @return The list with associated table names to clear foreign key values.
*/
List<String> getListToClearAssociatedFK() {
if (listToClearAssociatedFK == null) {
listToClearAssociatedFK = new ArrayList<String>();
}
return listToClearAssociatedFK;
}
/**
* Clear all the data for storing associated models' data.
*/
void clearAssociatedData() {
clearIdOfModelWithFK();
clearIdOfModelWithoutFK();
clearIdOfModelForJoinTable();
clearFKNameList();
}
/**
* Clear all the data in {@link #associatedModelsMapWithFK}.
*/
private void clearIdOfModelWithFK() {
for (String associatedModelName : getAssociatedModelsMapWithFK().keySet()) {
associatedModelsMapWithFK.get(associatedModelName).clear();
}
associatedModelsMapWithFK.clear();
}
/**
* Clear all the data in {@link #associatedModelsMapWithoutFK}.
*/
private void clearIdOfModelWithoutFK() {
getAssociatedModelsMapWithoutFK().clear();
}
/**
* Clear all the data in {@link #associatedModelsMapForJoinTable}.
*/
private void clearIdOfModelForJoinTable() {
for (String associatedModelName : getAssociatedModelsMapForJoinTable().keySet()) {
associatedModelsMapForJoinTable.get(associatedModelName).clear();
}
associatedModelsMapForJoinTable.clear();
}
/**
* Clear all the data in {@link #listToClearSelfFK}.
*/
private void clearFKNameList() {
getListToClearSelfFK().clear();
getListToClearAssociatedFK().clear();
}
}