/* * 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.database.Cursor; import android.text.TextUtils; 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.gtask.exception.ActionFailureException; import net.micode.notes.tool.GTaskStringUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class Task extends Node { private static final String TAG = Task.class.getSimpleName(); private boolean mCompleted; private String mNotes; private JSONObject mMetaInfo; private Task mPriorSibling; private TaskList mParent; public Task() { super(); mCompleted = false; mNotes = null; mPriorSibling = null; mParent = null; mMetaInfo = null; } /** * 生成创建任务的JSON对象 * * @param actionId 操作的ID,通常用于标识这个创建动作 * @return 包含创建任务所需信息的JSON对象 * @throws ActionFailureException 如果无法生成JSON对象,则抛出此异常 */ public JSONObject getCreateAction(int actionId) { // 创建一个新的JSONObject来存储结果 JSONObject js = new JSONObject(); try { // 添加动作类型到JSON对象中,这里指定为创建动作 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); // 添加动作ID到JSON对象中 js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); // 添加任务在当前父级中的索引位置到JSON对象中 // 假设mParent是一个包含子任务的对象,并且getChildTaskIndex方法返回当前任务在其中的索引 js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); // 创建一个新的JSONObject来存储任务实体的变更信息 JSONObject entity = new JSONObject(); // 添加任务名称到实体对象中 entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 添加创建者ID到实体对象中,这里假设为"null",可能表示未知或不需要指定 entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 添加实体类型到实体对象中,这里指定为任务类型 entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_TASK); // 如果存在笔记信息,则添加到实体对象中 if (getNotes() != null) { entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); } // 将实体对象的变更信息添加到主JSON对象中 js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 添加父级ID到主JSON对象中,这里假设mParent有一个getGid方法返回其唯一标识符 js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); // 添加目标父级类型到主JSON对象中,这里指定为组类型 js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_GROUP); // 添加列表ID到主JSON对象中,这里假设与父级ID相同 js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); // 如果存在前一个兄弟任务,则添加其ID到主JSON对象中 // 假设mPriorSibling是一个表示前一个兄弟任务的对象,并且getGid方法返回其唯一标识符 if (mPriorSibling != null) { js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); } } catch (JSONException e) { // 如果在创建JSON对象时发生异常,则记录错误日志并抛出自定义异常 Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("fail to generate task-create jsonobject"); } // 返回包含创建任务所需信息的JSON对象 return js; } /** * 生成更新任务的JSON对象 * * @param actionId 操作的ID,用于标识这个更新动作 * @return 包含更新任务所需信息的JSON对象 * @throws ActionFailureException 如果无法生成JSON对象,则抛出此异常 */ public JSONObject getUpdateAction(int actionId) { // 创建一个新的JSONObject实例,用于存储生成的JSON数据 JSONObject js = new JSONObject(); try { // 添加动作类型到JSON对象中,这里指定为更新动作 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); // 添加动作ID到JSON对象中,用于标识这个特定的更新操作 js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); // 添加任务的唯一标识符(ID)到JSON对象中,这是需要更新的目标任务的ID js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); // 假设getGid()方法返回当前任务的唯一ID // 创建一个新的JSONObject实例,用于存储任务实体的变更信息 JSONObject entity = new JSONObject(); // 添加任务名称到实体变更信息中 entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 假设getName()方法返回当前任务的名称 // 如果存在笔记信息,则将其添加到实体变更信息中 if (getNotes() != null) { entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 假设getNotes()方法返回当前任务的笔记信息 } // 添加任务是否被删除的标志到实体变更信息中 // 假设getDeleted()方法返回一个布尔值,表示任务是否被标记为删除 entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 将包含任务实体变更信息的JSON对象添加到主JSON对象中 js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); } catch (JSONException e) { // 如果在创建JSON对象的过程中发生异常,则记录错误日志 Log.e(TAG, e.toString()); // 打印堆栈跟踪信息,有助于调试 e.printStackTrace(); // 抛出一个自定义的异常,表示无法生成JSON对象 throw new ActionFailureException("fail to generate task-update jsonobject"); } // 返回包含更新任务所需信息的JSON对象 return js; } /** * 通过远程JSON对象设置任务内容 * * @param js 包含任务信息的JSON对象 * @throws ActionFailureException 如果无法从JSON对象中获取任务内容,则抛出此异常 */ public void setContentByRemoteJSON(JSONObject js) { // 检查传入的JSON对象是否为null if (js != null) { try { // 从JSON对象中提取任务ID,并设置到当前任务对象中 // 假设GTaskStringUtils.GTASK_JSON_ID是任务ID在JSON中的键名 // setGid()方法用于设置当前任务的唯一标识符(ID) if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); } // 从JSON对象中提取最后修改时间,并设置到当前任务对象中 // 假设GTaskStringUtils.GTASK_JSON_LAST_MODIFIED是最后修改时间在JSON中的键名 // setLastModified()方法用于设置任务的最后修改时间 if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); } // 从JSON对象中提取任务名称,并设置到当前任务对象中 // 假设GTaskStringUtils.GTASK_JSON_NAME是任务名称在JSON中的键名 // setName()方法用于设置任务的名称 if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); } // 从JSON对象中提取笔记信息,并设置到当前任务对象中 // 假设GTaskStringUtils.GTASK_JSON_NOTES是笔记信息在JSON中的键名 // setNotes()方法用于设置任务的笔记信息 if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); } // 从JSON对象中提取任务是否被删除的标志,并设置到当前任务对象中 // 假设GTaskStringUtils.GTASK_JSON_DELETED是删除标志在JSON中的键名 // setDeleted()方法用于设置任务是否被删除 if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); } // 从JSON对象中提取任务是否完成的标志,并设置到当前任务对象中 // 假设GTaskStringUtils.GTASK_JSON_COMPLETED是完成标志在JSON中的键名 // setCompleted()方法用于设置任务是否已完成 if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); } } catch (JSONException e) { // 如果在解析JSON对象的过程中发生异常,则记录错误日志 Log.e(TAG, e.toString()); // 打印堆栈跟踪信息,有助于调试 e.printStackTrace(); // 抛出一个自定义的异常,表示无法从JSON对象中获取任务内容 throw new ActionFailureException("fail to get task content from jsonobject"); } } // 如果传入的JSON对象为null,则不进行任何操作(这里也可以考虑添加日志记录或抛出异常) } /** * 通过本地JSON对象设置任务内容 * * @param js 包含任务笔记和数据信息的JSON对象 */ public void setContentByLocalJSON(JSONObject js) { // 检查传入的JSON对象是否为null,或者是否缺少必要的键(笔记头和数据头) if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) || !js.has(GTaskStringUtils.META_HEAD_DATA)) { // 如果条件满足,记录一条警告日志,表示没有可用的内容来设置任务 Log.w(TAG, "setContentByLocalJSON: nothing is available"); // 方法结束,不执行后续操作 return; } try { // 从JSON对象中获取笔记部分的JSON对象 JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 从JSON对象中获取数据部分的JSON数组 JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 检查笔记的类型是否为预期的笔记类型(这里假设Notes.TYPE_NOTE是预期的笔记类型常量) if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { // 如果类型不匹配,记录一条错误日志,并结束方法执行 Log.e(TAG, "invalid type"); return; } // 遍历数据数组,查找类型为笔记的数据项 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); // 检查当前数据项的MIME类型是否为预期的笔记类型(这里假设DataConstants.NOTE是预期的MIME类型字符串) if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { // 如果找到匹配的笔记类型数据项,则设置任务的名称为该数据项的内容 setName(data.getString(DataColumns.CONTENT)); // 找到匹配项后,跳出循环(假设只需要设置第一个找到的笔记内容作为任务名称) break; } } } catch (JSONException e) { // 如果在解析JSON对象的过程中发生异常,则记录错误日志 Log.e(TAG, e.toString()); // 打印堆栈跟踪信息,有助于调试 e.printStackTrace(); // 注意:这里没有抛出异常或进行其他错误处理,方法将正常结束 } // 方法正常结束,无论是否成功设置任务内容 } /** * 从任务内容中获取本地JSON对象 * * @return 包含任务笔记和数据信息的JSON对象,或者在出错时返回null */ public JSONObject getLocalJSONFromContent() { // 获取任务名称 String name = getName(); try { // 检查成员变量mMetaInfo是否为null,mMetaInfo通常用于存储已同步的任务信息 if (mMetaInfo == null) { // 如果mMetaInfo为null,表示这是一个新创建的任务(可能来自网页等) // 如果任务名称为null,则记录一条警告日志,并返回null,表示没有有效的内容来生成JSON对象 if (name == null) { Log.w(TAG, "the note seems to be an empty one"); return null; } // 创建一个新的JSON对象来存储任务信息 JSONObject js = new JSONObject(); // 创建一个JSON对象来存储笔记信息 JSONObject note = new JSONObject(); // 创建一个JSON数组来存储数据项 JSONArray dataArray = new JSONArray(); // 创建一个数据项的JSON对象,并设置其内容为任务名称 JSONObject data = new JSONObject(); data.put(DataColumns.CONTENT, name); // 将数据项添加到数据数组中 dataArray.put(data); // 将数据数组添加到JSON对象中,作为数据头 js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); // 设置笔记类型为预期的笔记类型(这里假设Notes.TYPE_NOTE是预期的笔记类型常量) note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 将笔记信息添加到JSON对象中,作为笔记头 js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 返回构建好的JSON对象 return js; } else { // 如果mMetaInfo不为null,表示这是一个已同步的任务 // 从mMetaInfo中获取笔记信息的JSON对象 JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 从mMetaInfo中获取数据项的JSON数组 JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 遍历数据数组,查找类型为笔记的数据项 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); // 如果找到匹配的数据项,则更新其内容为任务名称 if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { data.put(DataColumns.CONTENT, getName()); // 找到匹配项后,可以跳出循环(假设只需要更新第一个找到的笔记内容) break; } } // 注意:这里有一个潜在的问题,即如果遍历完数据数组都没有找到匹配的数据项, // 那么任务名称将不会被更新到任何数据项中。但在这个方法的当前实现中, // 我们没有对此情况进行处理,可能是因为在设计时就假设了每个已同步的任务都会有一个匹配的笔记数据项。 // 更新笔记类型为预期的笔记类型(虽然之前已经从mMetaInfo中获取了note,但这里再次设置可能是为了确保类型正确) note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 返回mMetaInfo,它现在包含了更新后的数据(如果找到了匹配的数据项) // 或者仍然是原始状态(如果没有找到匹配的数据项,但笔记类型已被重新设置) return mMetaInfo; } } catch (JSONException e) { // 如果在解析或构建JSON对象的过程中发生异常,则记录错误日志 Log.e(TAG, e.toString()); // 打印堆栈跟踪信息,有助于调试 e.printStackTrace(); // 返回null,表示由于异常而无法生成有效的JSON对象 return null; } } public void setMetaInfo(MetaData metaData) { if (metaData != null && metaData.getNotes() != null) { try { mMetaInfo = new JSONObject(metaData.getNotes()); } catch (JSONException e) { Log.w(TAG, e.toString()); mMetaInfo = null; } } } /** * 根据数据库游标(Cursor)中的信息确定同步操作 * * @param c 包含笔记信息的数据库游标 * @return 表示同步操作的整数值 */ public int getSyncAction(Cursor c) { try { // 初始化一个JSONObject来存储笔记的元数据 JSONObject noteInfo = null; // 如果成员变量mMetaInfo不为空且包含笔记的元数据头,则获取该元数据 if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) { noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); } // 如果noteInfo为空,表示笔记的元数据似乎已被删除 if (noteInfo == null) { Log.w(TAG, "it seems that note meta has been deleted"); // 返回更新远程的同步操作,因为本地似乎没有有效的笔记元数据 return SYNC_ACTION_UPDATE_REMOTE; } // 如果noteInfo不包含笔记ID,表示远程笔记ID似乎已被删除 if (!noteInfo.has(NoteColumns.ID)) { Log.w(TAG, "remote note id seems to be deleted"); // 返回更新本地的同步操作,因为远程似乎没有有效的笔记ID return SYNC_ACTION_UPDATE_LOCAL; } // 验证笔记ID // 如果数据库游标中的笔记ID与元数据中的笔记ID不匹配 if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { Log.w(TAG, "note id doesn't match"); // 返回更新本地的同步操作,因为ID不匹配,需要更新本地以匹配远程 return SYNC_ACTION_UPDATE_LOCAL; } // 检查本地是否有更新 if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { // 如果没有本地更新 // 检查同步ID是否等于最后修改时间,以确定两侧是否都没有更新 if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { // 如果没有更新,则返回不需要同步的操作 return SYNC_ACTION_NONE; } else { // 如果远程有更新,则应用远程更新到本地 return SYNC_ACTION_UPDATE_LOCAL; } } else { // 如果有本地更新 // 验证gtask ID以确保是同一个任务 if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { Log.e(TAG, "gtask id doesn't match"); // 如果gtask ID不匹配,则返回错误操作 return SYNC_ACTION_ERROR; } // 检查同步ID是否等于最后修改时间,以确定是否是仅本地修改 if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { // 如果是仅本地修改,则返回更新远程的同步操作 return SYNC_ACTION_UPDATE_REMOTE; } else { // 如果本地和远程都有修改,则返回冲突操作 return SYNC_ACTION_UPDATE_CONFLICT; } } } catch (Exception e) { // 如果在获取同步操作时发生异常,则记录错误日志并打印堆栈跟踪信息 Log.e(TAG, e.toString()); e.printStackTrace(); } // 如果发生异常或无法确定同步操作,则返回错误操作 return SYNC_ACTION_ERROR; } public boolean isWorthSaving() { return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) || (getNotes() != null && getNotes().trim().length() > 0); } public void setCompleted(boolean completed) { this.mCompleted = completed; } public void setNotes(String notes) { this.mNotes = notes; } public void setPriorSibling(Task priorSibling) { this.mPriorSibling = priorSibling; } public void setParent(TaskList parent) { this.mParent = parent; } public boolean getCompleted() { return this.mCompleted; } public String getNotes() { return this.mNotes; } public Task getPriorSibling() { return this.mPriorSibling; } public TaskList getParent() { return this.mParent; } }