mirror of https://github.com/tasks/tasks
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
596 lines
20 KiB
Java
596 lines
20 KiB
Java
/**
|
|
* Copyright (c) 2012 Todoroo Inc
|
|
*
|
|
* See the file "LICENSE" for the full license governing this code.
|
|
*/
|
|
package com.todoroo.astrid.service;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Map.Entry;
|
|
|
|
import android.content.ContentValues;
|
|
import android.text.TextUtils;
|
|
|
|
import com.todoroo.andlib.data.Property;
|
|
import com.todoroo.andlib.data.TodorooCursor;
|
|
import com.todoroo.andlib.service.Autowired;
|
|
import com.todoroo.andlib.service.DependencyInjectionService;
|
|
import com.todoroo.andlib.sql.Criterion;
|
|
import com.todoroo.andlib.sql.Field;
|
|
import com.todoroo.andlib.sql.Functions;
|
|
import com.todoroo.andlib.sql.Join;
|
|
import com.todoroo.andlib.sql.Order;
|
|
import com.todoroo.andlib.sql.Query;
|
|
import com.todoroo.andlib.utility.AndroidUtilities;
|
|
import com.todoroo.andlib.utility.DateUtilities;
|
|
import com.todoroo.andlib.utility.Preferences;
|
|
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
|
|
import com.todoroo.astrid.actfm.sync.messages.NameMaps;
|
|
import com.todoroo.astrid.adapter.UpdateAdapter;
|
|
import com.todoroo.astrid.api.Filter;
|
|
import com.todoroo.astrid.api.PermaSql;
|
|
import com.todoroo.astrid.core.PluginServices;
|
|
import com.todoroo.astrid.dao.MetadataDao;
|
|
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
|
|
import com.todoroo.astrid.dao.TaskDao;
|
|
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
|
|
import com.todoroo.astrid.dao.TaskOutstandingDao;
|
|
import com.todoroo.astrid.dao.UserActivityDao;
|
|
import com.todoroo.astrid.data.History;
|
|
import com.todoroo.astrid.data.Metadata;
|
|
import com.todoroo.astrid.data.RemoteModel;
|
|
import com.todoroo.astrid.data.SyncFlags;
|
|
import com.todoroo.astrid.data.Task;
|
|
import com.todoroo.astrid.data.TaskOutstanding;
|
|
import com.todoroo.astrid.data.User;
|
|
import com.todoroo.astrid.data.UserActivity;
|
|
import com.todoroo.astrid.gcal.GCalHelper;
|
|
import com.todoroo.astrid.gtasks.GtasksMetadata;
|
|
import com.todoroo.astrid.opencrx.OpencrxCoreUtils;
|
|
import com.todoroo.astrid.tags.TagService;
|
|
import com.todoroo.astrid.tags.TaskToTagMetadata;
|
|
import com.todoroo.astrid.utility.TitleParser;
|
|
|
|
|
|
/**
|
|
* Service layer for {@link Task}-centered activities.
|
|
*
|
|
* @author Tim Su <tim@todoroo.com>
|
|
*
|
|
*/
|
|
public class TaskService {
|
|
|
|
public static final String TRANS_QUICK_ADD_MARKUP = "markup"; //$NON-NLS-1$
|
|
|
|
public static final String TRANS_REPEAT_CHANGED = "repeat_changed"; //$NON-NLS-1$
|
|
|
|
public static final String TRANS_TAGS = "tags"; //$NON-NLS-1$
|
|
|
|
public static final String TRANS_ASSIGNED = "task-assigned"; //$NON-NLS-1$
|
|
|
|
public static final String TRANS_EDIT_SAVE = "task-edit-save"; //$NON-NLS-1$
|
|
|
|
public static final String TRANS_REPEAT_COMPLETE = "repeat-complete"; //$NON-NLS-1$
|
|
|
|
private static final int TOTAL_TASKS_FOR_ACTIVATION = 3;
|
|
private static final int COMPLETED_TASKS_FOR_ACTIVATION = 1;
|
|
private static final String PREF_USER_ACTVATED = "user-activated"; //$NON-NLS-1$
|
|
|
|
@Autowired
|
|
private TaskDao taskDao;
|
|
|
|
@Autowired
|
|
private TaskOutstandingDao taskOutstandingDao;
|
|
|
|
@Autowired
|
|
private MetadataDao metadataDao;
|
|
|
|
@Autowired
|
|
private UserActivityDao userActivityDao;
|
|
|
|
public TaskService() {
|
|
DependencyInjectionService.getInstance().inject(this);
|
|
}
|
|
|
|
// --- service layer
|
|
|
|
/**
|
|
* Query underlying database
|
|
* @param query
|
|
* @return
|
|
*/
|
|
public TodorooCursor<Task> query(Query query) {
|
|
return taskDao.query(query);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param properties
|
|
* @param id id
|
|
* @return item, or null if it doesn't exist
|
|
*/
|
|
public Task fetchById(long id, Property<?>... properties) {
|
|
return taskDao.fetch(id, properties);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param uuid
|
|
* @param properties
|
|
* @return item, or null if it doesn't exist
|
|
*/
|
|
public Task fetchByUUID(String uuid, Property<?>... properties) {
|
|
TodorooCursor<Task> task = query(Query.select(properties).where(Task.UUID.eq(uuid)));
|
|
try {
|
|
if (task.getCount() > 0) {
|
|
task.moveToFirst();
|
|
return new Task(task);
|
|
}
|
|
return null;
|
|
} finally {
|
|
task.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark the given task as completed and save it.
|
|
*
|
|
* @param item
|
|
*/
|
|
public void setComplete(Task item, boolean completed) {
|
|
if(completed) {
|
|
item.setValue(Task.COMPLETION_DATE, DateUtilities.now());
|
|
|
|
long reminderLast = item.getValue(Task.REMINDER_LAST);
|
|
String socialReminder = item.getValue(Task.SOCIAL_REMINDER);
|
|
if (reminderLast > 0) {
|
|
long diff = DateUtilities.now() - reminderLast;
|
|
if (diff > 0 && diff < DateUtilities.ONE_DAY) {
|
|
// within one day of last reminder
|
|
StatisticsService.reportEvent(StatisticsConstants.TASK_COMPLETED_ONE_DAY, "social", socialReminder); //$NON-NLS-1$
|
|
}
|
|
if (diff > 0 && diff < DateUtilities.ONE_WEEK) {
|
|
// within one week of last reminder
|
|
StatisticsService.reportEvent(StatisticsConstants.TASK_COMPLETED_ONE_WEEK, "social", socialReminder); //$NON-NLS-1$
|
|
}
|
|
}
|
|
StatisticsService.reportEvent(StatisticsConstants.TASK_COMPLETED_V2);
|
|
} else {
|
|
item.setValue(Task.COMPLETION_DATE, 0L);
|
|
}
|
|
|
|
taskDao.save(item);
|
|
}
|
|
|
|
/**
|
|
* Create or save the given action item
|
|
*
|
|
* @param item
|
|
* @param skipHooks
|
|
* Whether pre and post hooks should run. This should be set
|
|
* to true if tasks are created as part of synchronization
|
|
*/
|
|
public boolean save(Task item) {
|
|
return taskDao.save(item);
|
|
}
|
|
|
|
/**
|
|
* Clone the given task and all its metadata
|
|
*
|
|
* @param the old task
|
|
* @return the new task
|
|
*/
|
|
public Task clone(Task task) {
|
|
Task newTask = fetchById(task.getId(), Task.PROPERTIES);
|
|
if(newTask == null)
|
|
return new Task();
|
|
newTask.clearValue(Task.ID);
|
|
newTask.clearValue(Task.UUID);
|
|
TodorooCursor<Metadata> cursor = metadataDao.query(
|
|
Query.select(Metadata.PROPERTIES).where(MetadataCriteria.byTask(task.getId())));
|
|
try {
|
|
if(cursor.getCount() > 0) {
|
|
Metadata metadata = new Metadata();
|
|
newTask.putTransitory(SyncFlags.GTASKS_SUPPRESS_SYNC, true);
|
|
taskDao.save(newTask);
|
|
long newId = newTask.getId();
|
|
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
|
|
metadata.readFromCursor(cursor);
|
|
|
|
if(!metadata.containsNonNullValue(Metadata.KEY))
|
|
continue;
|
|
|
|
if(GtasksMetadata.METADATA_KEY.equals(metadata.getValue(Metadata.KEY)))
|
|
metadata.setValue(GtasksMetadata.ID, ""); //$NON-NLS-1$
|
|
if(OpencrxCoreUtils.OPENCRX_ACTIVITY_METADATA_KEY.equals(metadata.getValue(Metadata.KEY)))
|
|
metadata.setValue(OpencrxCoreUtils.ACTIVITY_ID, 0L);
|
|
|
|
metadata.setValue(Metadata.TASK, newId);
|
|
metadata.clearValue(Metadata.ID);
|
|
metadataDao.createNew(metadata);
|
|
}
|
|
}
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
return newTask;
|
|
}
|
|
|
|
public Task cloneReusableTask(Task task, String tagName, String tagUuid) {
|
|
Task newTask = fetchById(task.getId(), Task.PROPERTIES);
|
|
if (newTask == null)
|
|
return new Task();
|
|
newTask.clearValue(Task.ID);
|
|
newTask.clearValue(Task.UUID);
|
|
newTask.clearValue(Task.USER);
|
|
newTask.clearValue(Task.USER_ID);
|
|
newTask.clearValue(Task.IS_READONLY);
|
|
newTask.clearValue(Task.IS_PUBLIC);
|
|
|
|
taskDao.save(newTask);
|
|
|
|
if (!RemoteModel.isUuidEmpty(tagUuid)) {
|
|
TagService.getInstance().createLink(newTask, tagName, tagUuid);
|
|
}
|
|
return newTask;
|
|
}
|
|
|
|
/**
|
|
* Delete the given task. Instead of deleting from the database, we set
|
|
* the deleted flag.
|
|
*
|
|
* @param model
|
|
*/
|
|
public void delete(Task item) {
|
|
if(!item.isSaved())
|
|
return;
|
|
else if(item.containsValue(Task.TITLE) && item.getValue(Task.TITLE).length() == 0) {
|
|
taskDao.delete(item.getId());
|
|
taskOutstandingDao.deleteWhere(TaskOutstanding.ENTITY_ID_PROPERTY.eq(item.getId()));
|
|
item.setId(Task.NO_ID);
|
|
} else {
|
|
long id = item.getId();
|
|
item.clear();
|
|
item.setId(id);
|
|
GCalHelper.deleteTaskEvent(item);
|
|
item.setValue(Task.DELETION_DATE, DateUtilities.now());
|
|
taskDao.save(item);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Permanently delete the given task.
|
|
*
|
|
* @param model
|
|
*/
|
|
public void purge(long taskId) {
|
|
taskDao.delete(taskId);
|
|
}
|
|
|
|
/**
|
|
* Clean up tasks. Typically called on startup
|
|
*/
|
|
public void cleanup() {
|
|
TodorooCursor<Task> cursor = taskDao.query(
|
|
Query.select(Task.ID).where(TaskCriteria.hasNoTitle()));
|
|
try {
|
|
if(cursor.getCount() == 0)
|
|
return;
|
|
|
|
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
|
|
long id = cursor.getLong(0);
|
|
taskDao.delete(id);
|
|
}
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch tasks for the given filter
|
|
* @param properties
|
|
* @param constraint text constraint, or null
|
|
* @param filter
|
|
* @return
|
|
*/
|
|
@SuppressWarnings("nls")
|
|
public TodorooCursor<Task> fetchFiltered(String queryTemplate, CharSequence constraint,
|
|
Property<?>... properties) {
|
|
Criterion whereConstraint = null;
|
|
if(constraint != null)
|
|
whereConstraint = Functions.upper(Task.TITLE).like("%" +
|
|
constraint.toString().toUpperCase() + "%");
|
|
|
|
if(queryTemplate == null) {
|
|
if(whereConstraint == null)
|
|
return taskDao.query(Query.selectDistinct(properties));
|
|
else
|
|
return taskDao.query(Query.selectDistinct(properties).where(whereConstraint));
|
|
}
|
|
|
|
String sql;
|
|
if(whereConstraint != null) {
|
|
if(!queryTemplate.toUpperCase().contains("WHERE"))
|
|
sql = queryTemplate + " WHERE " + whereConstraint;
|
|
else
|
|
sql = queryTemplate.replace("WHERE ", "WHERE " + whereConstraint + " AND ");
|
|
} else
|
|
sql = queryTemplate;
|
|
|
|
sql = PermaSql.replacePlaceholders(sql);
|
|
|
|
return taskDao.query(Query.select(properties).withQueryTemplate(sql));
|
|
}
|
|
|
|
public boolean getUserActivationStatus() {
|
|
if (Preferences.getBoolean(PREF_USER_ACTVATED, false))
|
|
return true;
|
|
|
|
TodorooCursor<Task> all = query(Query.select(Task.ID));
|
|
try {
|
|
if (all.getCount() < TOTAL_TASKS_FOR_ACTIVATION)
|
|
return false;
|
|
|
|
TodorooCursor<Task> completed = query(Query.select(Task.ID).where(TaskCriteria.completed()));
|
|
try {
|
|
if (completed.getCount() < COMPLETED_TASKS_FOR_ACTIVATION)
|
|
return false;
|
|
} finally {
|
|
completed.close();
|
|
}
|
|
} finally {
|
|
all.close();
|
|
}
|
|
|
|
Preferences.setBoolean(PREF_USER_ACTVATED, true);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param query
|
|
* @return how many tasks are matched by this query
|
|
*/
|
|
public int count(Query query) {
|
|
return taskDao.count(query);
|
|
}
|
|
|
|
/**
|
|
* Clear details cache. Useful if user performs some operation that
|
|
* affects details
|
|
* @param criterion
|
|
*
|
|
* @return # of affected rows
|
|
*/
|
|
public int clearDetails(Criterion criterion) {
|
|
Task template = new Task();
|
|
template.setValue(Task.DETAILS_DATE, 0L);
|
|
return taskDao.update(criterion, template);
|
|
}
|
|
|
|
/**
|
|
* Update database based on selection and values
|
|
* @param selection
|
|
* @param selectionArgs
|
|
* @param setValues
|
|
* @return
|
|
*/
|
|
public int updateBySelection(String selection, String[] selectionArgs,
|
|
Task taskValues) {
|
|
TodorooCursor<Task> cursor = taskDao.rawQuery(selection, selectionArgs, Task.ID);
|
|
try {
|
|
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
|
|
taskValues.setValue(Task.ID, cursor.get(Task.ID));
|
|
taskDao.save(taskValues);
|
|
}
|
|
return cursor.getCount();
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update all matching a clause to have the values set on template object.
|
|
* <p>
|
|
* Example (updates "joe" => "bob" in metadata value1):
|
|
* {code}
|
|
* Metadata item = new Metadata();
|
|
* item.setValue(Metadata.VALUE1, "bob");
|
|
* update(item, Metadata.VALUE1.eq("joe"));
|
|
* {code}
|
|
* @param where sql criteria
|
|
* @param template set fields on this object in order to set them in the db.
|
|
* @return # of updated items
|
|
*/
|
|
public int update(Criterion where, Task template) {
|
|
return taskDao.update(where, template);
|
|
}
|
|
|
|
/**
|
|
* Count tasks overall
|
|
* @param filter
|
|
* @return
|
|
*/
|
|
public int countTasks() {
|
|
TodorooCursor<Task> cursor = query(Query.select(Task.ID));
|
|
try {
|
|
return cursor.getCount();
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
/** count tasks in a given filter */
|
|
public int countTasks(Filter filter) {
|
|
String queryTemplate = PermaSql.replacePlaceholders(filter.getSqlQuery());
|
|
TodorooCursor<Task> cursor = query(Query.select(Task.ID).withQueryTemplate(
|
|
queryTemplate));
|
|
try {
|
|
return cursor.getCount();
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete all tasks matching a given criterion
|
|
* @param all
|
|
*/
|
|
public int deleteWhere(Criterion criteria) {
|
|
return taskDao.deleteWhere(criteria);
|
|
}
|
|
|
|
/**
|
|
* Save task, parsing quick-add mark-up:
|
|
* <ul>
|
|
* <li>#tag - add the tag "tag"
|
|
* <li>@context - add the tag "@context"
|
|
* <li>!4 - set priority to !!!!
|
|
*/
|
|
private void quickAdd(Task task, List<String> tags) {
|
|
save(task);
|
|
for(String tag : tags) {
|
|
TagService.getInstance().createLink(task, tag);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse quick add markup for the given task
|
|
* @param task
|
|
* @param tags an empty array to apply tags to
|
|
* @return
|
|
*/
|
|
public static boolean parseQuickAddMarkup(Task task, ArrayList<String> tags) {
|
|
return TitleParser.parse(task, tags);
|
|
}
|
|
|
|
/**
|
|
* Create an uncompleted copy of this task and edit it
|
|
* @param itemId
|
|
* @return cloned item id
|
|
*/
|
|
public long duplicateTask(long itemId) {
|
|
Task original = new Task();
|
|
original.setId(itemId);
|
|
Task clone = clone(original);
|
|
String userId = clone.getValue(Task.USER_ID);
|
|
if (!Task.USER_ID_SELF.equals(userId) && !ActFmPreferenceService.userId().equals(userId))
|
|
clone.putTransitory(TRANS_ASSIGNED, true);
|
|
clone.setValue(Task.CREATION_DATE, DateUtilities.now());
|
|
clone.setValue(Task.COMPLETION_DATE, 0L);
|
|
clone.setValue(Task.DELETION_DATE, 0L);
|
|
clone.setValue(Task.CALENDAR_URI, ""); //$NON-NLS-1$
|
|
GCalHelper.createTaskEventIfEnabled(clone);
|
|
|
|
save(clone);
|
|
return clone.getId();
|
|
}
|
|
|
|
/**
|
|
* Create task from the given content values, saving it. This version
|
|
* doesn't need to start with a base task model.
|
|
*
|
|
* @param values
|
|
* @param title
|
|
* @param taskService
|
|
* @param metadataService
|
|
* @return
|
|
*/
|
|
public static Task createWithValues(ContentValues values, String title) {
|
|
Task task = new Task();
|
|
return createWithValues(task, values, title);
|
|
}
|
|
|
|
/**
|
|
* Create task from the given content values, saving it.
|
|
*
|
|
* @param task base task to start with
|
|
* @param values
|
|
* @param title
|
|
* @param taskService
|
|
* @param metadataService
|
|
* @return
|
|
*/
|
|
public static Task createWithValues(Task task, ContentValues values, String title) {
|
|
if (title != null)
|
|
task.setValue(Task.TITLE, title);
|
|
|
|
ArrayList<String> tags = new ArrayList<String>();
|
|
boolean quickAddMarkup = false;
|
|
try {
|
|
quickAddMarkup = parseQuickAddMarkup(task, tags);
|
|
} catch (Throwable e) {
|
|
PluginServices.getExceptionService().reportError("parse-quick-add", e); //$NON-NLS-1$
|
|
}
|
|
|
|
ContentValues forMetadata = null;
|
|
if (values != null && values.size() > 0) {
|
|
ContentValues forTask = new ContentValues();
|
|
forMetadata = new ContentValues();
|
|
outer: for (Entry<String, Object> item : values.valueSet()) {
|
|
String key = item.getKey();
|
|
Object value = item.getValue();
|
|
if (value instanceof String)
|
|
value = PermaSql.replacePlaceholders((String) value);
|
|
|
|
for (Property<?> property : Metadata.PROPERTIES)
|
|
if (property.name.equals(key)) {
|
|
AndroidUtilities.putInto(forMetadata, key, value, true);
|
|
continue outer;
|
|
}
|
|
|
|
AndroidUtilities.putInto(forTask, key, value, true);
|
|
}
|
|
task.mergeWithoutReplacement(forTask);
|
|
}
|
|
|
|
if (!Task.USER_ID_SELF.equals(task.getValue(Task.USER_ID)))
|
|
task.putTransitory(TRANS_ASSIGNED, true);
|
|
|
|
PluginServices.getTaskService().quickAdd(task, tags);
|
|
if (quickAddMarkup)
|
|
task.putTransitory(TRANS_QUICK_ADD_MARKUP, true);
|
|
|
|
if (forMetadata != null && forMetadata.size() > 0) {
|
|
Metadata metadata = new Metadata();
|
|
metadata.setValue(Metadata.TASK, task.getId());
|
|
metadata.mergeWith(forMetadata);
|
|
if (TaskToTagMetadata.KEY.equals(metadata.getValue(Metadata.KEY))) {
|
|
if (metadata.containsNonNullValue(TaskToTagMetadata.TAG_UUID) && !RemoteModel.NO_UUID.equals(metadata.getValue(TaskToTagMetadata.TAG_UUID))) {
|
|
// This is more efficient
|
|
TagService.getInstance().createLink(task, metadata.getValue(TaskToTagMetadata.TAG_NAME), metadata.getValue(TaskToTagMetadata.TAG_UUID));
|
|
} else {
|
|
// This is necessary for backwards compatibility
|
|
TagService.getInstance().createLink(task, metadata.getValue(TaskToTagMetadata.TAG_NAME));
|
|
}
|
|
} else {
|
|
PluginServices.getMetadataService().save(metadata);
|
|
}
|
|
}
|
|
|
|
return task;
|
|
}
|
|
|
|
public TodorooCursor<UserActivity> getActivityAndHistoryForTask(Task task) {
|
|
Query taskQuery = queryForTask(task, UpdateAdapter.USER_TABLE_ALIAS, UpdateAdapter.USER_ACTIVITY_PROPERTIES, UpdateAdapter.USER_PROPERTIES);
|
|
|
|
Query historyQuery = Query.select(AndroidUtilities.addToArray(Property.class, UpdateAdapter.HISTORY_PROPERTIES, UpdateAdapter.USER_PROPERTIES)).from(History.TABLE)
|
|
.where(Criterion.and(History.TABLE_ID.eq(NameMaps.TABLE_ID_TASKS), History.TARGET_ID.eq(task.getUuid())))
|
|
.from(History.TABLE)
|
|
.join(Join.left(User.TABLE.as(UpdateAdapter.USER_TABLE_ALIAS), History.USER_UUID.eq(Field.field(UpdateAdapter.USER_TABLE_ALIAS + "." + User.UUID.name)))); //$NON-NLS-1$;
|
|
|
|
Query resultQuery = taskQuery.union(historyQuery).orderBy(Order.desc("1")); //$NON-NLS-1$
|
|
|
|
return userActivityDao.query(resultQuery);
|
|
}
|
|
|
|
private static Query queryForTask(Task task, String userTableAlias, Property<?>[] activityProperties, Property<?>[] userProperties) {
|
|
Query result = Query.select(AndroidUtilities.addToArray(Property.class, activityProperties, userProperties))
|
|
.where(Criterion.and(UserActivity.ACTION.eq(UserActivity.ACTION_TASK_COMMENT), UserActivity.TARGET_ID.eq(task.getUuid()), UserActivity.DELETED_AT.eq(0)));
|
|
if (!TextUtils.isEmpty(userTableAlias))
|
|
result = result.join(Join.left(User.TABLE.as(userTableAlias), UserActivity.USER_UUID.eq(Field.field(userTableAlias + "." + User.UUID.name)))); //$NON-NLS-1$
|
|
return result;
|
|
}
|
|
|
|
}
|