You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
git/java/net/micode/notes/gtask/data/SqlData.java

335 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* 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 net.micode.notes.gtask.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
/**
* 笔记应用data表的数据操作封装类
* 该类专门处理Android ContentProvider对data表的增改、数据差异对比、JSON序列化/反序列化以及持久化操作,
* 是GTask同步过程中处理data表数据的核心类。它会记录数据的变更差异mDiffDataValues仅提交有变化的数据
* 同时支持创建新数据和更新已有数据两种场景,并提供版本验证机制保证数据同步的一致性。
*
* @author MiCode Open Source Community
* @date 2010-2011
*/
public class SqlData {
/**
* 日志标签,使用类的简单名称,便于调试时定位日志来源
*/
private static final String TAG = SqlData.class.getSimpleName();
/**
* 无效的ID常量用于标记数据ID尚未初始化或不存在区别于数据库的自增ID
*/
private static final int INVALID_ID = -99999;
/**
* data表的查询投影Projection定义了查询时需要返回的列减少数据传输开销
* 包含ID、MIME_TYPE、CONTENT、DATA1、DATA3
*/
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
// ====================== PROJECTION_DATA的列索引常量 ======================
/**
* PROJECTION_DATA中ID列的索引对应DataColumns.ID
*/
public static final int DATA_ID_COLUMN = 0;
/**
* PROJECTION_DATA中MIME_TYPE列的索引对应DataColumns.MIME_TYPE
*/
public static final int DATA_MIME_TYPE_COLUMN = 1;
/**
* PROJECTION_DATA中CONTENT列的索引对应DataColumns.CONTENT
*/
public static final int DATA_CONTENT_COLUMN = 2;
/**
* PROJECTION_DATA中DATA1列的索引对应DataColumns.DATA1
*/
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
/**
* PROJECTION_DATA中DATA3列的索引对应DataColumns.DATA3
*/
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
// ====================== 成员变量 ======================
/**
* Android内容解析器用于访问ContentProvider进行数据的增删改查操作
*/
private ContentResolver mContentResolver;
/**
* 数据创建标记true表示当前是新数据需要插入数据库false表示已有数据需要更新数据库
*/
private boolean mIsCreate;
/**
* data表的主键ID关联到具体的行数据
*/
private long mDataId;
/**
* 数据的MIME类型如文本笔记DataConstants.NOTE通话笔记DataConstants.CALL_NOTE
*/
private String mDataMimeType;
/**
* 数据的内容(如文本笔记的文本内容)
*/
private String mDataContent;
/**
* DATA1列的数据整型如文本笔记的模式、通话笔记的通话时间
*/
private long mDataContentData1;
/**
* DATA3列的数据文本如通话笔记的电话号码
*/
private String mDataContentData3;
/**
* 数据差异的ContentValues仅存储有变化的字段用于提交到数据库减少不必要的更新
*/
private ContentValues mDiffDataValues;
/**
* 构造方法初始化新的SqlData对象用于创建新数据
*
* @param context 上下文对象用于获取ContentResolver
*/
public SqlData(Context context) {
// 获取ContentResolver实例用于访问ContentProvider
mContentResolver = context.getContentResolver();
// 标记为新数据,需要插入数据库
mIsCreate = true;
// 初始化ID为无效值
mDataId = INVALID_ID;
// 默认MIME类型为文本笔记
mDataMimeType = DataConstants.NOTE;
// 初始化内容为空字符串
mDataContent = "";
// 初始化DATA1为0
mDataContentData1 = 0;
// 初始化DATA3为空字符串
mDataContentData3 = "";
// 初始化数据差异容器
mDiffDataValues = new ContentValues();
}
/**
* 构造方法从Cursor中加载已有数据初始化SqlData对象用于更新已有数据
*
* @param context 上下文对象用于获取ContentResolver
* @param c 包含data表数据的Cursor对象需使用PROJECTION_DATA投影查询
*/
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
// 标记为已有数据,需要更新数据库
mIsCreate = false;
// 从Cursor中加载数据到成员变量
loadFromCursor(c);
// 初始化数据差异容器
mDiffDataValues = new ContentValues();
}
/**
* 辅助方法从Cursor中加载数据到成员变量
* 需保证Cursor使用PROJECTION_DATA投影查询否则会出现列索引越界异常
*
* @param c 包含data表数据的Cursor对象
*/
private void loadFromCursor(Cursor c) {
// 从Cursor中获取ID长整型
mDataId = c.getLong(DATA_ID_COLUMN);
// 获取MIME类型字符串
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
// 获取内容(字符串)
mDataContent = c.getString(DATA_CONTENT_COLUMN);
// 获取DATA1长整型
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
// 获取DATA3字符串
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
/**
* 根据JSON对象设置数据内容并记录数据差异到mDiffDataValues
* 核心逻辑对比当前数据与JSON中的数据仅将有变化的字段加入差异容器新数据则直接加入所有字段
*
* @param js 包含data表数据的JSON对象
* @throws JSONException JSON解析异常如字段类型不匹配、缺失等
*/
public void setContent(JSONObject js) throws JSONException {
// 从JSON中获取ID默认值为INVALID_ID
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
// 新数据或ID变化时将ID加入差异容器
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
// 更新当前ID
mDataId = dataId;
// 从JSON中获取MIME类型默认值为文本笔记
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
// 新数据或MIME类型变化时将MIME_TYPE加入差异容器
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
// 更新当前MIME类型
mDataMimeType = dataMimeType;
// 从JSON中获取内容默认值为空字符串
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
// 新数据或内容变化时将CONTENT加入差异容器
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
// 更新当前内容
mDataContent = dataContent;
// 从JSON中获取DATA1默认值为0
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
// 新数据或DATA1变化时将DATA1加入差异容器
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
// 更新当前DATA1
mDataContentData1 = dataContentData1;
// 从JSON中获取DATA3默认值为空字符串
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
// 新数据或DATA3变化时将DATA3加入差异容器
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
// 更新当前DATA3
mDataContentData3 = dataContentData3;
}
/**
* 将当前数据序列化为JSON对象
* 仅当数据已存在非新数据时才会序列化否则输出错误日志并返回null
*
* @return 包含data表数据的JSON对象
* @throws JSONException JSON序列化异常如字段写入失败
*/
public JSONObject getContent() throws JSONException {
// 新数据尚未持久化输出错误日志并返回null
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
// 创建JSON对象并写入所有数据字段
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
js.put(DataColumns.CONTENT, mDataContent);
js.put(DataColumns.DATA1, mDataContentData1);
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
/**
* 提交数据变更到数据库(插入或更新)
* 支持版本验证机制保证同步时数据的一致性仅当note表的版本匹配时才更新
*
* @param noteId 关联的note表IDdata表的NOTE_ID列
* @param validateVersion 是否开启版本验证true表示验证false表示不验证
* @param version 需要验证的note表版本号仅当validateVersion为true时生效
*/
public void commit(long noteId, boolean validateVersion, long version) {
// 新数据:执行插入操作
if (mIsCreate) {
// 若ID为无效值且差异容器中包含ID移除该ID数据库自增ID无需手动设置
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
// 添加关联的note表ID到差异容器
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
// 插入数据到ContentProvider获取返回的Uri包含新数据的ID
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
// 从Uri中解析出新数据的IDUri路径分段的第二个元素如data/123中的123
mDataId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
// 解析ID失败输出错误日志并抛出同步失败异常
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
} else {
// 已有数据:仅当存在差异时执行更新操作
if (mDiffDataValues.size() > 0) {
int result = 0;
if (!validateVersion) {
// 不验证版本直接更新数据根据data表ID
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
// 验证版本仅当note表中对应ID的版本号匹配时才更新
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
});
}
// 更新结果为0说明数据未更新可能同步时用户修改了数据输出警告日志
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
}
// 清空差异容器,标记数据为已存在(非新数据)
mDiffDataValues.clear();
mIsCreate = false;
}
/**
* 获取当前data表数据的ID
*
* @return data表的主键ID若未初始化则返回INVALID_ID
*/
public long getId() {
return mDataId;
}
}