/*
* Copyright (C) 2013 litesuits.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 com.litesuits.orm;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteClosable;
import android.database.sqlite.SQLiteDatabase;
import com.litesuits.orm.db.DataBase;
import com.litesuits.orm.db.DataBaseConfig;
import com.litesuits.orm.db.TableManager;
import com.litesuits.orm.db.annotation.Check;
import com.litesuits.orm.db.assit.*;
import com.litesuits.orm.db.impl.CascadeSQLiteImpl;
import com.litesuits.orm.db.impl.SingleSQLiteImpl;
import com.litesuits.orm.db.model.*;
import com.litesuits.orm.db.utils.ClassUtil;
import com.litesuits.orm.db.utils.DataUtil;
import com.litesuits.orm.db.utils.FieldUtil;
import com.litesuits.orm.log.OrmLog;
import java.io.File;
import java.io.FileFilter;
import java.util.*;
/**
* 数据SQLite操作实现
* 可查阅 <a href="http://www.sqlite.org/lang.html">SQLite操作指南</a>
*
* @author mty
* @date 2013-6-2下午2:32:56
*/
public abstract class LiteOrm extends SQLiteClosable implements DataBase {
public static final String TAG = LiteOrm.class.getSimpleName();
protected SQLiteHelper mHelper;
protected DataBaseConfig mConfig;
protected TableManager mTableManager;
protected LiteOrm otherDatabase;
protected LiteOrm(LiteOrm dataBase) {
this.mHelper = dataBase.mHelper;
this.mConfig = dataBase.mConfig;
this.mTableManager = dataBase.mTableManager;
this.otherDatabase = dataBase;
}
protected LiteOrm(DataBaseConfig config) {
config.context = config.context.getApplicationContext();
if (config.dbName == null) {
config.dbName = DataBaseConfig.DEFAULT_DB_NAME;
}
if (config.dbVersion <= 0) {
config.dbVersion = DataBaseConfig.DEFAULT_DB_VERSION;
}
mConfig = config;
setDebugged(config.debugged);
openOrCreateDatabase();
}
@Override
public SQLiteDatabase openOrCreateDatabase() {
initDatabasePath(mConfig.dbName);
if (mHelper != null) {
justRelease();
}
mHelper = new SQLiteHelper(mConfig.context.getApplicationContext(),
mConfig.dbName, null, mConfig.dbVersion, mConfig.onUpdateListener);
mTableManager = new TableManager(mConfig.dbName, mHelper.getReadableDatabase());
return mHelper.getWritableDatabase();
}
private void initDatabasePath(String path) {
OrmLog.i(TAG, "create database path: " + path);
path = mConfig.context.getDatabasePath(mConfig.dbName).getPath();
OrmLog.i(TAG, "context database path: " + path);
File dbp = new File(path).getParentFile();
if (dbp != null && !dbp.exists()) {
boolean mks = dbp.mkdirs();
OrmLog.i(TAG, "create database, parent file mkdirs: " + mks + " path:" + dbp.getAbsolutePath());
}
}
/**
* get and new a single model operator based on SQLite
*
* @param context app context
* @param dbName database name
* @return {@link SingleSQLiteImpl}
*/
public static LiteOrm newSingleInstance(Context context, String dbName) {
return newSingleInstance(new DataBaseConfig(context, dbName));
}
/**
* get and new a single model operator based on SQLite
*
* @param config lite-orm config
* @return {@link CascadeSQLiteImpl}
*/
public synchronized static LiteOrm newSingleInstance(DataBaseConfig config) {
return SingleSQLiteImpl.newInstance(config);
}
/**
* get and new a cascade model operator based on SQLite
*
* @param context app context
* @param dbName database name
* @return {@link SingleSQLiteImpl}
*/
public static LiteOrm newCascadeInstance(Context context, String dbName) {
return newCascadeInstance(new DataBaseConfig(context, dbName));
}
/**
* get and new a cascade model operator based on SQLite
*
* @param config lite-orm config
* @return {@link CascadeSQLiteImpl}
*/
public synchronized static LiteOrm newCascadeInstance(DataBaseConfig config) {
return CascadeSQLiteImpl.newInstance(config);
}
/**
* get a single data operator based on SQLite
*
* @return {@link com.litesuits.orm.db.impl.CascadeSQLiteImpl}
*/
public abstract LiteOrm single();
/**
* get a cascade data operator based on SQLite
*
* @return {@link com.litesuits.orm.db.impl.CascadeSQLiteImpl}
*/
public abstract LiteOrm cascade();
/**
* when debugged is true, the {@link OrmLog} is opened.
*
* @param debugged true if debugged
*/
public void setDebugged(boolean debugged) {
mConfig.debugged = debugged;
OrmLog.isPrint = debugged;
}
@Override
public ArrayList<RelationKey> queryRelation(final Class class1, final Class class2, final List<String> key1List) {
acquireReference();
final ArrayList<RelationKey> rList = new ArrayList<RelationKey>();
try {
final EntityTable table1 = TableManager.getTable(class1);
final EntityTable table2 = TableManager.getTable(class2);
if (mTableManager.isSQLMapTableCreated(table1.name, table2.name)) {
CollSpliter.split(key1List, SQLStatement.IN_TOP_LIMIT, new CollSpliter.Spliter<String>() {
@Override
public int oneSplit(ArrayList<String> list) throws Exception {
SQLStatement stmt = SQLBuilder.buildQueryRelationSql(class1, class2, key1List);
Querier.doQuery(mHelper.getReadableDatabase(), stmt, new Querier.CursorParser() {
@Override
public void parseEachCursor(SQLiteDatabase db, Cursor c) throws Exception {
RelationKey relation = new RelationKey();
relation.key1 = c.getString(c.getColumnIndex(table1.name));
relation.key2 = c.getString(c.getColumnIndex(table2.name));
rList.add(relation);
}
});
return 0;
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
releaseReference();
}
return rList;
}
@Override
public <E, T> boolean mapping(Collection<E> col1, Collection<T> col2) {
if (Checker.isEmpty(col1) || Checker.isEmpty(col2)) {
return false;
}
acquireReference();
try {
return keepMapping(col1, col2) | keepMapping(col2, col1);
} catch (Exception e) {
e.printStackTrace();
} finally {
releaseReference();
}
return false;
}
@Override
public SQLStatement createSQLStatement(String sql, Object[] bindArgs) {
return new SQLStatement(sql, bindArgs);
}
@Override
public boolean execute(SQLiteDatabase db, SQLStatement statement) {
acquireReference();
try {
if (statement != null) {
return statement.execute(db);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
releaseReference();
}
return false;
}
@Override
@Deprecated
public boolean dropTable(Object entity) {
return dropTable(entity.getClass());
}
@Override
public boolean dropTable(Class<?> claxx) {
return dropTable(TableManager.getTable(claxx, false).name);
}
@Override
public boolean dropTable(String tableName) {
acquireReference();
try {
return SQLBuilder.buildDropTable(tableName).execute(mHelper.getWritableDatabase());
} catch (Exception e) {
e.printStackTrace();
} finally {
releaseReference();
}
return false;
}
@Override
public <T> long queryCount(Class<T> claxx) {
return queryCount(new QueryBuilder<T>(claxx));
}
@Override
public long queryCount(QueryBuilder qb) {
acquireReference();
try {
if (mTableManager.isSQLTableCreated(qb.getTableName())) {
SQLiteDatabase db = mHelper.getReadableDatabase();
SQLStatement stmt = qb.createStatementForCount();
return stmt.queryForLong(db);
} else {
return 0;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
releaseReference();
}
return SQLStatement.NONE;
}
@Override
public int update(WhereBuilder where, ColumnsValue cvs, ConflictAlgorithm conflictAlgorithm) {
acquireReference();
try {
SQLiteDatabase db = mHelper.getWritableDatabase();
SQLStatement stmt = SQLBuilder.buildUpdateSql(where, cvs, conflictAlgorithm);
return stmt.execUpdate(db);
} catch (Exception e) {
e.printStackTrace();
} finally {
releaseReference();
}
return SQLStatement.NONE;
}
@Override
public synchronized SQLiteDatabase getReadableDatabase() {
return mHelper.getReadableDatabase();
}
@Override
public synchronized SQLiteDatabase getWritableDatabase() {
return mHelper.getWritableDatabase();
}
@Override
public TableManager getTableManager() {
return mTableManager;
}
@Override
public SQLiteHelper getSQLiteHelper() {
return mHelper;
}
@Override
public DataBaseConfig getDataBaseConfig() {
return mConfig;
}
@Override
public SQLiteDatabase openOrCreateDatabase(String path, SQLiteDatabase.CursorFactory factory) {
path = mConfig.context.getDatabasePath(mConfig.dbName).getPath();
return SQLiteDatabase.openOrCreateDatabase(path, factory);
}
@Override
public boolean deleteDatabase() {
String path = mHelper.getWritableDatabase().getPath();
justRelease();
OrmLog.i(TAG, "data has cleared. delete Database path: " + path);
return deleteDatabase(new File(path));
}
@Override
public boolean deleteDatabase(File file) {
acquireReference();
try {
if (file == null) {
throw new IllegalArgumentException("file must not be null");
}
boolean deleted = file.delete();
deleted |= new File(file.getPath() + "-journal").delete();
deleted |= new File(file.getPath() + "-shm").delete();
deleted |= new File(file.getPath() + "-wal").delete();
File dir = file.getParentFile();
if (dir != null) {
final String prefix = file.getName() + "-mj";
final FileFilter filter = new FileFilter() {
@Override
public boolean accept(File candidate) {
return candidate.getName().startsWith(prefix);
}
};
for (File masterJournal : dir.listFiles(filter)) {
deleted |= masterJournal.delete();
}
}
return deleted;
} catch (Exception e) {
e.printStackTrace();
} finally {
releaseReference();
}
return false;
}
@Override
public synchronized void close() {
releaseReference();
}
/**
* refCountIsZero 降到0时自动触发释放各种资源
*/
@Override
protected void onAllReferencesReleased() {
justRelease();
}
protected void justRelease() {
if (mHelper != null) {
mHelper.getWritableDatabase().close();
mHelper.close();
mHelper = null;
}
if (mTableManager != null) {
mTableManager.release();
mTableManager = null;
}
}
/**
* Attempts to release memory that SQLite holds but does not require to
* operate properly. Typically this memory will come from the page cache.
*
* @return the number of bytes actually released
*/
public static int releaseMemory() {
return SQLiteDatabase.releaseMemory();
}
/* -------------------------------- 私有方法 -------------------------------- */
@SuppressWarnings("unchecked")
private <E, T> boolean keepMapping(Collection<E> col1,
Collection<T> col2) throws IllegalAccessException, InstantiationException {
Class claxx1 = col1.iterator().next().getClass();
Class claxx2 = col2.iterator().next().getClass();
EntityTable table1 = TableManager.getTable(claxx1);
EntityTable table2 = TableManager.getTable(claxx2);
if (table1.mappingList != null) {
for (MapProperty mp : table1.mappingList) {
Class itemClass;
Class fieldClass = mp.field.getType();
if (mp.isToMany()) {
// N对多关系
if (ClassUtil.isCollection(fieldClass)) {
itemClass = FieldUtil.getGenericType(mp.field);
} else if (fieldClass.isArray()) {
itemClass = FieldUtil.getComponentType(mp.field);
} else {
throw new RuntimeException(
"OneToMany and ManyToMany Relation, Must use collection or array object");
}
} else {
itemClass = fieldClass;
}
if (itemClass == claxx2) {
ArrayList<String> key1List = new ArrayList<String>();
HashMap<String, Object> map1 = new HashMap<String, Object>();
// 构建第1个集合对象的key集合以及value映射
for (Object o1 : col1) {
if (o1 != null) {
Object key1 = FieldUtil.get(table1.key.field, o1);
if (key1 != null) {
key1List.add(key1.toString());
map1.put(key1.toString(), o1);
}
}
}
ArrayList<RelationKey> relationKeys = queryRelation(claxx1, claxx2, key1List);
if (!Checker.isEmpty(relationKeys)) {
HashMap<String, Object> map2 = new HashMap<String, Object>();
// 构建第2个对象的value映射
for (Object o2 : col2) {
if (o2 != null) {
Object key2 = FieldUtil.get(table2.key.field, o2);
if (key2 != null) {
map2.put(key2.toString(), o2);
}
}
}
HashMap<Object, ArrayList> collMap = new HashMap<Object, ArrayList>();
for (RelationKey m : relationKeys) {
Object obj1 = map1.get(m.key1);
Object obj2 = map2.get(m.key2);
if (obj1 != null && obj2 != null) {
if (mp.isToMany()) {
// N对多关系
ArrayList col = collMap.get(obj1);
if (col == null) {
col = new ArrayList();
collMap.put(obj1, col);
}
col.add(obj2);
} else {
FieldUtil.set(mp.field, obj1, obj2);
}
}
}
// N对多关系,查出来的数组
if (!Checker.isEmpty(collMap)) {
for (Map.Entry<Object, ArrayList> entry : collMap.entrySet()) {
Object obj1 = entry.getKey();
Collection tempColl = entry.getValue();
if (ClassUtil.isCollection(itemClass)) {
Collection col = (Collection) FieldUtil.get(mp.field, obj1);
if (col == null) {
FieldUtil.set(mp.field, obj1, tempColl);
} else {
col.addAll(tempColl);
}
} else if (ClassUtil.isArray(itemClass)) {
Object[] tempArray = (Object[]) ClassUtil.newArray(itemClass, tempColl.size());
tempColl.toArray(tempArray);
Object[] array = (Object[]) FieldUtil.get(mp.field, obj1);
if (array == null) {
FieldUtil.set(mp.field, obj1, tempArray);
} else {
Object[] newArray = DataUtil.concat(array, tempArray);
FieldUtil.set(mp.field, obj1, newArray);
}
}
}
}
return true;
}
}
}
}
return false;
}
}