mirror of https://github.com/tasks/tasks
Moved a bunch of API files into the API project. Yay for code reuse.
Please import the github project todoroo/astridApi. :)pull/14/head
parent
be125eca89
commit
c6da0907ff
@ -1,93 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Represents an add-onn for Astrid. Users can enable or disable add-ons,
|
||||
* which affect all other extension points that share the same identifier.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class Addon implements Parcelable {
|
||||
|
||||
/**
|
||||
* Add-on Identifier
|
||||
*/
|
||||
public String addon = null;
|
||||
|
||||
/**
|
||||
* Plug-in Title
|
||||
*/
|
||||
public String title = null;
|
||||
|
||||
/**
|
||||
* Plug-in Author
|
||||
*/
|
||||
public String author = null;
|
||||
|
||||
/**
|
||||
* Plug-in Description
|
||||
*/
|
||||
public String description = null;
|
||||
|
||||
/**
|
||||
* Convenience constructor to generate a plug-in object
|
||||
*
|
||||
* @param addon
|
||||
* @param title
|
||||
* @param author
|
||||
* @param description
|
||||
*/
|
||||
public Addon(String addon, String title, String author, String description) {
|
||||
this.addon = addon;
|
||||
this.title = title;
|
||||
this.author = author;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
// --- parcelable helpers
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(addon);
|
||||
dest.writeString(title);
|
||||
dest.writeString(author);
|
||||
dest.writeString(description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parcelable creator
|
||||
*/
|
||||
public static final Parcelable.Creator<Addon> CREATOR = new Parcelable.Creator<Addon>() {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Addon createFromParcel(Parcel source) {
|
||||
return new Addon(source.readString(), source.readString(),
|
||||
source.readString(), source.readString());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Addon[] newArray(int size) {
|
||||
return new Addon[size];
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@ -1,242 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
/**
|
||||
* Constants for interfacing with Astrid.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class AstridApiConstants {
|
||||
|
||||
// --- General Constants
|
||||
|
||||
/**
|
||||
* Astrid broadcast base package name
|
||||
*/
|
||||
public static final String PACKAGE = "com.todoroo.astrid";
|
||||
|
||||
/**
|
||||
* Permission for reading tasks and receiving to GET_FILTERS intent
|
||||
*/
|
||||
public static final String PERMISSION_READ = PACKAGE + ".READ";
|
||||
|
||||
/**
|
||||
* Permission for writing and creating tasks
|
||||
*/
|
||||
public static final String PERMISSION_WRITE = PACKAGE + ".WRITE";
|
||||
|
||||
// --- Content Provider
|
||||
|
||||
/**
|
||||
* URI to append to base content URI for making group-by queries
|
||||
*/
|
||||
public static final String GROUP_BY_URI = "/groupby/";
|
||||
|
||||
// --- Broadcast Extras
|
||||
|
||||
/**
|
||||
* Extras name for task id
|
||||
*/
|
||||
public static final String EXTRAS_TASK_ID = "task";
|
||||
|
||||
/**
|
||||
* Extras name for a response item broadcast to astrid
|
||||
*/
|
||||
public static final String EXTRAS_RESPONSE = "response";
|
||||
|
||||
/**
|
||||
* Extras name for plug-in identifier
|
||||
*/
|
||||
public static final String EXTRAS_ADDON = "addon";
|
||||
|
||||
/**
|
||||
* Extras name for whether task detail request is extended
|
||||
*/
|
||||
public static final String EXTRAS_EXTENDED = "extended";
|
||||
|
||||
/**
|
||||
* Extras name for old task due date
|
||||
*/
|
||||
public static final String EXTRAS_OLD_DUE_DATE= "oldDueDate";
|
||||
|
||||
/**
|
||||
* Extras name for new task due date
|
||||
*/
|
||||
public static final String EXTRAS_NEW_DUE_DATE = "newDueDate";
|
||||
|
||||
/**
|
||||
* Extras name for sync provider name
|
||||
*/
|
||||
public static final String EXTRAS_NAME = "name";
|
||||
|
||||
// --- Add-ons API
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent requesting add-ons
|
||||
*/
|
||||
public static final String BROADCAST_REQUEST_ADDONS = PACKAGE + ".REQUEST_ADDONS";
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent sending add-ons back to Astrid
|
||||
* @extra EXTRAS_RESPONSE an {@link Addon} object
|
||||
*/
|
||||
public static final String BROADCAST_SEND_ADDONS = PACKAGE + ".SEND_ADDONS";
|
||||
|
||||
// --- Filters API
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent requesting filters
|
||||
*/
|
||||
public static final String BROADCAST_REQUEST_FILTERS = PACKAGE + ".REQUEST_FILTERS";
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent sending filters back to Astrid
|
||||
* @extra EXTRAS_ADDON your add-on identifier
|
||||
* @extra EXTRAS_RESPONSE an array of {@link FilterListItem}s
|
||||
*/
|
||||
public static final String BROADCAST_SEND_FILTERS = PACKAGE + ".SEND_FILTERS";
|
||||
|
||||
// --- Edit Controls API
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent requesting task edit controls
|
||||
* @extra EXTRAS_TASK_ID id of the task user is editing
|
||||
*/
|
||||
public static final String BROADCAST_REQUEST_EDIT_CONTROLS = PACKAGE + ".REQUEST_EDIT_CONTROLS";
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent sending task edit controls back to Astrid
|
||||
* @extra EXTRAS_ADDON your add-on identifier
|
||||
* @extra EXTRAS_RESPONSE a {@link RemoteViews} with your edit controls
|
||||
*/
|
||||
public static final String BROADCAST_SEND_EDIT_CONTROLS = PACKAGE + ".SEND_EDIT_CONTROLS";
|
||||
|
||||
// --- Task Details API
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent requesting details for a task.
|
||||
* Extended details are displayed when a user presses on a task.
|
||||
*
|
||||
* @extra EXTRAS_TASK_ID id of the task
|
||||
* @extra EXTRAS_EXTENDED whether request is for standard or extended details
|
||||
*/
|
||||
public static final String BROADCAST_REQUEST_DETAILS = PACKAGE + ".REQUEST_DETAILS";
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent sending details back to Astrid
|
||||
* @extra EXTRAS_ADDON your add-on identifier
|
||||
* @extra EXTRAS_TASK_ID id of the task
|
||||
* @extra EXTRAS_EXTENDED whether request is for standard or extended details
|
||||
* @extra EXTRAS_RESPONSE a String
|
||||
*/
|
||||
public static final String BROADCAST_SEND_DETAILS = PACKAGE + ".SEND_DETAILS";
|
||||
|
||||
// --- Sync Action API
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent requesting a listing of active
|
||||
* sync actions users can activate from the menu
|
||||
*/
|
||||
public static final String BROADCAST_REQUEST_SYNC_ACTIONS = PACKAGE + ".REQUEST_SYNC_ACTIONS";
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent sending sync provider information back to Astrid
|
||||
* @extra EXTRAS_ADDON your add-on identifier
|
||||
* @extra EXTRAS_RESPONSE a {@link SyncAction} to invoke synchronization
|
||||
*/
|
||||
public static final String BROADCAST_SEND_SYNC_ACTIONS = PACKAGE + ".SEND_SYNC_ACTIONS";
|
||||
|
||||
// --- Task Actions API
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent requesting actions for a task
|
||||
* @extra EXTRAS_TASK_ID id of the task
|
||||
*/
|
||||
public static final String BROADCAST_REQUEST_ACTIONS = PACKAGE + ".REQUEST_ACTIONS";
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent sending actions back to Astrid
|
||||
* @extra EXTRAS_ADDON your add-on identifier
|
||||
* @extra EXTRAS_TASK_ID id of the task
|
||||
* @extra EXTRAS_RESPONSE a String
|
||||
*/
|
||||
public static final String BROADCAST_SEND_ACTIONS = PACKAGE + ".SEND_ACTIONS";
|
||||
|
||||
// --- Task Decorations API
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent requesting task list decorations for a task
|
||||
* @extra EXTRAS_TASK_ID id of the task
|
||||
*/
|
||||
public static final String BROADCAST_REQUEST_DECORATIONS = PACKAGE + ".REQUEST_DECORATIONS";
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent sending decorations back to Astrid
|
||||
* @extra EXTRAS_ADDON your add-on identifier
|
||||
* @extra EXTRAS_TASK_ID id of the task
|
||||
* @extra EXTRAS_RESPONSE a {@link TaskDecoration}
|
||||
*/
|
||||
public static final String BROADCAST_SEND_DECORATIONS = PACKAGE + ".SEND_DECORATIONS";
|
||||
|
||||
// --- Actions API
|
||||
|
||||
/**
|
||||
* Action name for intents to be displayed on task context menu
|
||||
* @extra EXTRAS_TASK_ID id of the task
|
||||
*/
|
||||
public static final String ACTION_TASK_CONTEXT_MENU = PACKAGE + ".CONTEXT_MENU";
|
||||
|
||||
/**
|
||||
* Action name for intents to be displayed on Astrid's task list menu
|
||||
* @extra EXTRAS_ADDON your add-on identifier
|
||||
* @extra EXTRAS_RESPONSE an array of {@link Intent}s
|
||||
*/
|
||||
public static final String ACTION_TASK_LIST_MENU = PACKAGE + ".TASK_LIST_MENU";
|
||||
|
||||
/**
|
||||
* Action name for intents to be displayed in Astrid's settings. By default,
|
||||
* your application will be put into the category named by your application,
|
||||
* but you can add a string meta-data with name "category" to override this.
|
||||
*/
|
||||
public static final String ACTION_SETTINGS = PACKAGE + ".SETTINGS";
|
||||
|
||||
// --- Events API
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent notifying add-ons that Astrid started up
|
||||
*/
|
||||
public static final String BROADCAST_EVENT_STARTUP = PACKAGE + ".STARTUP";
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent notifying Astrid task list to refresh
|
||||
*/
|
||||
public static final String BROADCAST_EVENT_REFRESH = PACKAGE + ".REFRESH";
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent notifying Astrid to clear detail cache
|
||||
* because an event occurred that potentially affects all tasks (e.g.
|
||||
* logging out of a sync provider). Use this call carefully, as loading
|
||||
* details can degrade the performance of Astrid.
|
||||
*/
|
||||
public static final String BROADCAST_EVENT_FLUSH_DETAILS = PACKAGE + ".FLUSH_DETAILS";
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent notifying that task was completed
|
||||
* @extra EXTRAS_TASK_ID id of the task
|
||||
*/
|
||||
public static final String BROADCAST_EVENT_TASK_COMPLETED = PACKAGE + ".TASK_COMPLETED";
|
||||
|
||||
/**
|
||||
* Action name for broadcast intent notifying that task was created from repeating template
|
||||
* @extra EXTRAS_TASK_ID id of the task
|
||||
* @extra EXTRAS_OLD_DUE_DATE task old due date (could be 0)
|
||||
* @extra EXTRAS_NEW_DUE_DATE task new due date (will not be 0)
|
||||
*/
|
||||
public static final String BROADCAST_EVENT_TASK_REPEATED = PACKAGE + ".TASK_REPEATED";
|
||||
|
||||
}
|
||||
@ -1,159 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
|
||||
/**
|
||||
* CustomFilterCriteria allow users to build a custom filter by chaining
|
||||
* together criteria
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public final class CustomFilterCriterion implements Parcelable {
|
||||
|
||||
/**
|
||||
* Criteria Identifier. This identifier allows saved filters to be reloaded.
|
||||
* <p>
|
||||
* e.g "duedate"
|
||||
*/
|
||||
@CheckForNull
|
||||
public String identifier;
|
||||
|
||||
/**
|
||||
* Criteria Title. If the title contains ?, this is replaced by the entry
|
||||
* label string selected.
|
||||
* <p>
|
||||
* e.g "Due: ?"
|
||||
*/
|
||||
@CheckForNull
|
||||
public String text;
|
||||
|
||||
/**
|
||||
* Criterion SQL. This query should return task id's. If this contains
|
||||
* ?, it will be replaced by the entry value
|
||||
* <p>
|
||||
* Examples:
|
||||
* <ul>
|
||||
* <li><code>SELECT _id FROM tasks WHERE dueDate <= ?</code>
|
||||
* <li><code>SELECT task FROM metadata WHERE value = '?'</code>
|
||||
* </ul>
|
||||
*/
|
||||
@CheckForNull
|
||||
public String sql;
|
||||
|
||||
/**
|
||||
* Values to apply to a task when quick-adding a task from a filter
|
||||
* created from this criterion. ? will be replaced with the entry value.
|
||||
* For example, when a user views tasks tagged 'ABC', the
|
||||
* tasks they create should also be tagged 'ABC'. If set to null, no
|
||||
* additional values will be stored for a task.
|
||||
*/
|
||||
@CheckForNull
|
||||
public ContentValues valuesForNewTasks = null;
|
||||
|
||||
/**
|
||||
* Array of entries for user to select from
|
||||
*/
|
||||
@CheckForNull
|
||||
public String[] entryTitles;
|
||||
|
||||
/**
|
||||
* Array of entry values corresponding to entries
|
||||
*/
|
||||
@CheckForNull
|
||||
public String[] entryValues;
|
||||
|
||||
/**
|
||||
* Icon for this criteria. Can be null for no bitmap
|
||||
*/
|
||||
@CheckForNull
|
||||
public Bitmap icon;
|
||||
|
||||
/**
|
||||
* Criteria name. This is displayed when users are selecting a criteria
|
||||
*/
|
||||
@CheckForNull
|
||||
public String name;
|
||||
|
||||
/**
|
||||
* Create a new CustomFilterCriteria object
|
||||
*
|
||||
* @param title
|
||||
* @param sql
|
||||
* @param valuesForNewTasks
|
||||
* @param entryTitles
|
||||
* @param entryValues
|
||||
* @param icon
|
||||
* @param name
|
||||
*/
|
||||
public CustomFilterCriterion(String identifier, String title, String sql,
|
||||
ContentValues valuesForNewTasks, String[] entryTitles,
|
||||
String[] entryValues, Bitmap icon, String name) {
|
||||
this.identifier = identifier;
|
||||
this.text = title;
|
||||
this.sql = sql;
|
||||
this.valuesForNewTasks = valuesForNewTasks;
|
||||
this.entryTitles = entryTitles;
|
||||
this.entryValues = entryValues;
|
||||
this.icon = icon;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
// --- parcelable
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(identifier);
|
||||
dest.writeString(text);
|
||||
dest.writeString(sql);
|
||||
dest.writeParcelable(valuesForNewTasks, 0);
|
||||
dest.writeStringArray(entryTitles);
|
||||
dest.writeStringArray(entryValues);
|
||||
dest.writeParcelable(icon, 0);
|
||||
dest.writeString(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parcelable Creator Object
|
||||
*/
|
||||
public static final Parcelable.Creator<CustomFilterCriterion> CREATOR = new Parcelable.Creator<CustomFilterCriterion>() {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public CustomFilterCriterion createFromParcel(Parcel source) {
|
||||
CustomFilterCriterion item = new CustomFilterCriterion(
|
||||
source.readString(), source.readString(), source.readString(),
|
||||
(ContentValues)source.readParcelable(ContentValues.class.getClassLoader()),
|
||||
source.createStringArray(), source.createStringArray(),
|
||||
(Bitmap)source.readParcelable(Bitmap.class.getClassLoader()),
|
||||
source.readString());
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public CustomFilterCriterion[] newArray(int size) {
|
||||
return new CustomFilterCriterion[size];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@ -1,164 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.todoroo.andlib.sql.QueryTemplate;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
|
||||
/**
|
||||
* A <code>FilterListFilter</code> allows users to display tasks that have
|
||||
* something in common.
|
||||
* <p>
|
||||
* A plug-in can expose new <code>FilterListFilter</code>s to the system by
|
||||
* responding to the <code>com.todoroo.astrid.GET_FILTERS</code> broadcast
|
||||
* intent.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public final class Filter extends FilterListItem {
|
||||
|
||||
// --- constants
|
||||
|
||||
/** Constant for valuesForNewTasks to indicate the value should be replaced
|
||||
* with the current time as long */
|
||||
public static final long VALUE_NOW = Long.MIN_VALUE + 1;
|
||||
|
||||
// --- instance variables
|
||||
|
||||
/**
|
||||
* Expanded title of this filter. This is displayed at the top
|
||||
* of the screen when user is viewing this filter.
|
||||
* <p>
|
||||
* e.g "Tasks With Notes"
|
||||
*/
|
||||
@CheckForNull
|
||||
public String title;
|
||||
|
||||
/**
|
||||
* {@link PermaSql} query for this filter. The query will be appended to the select
|
||||
* statement after "<code>SELECT fields FROM table %s</code>". It is
|
||||
* recommended that you use a {@link QueryTemplate} to construct your
|
||||
* query.
|
||||
* <p>
|
||||
* Examples:
|
||||
* <ul>
|
||||
* <li><code>"WHERE completionDate = 0"</code>
|
||||
* <li><code>"INNER JOIN " +
|
||||
* Constants.TABLE_METADATA + " ON metadata.task = tasks.id WHERE
|
||||
* metadata.namespace = " + NAMESPACE + " AND metadata.key = 'a' AND
|
||||
* metadata.value = 'b' GROUP BY tasks.id ORDER BY tasks.title"</code>
|
||||
* </ul>
|
||||
*/
|
||||
@CheckForNull
|
||||
public String sqlQuery;
|
||||
|
||||
/**
|
||||
* Values to apply to a task when quick-adding a task from this filter.
|
||||
* For example, when a user views tasks tagged 'ABC', the
|
||||
* tasks they create should also be tagged 'ABC'. If set to null, no
|
||||
* additional values will be stored for a task. Can use {@link PermaSql}
|
||||
*/
|
||||
@CheckForNull
|
||||
public ContentValues valuesForNewTasks = null;
|
||||
|
||||
/**
|
||||
* Utility constructor for creating a Filter object
|
||||
* @param listingTitle
|
||||
* Title of this item as displayed on the lists page, e.g. Inbox
|
||||
* @param title
|
||||
* Expanded title of this filter when user is viewing this
|
||||
* filter, e.g. Inbox (20 tasks)
|
||||
* @param sqlQuery
|
||||
* SQL query for this list (see {@link sqlQuery} for examples).
|
||||
* @param valuesForNewTasks
|
||||
* see {@link sqlForNewTasks}
|
||||
*/
|
||||
public Filter(String listingTitle, String title,
|
||||
QueryTemplate sqlQuery, ContentValues valuesForNewTasks) {
|
||||
this(listingTitle, title, sqlQuery == null ? null : sqlQuery.toString(),
|
||||
valuesForNewTasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility constructor for creating a Filter object
|
||||
* @param listingTitle
|
||||
* Title of this item as displayed on the lists page, e.g. Inbox
|
||||
* @param title
|
||||
* Expanded title of this filter when user is viewing this
|
||||
* filter, e.g. Inbox (20 tasks)
|
||||
* @param sqlQuery
|
||||
* SQL query for this list (see {@link sqlQuery} for examples).
|
||||
* @param valuesForNewTasks
|
||||
* see {@link sqlForNewTasks}
|
||||
*/
|
||||
public Filter(String listingTitle, String title,
|
||||
String sqlQuery, ContentValues valuesForNewTasks) {
|
||||
this.listingTitle = listingTitle;
|
||||
this.title = title;
|
||||
this.sqlQuery = sqlQuery;
|
||||
this.valuesForNewTasks = valuesForNewTasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility constructor
|
||||
*
|
||||
* @param plugin
|
||||
* {@link Addon} identifier that encompasses object
|
||||
*/
|
||||
protected Filter() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// --- parcelable
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeString(title);
|
||||
dest.writeString(sqlQuery);
|
||||
dest.writeParcelable(valuesForNewTasks, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parcelable Creator Object
|
||||
*/
|
||||
public static final Parcelable.Creator<Filter> CREATOR = new Parcelable.Creator<Filter>() {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Filter createFromParcel(Parcel source) {
|
||||
Filter item = new Filter();
|
||||
item.readFromParcel(source);
|
||||
item.title = source.readString();
|
||||
item.sqlQuery = source.readString();
|
||||
item.valuesForNewTasks = source.readParcelable(ContentValues.class.getClassLoader());
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Filter[] newArray(int size) {
|
||||
return new Filter[size];
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
@ -1,98 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
|
||||
/**
|
||||
* A <code>FilterCategory</code> groups common {@link Filter}s and allows
|
||||
* a user to show/hide all of its children.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class FilterCategory extends FilterListItem {
|
||||
|
||||
/**
|
||||
* {@link Filter}s contained by this category
|
||||
*/
|
||||
@CheckForNull
|
||||
public Filter[] children;
|
||||
|
||||
/**
|
||||
* Constructor for creating a new FilterCategory
|
||||
* @param listingTitle
|
||||
* Title of this item as displayed on the lists page, e.g. Inbox
|
||||
* @param children
|
||||
* filters belonging to this category
|
||||
*/
|
||||
public FilterCategory(String listingTitle, Filter[] children) {
|
||||
this.listingTitle = listingTitle;
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for creating a new FilterCategory
|
||||
*
|
||||
* @param plugin
|
||||
* {@link Addon} identifier that encompasses object
|
||||
*/
|
||||
protected FilterCategory() {
|
||||
//
|
||||
}
|
||||
|
||||
// --- parcelable
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeParcelableArray(children, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parcelable creator
|
||||
*/
|
||||
public static final Parcelable.Creator<FilterCategory> CREATOR = new Parcelable.Creator<FilterCategory>() {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public FilterCategory createFromParcel(Parcel source) {
|
||||
FilterCategory item = new FilterCategory();
|
||||
item.readFromParcel(source);
|
||||
|
||||
Parcelable[] parcelableChildren = source.readParcelableArray(
|
||||
FilterCategory.class.getClassLoader());
|
||||
item.children = new Filter[parcelableChildren.length];
|
||||
for(int i = 0; i < item.children.length; i++) {
|
||||
if(parcelableChildren[i] instanceof FilterListItem)
|
||||
item.children[i] = (Filter) parcelableChildren[i];
|
||||
else
|
||||
item.children[i] = null;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public FilterCategory[] newArray(int size) {
|
||||
return new FilterCategory[size];
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Section Header for Filter List
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class FilterListHeader extends FilterListItem {
|
||||
|
||||
/**
|
||||
* Constructor for creating a new FilterListHeader
|
||||
* @param listingTitle
|
||||
* @param listingIconResource
|
||||
* @param priority
|
||||
*/
|
||||
public FilterListHeader(String listingTitle) {
|
||||
this.listingTitle = listingTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for creating a new FilterListHeader
|
||||
*
|
||||
* @param plugin
|
||||
* {@link Addon} identifier that encompasses object
|
||||
*/
|
||||
protected FilterListHeader() {
|
||||
//
|
||||
}
|
||||
|
||||
// --- parcelable
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<FilterListHeader> CREATOR = new Parcelable.Creator<FilterListHeader>() {
|
||||
|
||||
public FilterListHeader createFromParcel(Parcel source) {
|
||||
FilterListHeader item = new FilterListHeader();
|
||||
item.readFromParcel(source);
|
||||
return item;
|
||||
}
|
||||
|
||||
public FilterListHeader[] newArray(int size) {
|
||||
return new FilterListHeader[size];
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
|
||||
/**
|
||||
* Represents an item displayed by Astrid's FilterListActivity
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
abstract public class FilterListItem implements Parcelable {
|
||||
|
||||
/**
|
||||
* Title of this item displayed on the Filters page
|
||||
*/
|
||||
@CheckForNull
|
||||
public String listingTitle = null;
|
||||
|
||||
/**
|
||||
* Bitmap for icon used on listing page. <code>null</code> => no icon
|
||||
*/
|
||||
@CheckForNull
|
||||
public Bitmap listingIcon = null;
|
||||
|
||||
/**
|
||||
* Text Color. <code>0</code> => default color
|
||||
*/
|
||||
public int color = 0;
|
||||
|
||||
/**
|
||||
* Context Menu labels. The context menu will be displayed when users
|
||||
* long-press on this filter list item.
|
||||
*/
|
||||
@CheckForNull
|
||||
public String contextMenuLabels[] = new String[0];
|
||||
|
||||
/**
|
||||
* Context menu intents. This intent will be started when the corresponding
|
||||
* content menu label is invoked. This array must be the same size as
|
||||
* the contextMenuLabels array.
|
||||
*/
|
||||
@CheckForNull
|
||||
public Intent contextMenuIntents[] = new Intent[0];
|
||||
|
||||
// --- parcelable helpers
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(listingTitle);
|
||||
dest.writeParcelable(listingIcon, 0);
|
||||
dest.writeInt(color);
|
||||
|
||||
// write array lengths before arrays
|
||||
dest.writeStringArray(contextMenuLabels);
|
||||
dest.writeTypedArray(contextMenuIntents, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to read FilterListItem properties from a parcel.
|
||||
*
|
||||
* @param source
|
||||
*/
|
||||
public void readFromParcel(Parcel source) {
|
||||
listingTitle = source.readString();
|
||||
listingIcon = source.readParcelable(Bitmap.class.getClassLoader());
|
||||
color = source.readInt();
|
||||
|
||||
contextMenuLabels = source.createStringArray();
|
||||
contextMenuIntents = source.createTypedArray(Intent.CREATOR);
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
|
||||
/**
|
||||
* PermaSql allows for creating SQL statements that can be saved and used
|
||||
* later without dates getting stale. It also allows these values to be
|
||||
* used in
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public final class PermaSql {
|
||||
|
||||
// --- placeholder strings
|
||||
|
||||
/** value to be replaced with the current time as long */
|
||||
public static final String VALUE_NOW = "NOW()"; //$NON-NLS-1$
|
||||
|
||||
/** value to be replaced by end of day as long */
|
||||
public static final String VALUE_EOD = "EOD()"; //$NON-NLS-1$
|
||||
|
||||
/** value to be replaced by end of day yesterday as long */
|
||||
public static final String VALUE_EOD_YESTERDAY = "EODY()"; //$NON-NLS-1$
|
||||
|
||||
/** value to be replaced by end of day tomorrow as long */
|
||||
public static final String VALUE_EOD_TOMORROW = "EODT()"; //$NON-NLS-1$
|
||||
|
||||
/** value to be replaced by end of day day after tomorrow as long */
|
||||
public static final String VALUE_EOD_DAY_AFTER = "EODTT()"; //$NON-NLS-1$
|
||||
|
||||
/** value to be replaced by end of day next week as long */
|
||||
public static final String VALUE_EOD_NEXT_WEEK = "EODW()"; //$NON-NLS-1$
|
||||
|
||||
/** Replace placeholder strings with actual */
|
||||
public static String replacePlaceholders(String value) {
|
||||
if(value.contains(VALUE_NOW))
|
||||
value = value.replace(VALUE_NOW, Long.toString(DateUtilities.now()));
|
||||
if(value.contains(VALUE_EOD) || value.contains(VALUE_EOD_DAY_AFTER) ||
|
||||
value.contains(VALUE_EOD_NEXT_WEEK) || value.contains(VALUE_EOD_TOMORROW) ||
|
||||
value.contains(VALUE_EOD_YESTERDAY)) {
|
||||
Date date = new Date();
|
||||
date.setHours(23);
|
||||
date.setMinutes(59);
|
||||
date.setSeconds(59);
|
||||
long time = date.getTime();
|
||||
value = value.replace(VALUE_EOD_YESTERDAY, Long.toString(time - DateUtilities.ONE_DAY));
|
||||
value = value.replace(VALUE_EOD, Long.toString(time));
|
||||
value = value.replace(VALUE_EOD_TOMORROW, Long.toString(time + DateUtilities.ONE_DAY));
|
||||
value = value.replace(VALUE_EOD_DAY_AFTER, Long.toString(time + 2 * DateUtilities.ONE_DAY));
|
||||
value = value.replace(VALUE_EOD_NEXT_WEEK, Long.toString(time + 7 * DateUtilities.ONE_DAY));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Represents an intent that can be called to perform synchronization
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class SyncAction implements Parcelable {
|
||||
|
||||
/**
|
||||
* Label
|
||||
*/
|
||||
public String label = null;
|
||||
|
||||
/**
|
||||
* Intent to call when invoking this operation
|
||||
*/
|
||||
public PendingIntent intent = null;
|
||||
|
||||
/**
|
||||
* Create an EditOperation object
|
||||
*
|
||||
* @param label
|
||||
* label to display
|
||||
* @param intent
|
||||
* intent to invoke. {@link EXTRAS_TASK_ID} will be passed
|
||||
*/
|
||||
public SyncAction(String label, PendingIntent intent) {
|
||||
super();
|
||||
this.label = label;
|
||||
this.intent = intent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label of this action
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return label;
|
||||
}
|
||||
|
||||
// --- parcelable helpers
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(label);
|
||||
dest.writeParcelable(intent, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parcelable creator
|
||||
*/
|
||||
public static final Parcelable.Creator<SyncAction> CREATOR = new Parcelable.Creator<SyncAction>() {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public SyncAction createFromParcel(Parcel source) {
|
||||
return new SyncAction(source.readString(), (PendingIntent)source.readParcelable(
|
||||
PendingIntent.class.getClassLoader()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public SyncAction[] newArray(int size) {
|
||||
return new SyncAction[size];
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Represents an intent that can be called on a task
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class TaskAction implements Parcelable {
|
||||
|
||||
/**
|
||||
* Label
|
||||
*/
|
||||
public String text = null;
|
||||
|
||||
/**
|
||||
* Intent to call when invoking this operation
|
||||
*/
|
||||
public PendingIntent intent = null;
|
||||
|
||||
/**
|
||||
* Create an EditOperation object
|
||||
*
|
||||
* @param text
|
||||
* label to display
|
||||
* @param intent
|
||||
* intent to invoke. {@link EXTRAS_TASK_ID} will be passed
|
||||
*/
|
||||
public TaskAction(String text, PendingIntent intent) {
|
||||
super();
|
||||
this.text = text;
|
||||
this.intent = intent;
|
||||
}
|
||||
|
||||
// --- parcelable helpers
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(text);
|
||||
dest.writeParcelable(intent, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parcelable creator
|
||||
*/
|
||||
public static final Parcelable.Creator<TaskAction> CREATOR = new Parcelable.Creator<TaskAction>() {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public TaskAction createFromParcel(Parcel source) {
|
||||
return new TaskAction(source.readString(), (PendingIntent)source.readParcelable(
|
||||
PendingIntent.class.getClassLoader()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public TaskAction[] newArray(int size) {
|
||||
return new TaskAction[size];
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@ -1,98 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.api;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.RemoteViews.RemoteView;
|
||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
|
||||
/**
|
||||
* Represents a line of text displayed in the Task List
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public final class TaskDecoration implements Parcelable {
|
||||
|
||||
/**
|
||||
* Place decoration between completion box and task title
|
||||
*/
|
||||
public static final int POSITION_LEFT = 0;
|
||||
|
||||
/**
|
||||
* Place decoration between task title and importance bar
|
||||
*/
|
||||
public static final int POSITION_RIGHT = 1;
|
||||
|
||||
/**
|
||||
* {@link RemoteView} decoration
|
||||
*/
|
||||
@CheckForNull
|
||||
public RemoteViews decoration = null;
|
||||
|
||||
/**
|
||||
* Decoration position
|
||||
*/
|
||||
public int position = POSITION_LEFT;
|
||||
|
||||
/**
|
||||
* Decorated task background color. 0 is default
|
||||
*/
|
||||
public int color = 0;
|
||||
|
||||
/**
|
||||
* Creates a TaskDetail object
|
||||
* @param text
|
||||
* text to display
|
||||
* @param color
|
||||
* color to use for text. Use <code>0</code> for default color
|
||||
*/
|
||||
public TaskDecoration(RemoteViews decoration, int position, int color) {
|
||||
this.decoration = decoration;
|
||||
this.position = position;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
// --- parcelable helpers
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeParcelable(decoration, 0);
|
||||
dest.writeInt(position);
|
||||
dest.writeInt(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parcelable creator
|
||||
*/
|
||||
public static final Parcelable.Creator<TaskDecoration> CREATOR = new Parcelable.Creator<TaskDecoration>() {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public TaskDecoration createFromParcel(Parcel source) {
|
||||
return new TaskDecoration((RemoteViews)source.readParcelable(
|
||||
RemoteViews.class.getClassLoader()),
|
||||
source.readInt(), source.readInt());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public TaskDecoration[] newArray(int size) {
|
||||
return new TaskDecoration[size];
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@ -1,296 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Todoroo Inc
|
||||
* All Rights Reserved
|
||||
* http://www.todoroo.com
|
||||
*/
|
||||
package com.todoroo.andlib.data;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteDatabase.CursorFactory;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.todoroo.andlib.data.Property.PropertyVisitor;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
||||
|
||||
/**
|
||||
* AbstractDatabase is a database abstraction which wraps a SQLite database.
|
||||
* <p>
|
||||
* Users of this class are in charge of the database's lifecycle - ensuring that
|
||||
* the database is open when needed and closed when usage is finished. Within an
|
||||
* activity, this is typically accomplished through the onResume and onPause
|
||||
* methods, though if the database is not needed for the activity's entire
|
||||
* lifecycle, it can be closed earlier.
|
||||
* <p>
|
||||
* Direct querying is not recommended for type safety reasons. Instead, use one
|
||||
* of the service classes to issue the request and return a {@link TodorooCursor}.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
abstract public class AbstractDatabase {
|
||||
|
||||
// --- abstract methods
|
||||
|
||||
/**
|
||||
* @return database name
|
||||
*/
|
||||
protected abstract String getName();
|
||||
|
||||
/**
|
||||
* @return all tables in this database
|
||||
*/
|
||||
protected abstract Table[] getTables();
|
||||
|
||||
/**
|
||||
* @return database version
|
||||
*/
|
||||
protected abstract int getVersion();
|
||||
|
||||
/**
|
||||
* Called after database and tables are created. Use this method to
|
||||
* create indices and perform other database maintenance
|
||||
*/
|
||||
protected abstract void onCreateTables();
|
||||
|
||||
/**
|
||||
* Upgrades an open database from one version to the next
|
||||
* @param oldVersion
|
||||
* @param newVersion
|
||||
* @return true if upgrade was handled, false otherwise
|
||||
*/
|
||||
protected abstract boolean onUpgrade(int oldVersion, int newVersion);
|
||||
|
||||
// --- protected variables
|
||||
|
||||
/**
|
||||
* SQLiteOpenHelper that takes care of database operations
|
||||
*/
|
||||
protected SQLiteOpenHelper helper = null;
|
||||
|
||||
/**
|
||||
* Internal pointer to open database. Hides the fact that there is a
|
||||
* database and a wrapper by making a single monolithic interface
|
||||
*/
|
||||
protected SQLiteDatabase database = null;
|
||||
|
||||
// --- internal implementation
|
||||
|
||||
/**
|
||||
* Return the name of the table containing these models
|
||||
* @param modelType
|
||||
* @return
|
||||
*/
|
||||
public final Table getTable(Class<? extends AbstractModel> modelType) {
|
||||
for(Table table : getTables()) {
|
||||
if(table.modelClass.equals(modelType))
|
||||
return table;
|
||||
}
|
||||
throw new UnsupportedOperationException("Unknown model class " + modelType); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
protected synchronized final void initializeHelper() {
|
||||
if(helper == null) {
|
||||
if(ContextManager.getContext() == null)
|
||||
throw new NullPointerException("Null context creating database helper");
|
||||
helper = new DatabaseHelper(ContextManager.getContext(),
|
||||
getName(), null, getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the database for writing. Must be closed afterwards. If user is
|
||||
* out of disk space, database may be opened for reading instead
|
||||
*/
|
||||
public synchronized final void openForWriting() {
|
||||
initializeHelper();
|
||||
|
||||
if(database != null && !database.isReadOnly() && database.isOpen())
|
||||
return;
|
||||
|
||||
try {
|
||||
database = helper.getWritableDatabase();
|
||||
} catch (NullPointerException e) {
|
||||
// don't know why this happens
|
||||
throw new IllegalStateException(e);
|
||||
} catch (final RuntimeException original) {
|
||||
Log.e("database-" + getName(), "Error opening db",
|
||||
original);
|
||||
try {
|
||||
// provide read-only database
|
||||
openForReading();
|
||||
} catch (Exception readException) {
|
||||
// throw original write exception
|
||||
throw original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the database for reading. Must be closed afterwards
|
||||
*/
|
||||
public synchronized final void openForReading() {
|
||||
initializeHelper();
|
||||
if(database != null && database.isOpen())
|
||||
return;
|
||||
database = helper.getReadableDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database if it has been opened previously
|
||||
*/
|
||||
public synchronized final void close() {
|
||||
if(database != null) {
|
||||
database.close();
|
||||
}
|
||||
database = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all data in database. Warning: this does what it says. Any open
|
||||
* database resources will be abruptly closed.
|
||||
*/
|
||||
public synchronized final void clear() {
|
||||
close();
|
||||
ContextManager.getContext().deleteDatabase(getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return sql database. opens database if not yet open
|
||||
*/
|
||||
public synchronized final SQLiteDatabase getDatabase() {
|
||||
if(database == null) {
|
||||
AndroidUtilities.sleepDeep(300L);
|
||||
openForWriting();
|
||||
}
|
||||
return database;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return human-readable database name for debugging
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DB:" + getName();
|
||||
}
|
||||
|
||||
// --- database wrapper
|
||||
|
||||
/*
|
||||
* @see android.database.sqlite.SQLiteDatabase#rawQuery(String sql, String[] selectionArgs)
|
||||
*/
|
||||
public synchronized Cursor rawQuery(String sql, String[] selectionArgs) {
|
||||
return getDatabase().rawQuery(sql, selectionArgs);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see android.database.sqlite.SQLiteDatabase#insert(String table, String nullColumnHack, ContentValues values)
|
||||
*/
|
||||
public synchronized long insert(String table, String nullColumnHack, ContentValues values) {
|
||||
return getDatabase().insert(table, nullColumnHack, values);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see android.database.sqlite.SQLiteDatabase#delete(String table, String whereClause, String[] whereArgs)
|
||||
*/
|
||||
public synchronized int delete(String table, String whereClause, String[] whereArgs) {
|
||||
return getDatabase().delete(table, whereClause, whereArgs);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see android.database.sqlite.SQLiteDatabase#update(String table, ContentValues values, String whereClause, String[] whereArgs)
|
||||
*/
|
||||
public synchronized int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
|
||||
return getDatabase().update(table, values, whereClause, whereArgs);
|
||||
}
|
||||
|
||||
// --- helper classes
|
||||
|
||||
/**
|
||||
* Default implementation of Astrid database helper
|
||||
*/
|
||||
private class DatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public DatabaseHelper(Context context, String name,
|
||||
CursorFactory factory, int version) {
|
||||
super(context, name, factory, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create the database tables
|
||||
*/
|
||||
@Override
|
||||
public synchronized void onCreate(SQLiteDatabase db) {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
SqlConstructorVisitor sqlVisitor = new SqlConstructorVisitor();
|
||||
|
||||
// create tables
|
||||
for(Table table : getTables()) {
|
||||
sql.append("CREATE TABLE IF NOT EXISTS ").append(table.name).append('(').
|
||||
append(AbstractModel.ID_PROPERTY).append(" INTEGER PRIMARY KEY AUTOINCREMENT");
|
||||
for(Property<?> property : table.getProperties()) {
|
||||
if(AbstractModel.ID_PROPERTY.name.equals(property.name))
|
||||
continue;
|
||||
sql.append(',').append(property.accept(sqlVisitor, null));
|
||||
}
|
||||
sql.append(')');
|
||||
db.execSQL(sql.toString());
|
||||
sql.setLength(0);
|
||||
}
|
||||
|
||||
// post-table-creation
|
||||
database = db;
|
||||
onCreateTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to upgrade the database to a new version
|
||||
*/
|
||||
@Override
|
||||
public synchronized void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.w("database-" + getName(), String.format("Upgrading database from version %d to %d.",
|
||||
oldVersion, newVersion));
|
||||
|
||||
database = db;
|
||||
if(!AbstractDatabase.this.onUpgrade(oldVersion, newVersion)) {
|
||||
// We don't know how to handle this case because someone forgot to
|
||||
// implement the upgrade. We can't drop tables, we can only
|
||||
// throw a nasty exception at this time
|
||||
|
||||
throw new IllegalStateException("Missing database migration " +
|
||||
"from " + oldVersion + " to " + newVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor that returns SQL constructor for this property
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public static class SqlConstructorVisitor implements PropertyVisitor<String, Void> {
|
||||
|
||||
public String visitDouble(Property<Double> property, Void data) {
|
||||
return String.format("%s REAL", property.name);
|
||||
}
|
||||
|
||||
public String visitInteger(Property<Integer> property, Void data) {
|
||||
return String.format("%s INTEGER", property.name);
|
||||
}
|
||||
|
||||
public String visitLong(Property<Long> property, Void data) {
|
||||
return String.format("%s INTEGER", property.name);
|
||||
}
|
||||
|
||||
public String visitString(Property<String> property, Void data) {
|
||||
return String.format("%s TEXT", property.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,431 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Todoroo Inc
|
||||
* All Rights Reserved
|
||||
* http://www.todoroo.com
|
||||
*/
|
||||
package com.todoroo.andlib.data;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.todoroo.andlib.data.Property.DoubleProperty;
|
||||
import com.todoroo.andlib.data.Property.IntegerProperty;
|
||||
import com.todoroo.andlib.data.Property.LongProperty;
|
||||
import com.todoroo.andlib.data.Property.PropertyVisitor;
|
||||
|
||||
/**
|
||||
* <code>AbstractModel</code> represents a row in a database.
|
||||
* <p>
|
||||
* A single database can be represented by multiple <code>AbstractModel</code>s
|
||||
* corresponding to different queries that return a different set of columns.
|
||||
* Each model exposes a set of properties that it contains.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractModel implements Parcelable {
|
||||
|
||||
// --- static variables
|
||||
|
||||
private static final ContentValuesSavingVisitor saver = new ContentValuesSavingVisitor();
|
||||
|
||||
// --- constants
|
||||
|
||||
/** id property common to all models */
|
||||
protected static final String ID_PROPERTY_NAME = "_id"; //$NON-NLS-1$
|
||||
|
||||
/** id field common to all models */
|
||||
public static final IntegerProperty ID_PROPERTY = new IntegerProperty(null, ID_PROPERTY_NAME);
|
||||
|
||||
/** sentinel for objects without an id */
|
||||
public static final long NO_ID = 0;
|
||||
|
||||
// --- abstract methods
|
||||
|
||||
/** Get the default values for this object */
|
||||
abstract public ContentValues getDefaultValues();
|
||||
|
||||
// --- data store variables and management
|
||||
|
||||
/* Data Source Ordering:
|
||||
*
|
||||
* In order to return the best data, we want to check first what the user
|
||||
* has explicitly set (setValues), then the values we have read out of
|
||||
* the database (values), then defaults (getDefaultValues)
|
||||
*/
|
||||
|
||||
/** User set values */
|
||||
protected ContentValues setValues = null;
|
||||
|
||||
/** Values from database */
|
||||
protected ContentValues values = null;
|
||||
|
||||
/** Get database-read values for this object */
|
||||
public ContentValues getDatabaseValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
/** Get the user-set values for this object */
|
||||
public ContentValues getSetValues() {
|
||||
return setValues;
|
||||
}
|
||||
|
||||
/** Get a list of all field/value pairs merged across data sources */
|
||||
public ContentValues getMergedValues() {
|
||||
ContentValues mergedValues = new ContentValues();
|
||||
|
||||
ContentValues defaultValues = getDefaultValues();
|
||||
if(defaultValues != null)
|
||||
mergedValues.putAll(defaultValues);
|
||||
if(values != null)
|
||||
mergedValues.putAll(values);
|
||||
if(setValues != null)
|
||||
mergedValues.putAll(setValues);
|
||||
|
||||
return mergedValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all data on this model
|
||||
*/
|
||||
public void clear() {
|
||||
values = null;
|
||||
setValues = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers all set values into values. This occurs when a task is
|
||||
* saved - future saves will not need to write all the data as before.
|
||||
*/
|
||||
public void markSaved() {
|
||||
if(values == null)
|
||||
values = setValues;
|
||||
else if(setValues != null)
|
||||
values.putAll(setValues);
|
||||
setValues = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use merged values to compare two models to each other. Must be of
|
||||
* exactly the same class.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if(other == null || other.getClass() != getClass())
|
||||
return false;
|
||||
|
||||
return getMergedValues().equals(((AbstractModel)other).getMergedValues());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getMergedValues().hashCode() ^ getClass().hashCode();
|
||||
}
|
||||
|
||||
// --- data retrieval
|
||||
|
||||
/**
|
||||
* Reads all properties from the supplied cursor and store
|
||||
*/
|
||||
protected synchronized void readPropertiesFromCursor(TodorooCursor<? extends AbstractModel> cursor) {
|
||||
if (values == null)
|
||||
values = new ContentValues();
|
||||
|
||||
// clears user-set values
|
||||
setValues = null;
|
||||
|
||||
for (Property<?> property : cursor.getProperties()) {
|
||||
saver.save(property, values, cursor.get(property));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given property. Make sure this model has this property!
|
||||
*/
|
||||
public synchronized <TYPE> TYPE getValue(Property<TYPE> property) {
|
||||
Object value;
|
||||
if(setValues != null && setValues.containsKey(property.name))
|
||||
value = setValues.get(property.name);
|
||||
|
||||
else if(values != null && values.containsKey(property.name))
|
||||
value = values.get(property.name);
|
||||
|
||||
else if(getDefaultValues().containsKey(property.name))
|
||||
value = getDefaultValues().get(property.name);
|
||||
|
||||
else
|
||||
throw new UnsupportedOperationException(
|
||||
"Model Error: Did not read property " + property.name); //$NON-NLS-1$
|
||||
|
||||
// resolve properties that were retrieved with a different type than accessed
|
||||
if(value instanceof String && property instanceof LongProperty)
|
||||
return (TYPE) Long.valueOf((String)value);
|
||||
else if(value instanceof String && property instanceof IntegerProperty)
|
||||
return (TYPE) Integer.valueOf((String)value);
|
||||
else if(value instanceof String && property instanceof DoubleProperty)
|
||||
return (TYPE) Double.valueOf((String)value);
|
||||
else if(value instanceof Integer && property instanceof LongProperty)
|
||||
return (TYPE) Long.valueOf(((Number)value).longValue());
|
||||
return (TYPE) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to get the identifier of the model, if it exists.
|
||||
*
|
||||
* @return {@value #NO_ID} if this model was not added to the database
|
||||
*/
|
||||
abstract public long getId();
|
||||
|
||||
protected long getIdHelper(LongProperty id) {
|
||||
if(setValues != null && setValues.containsKey(id.name))
|
||||
return setValues.getAsLong(id.name);
|
||||
else if(values != null && values.containsKey(id.name))
|
||||
return values.getAsLong(id.name);
|
||||
else
|
||||
return NO_ID;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
if (setValues == null)
|
||||
setValues = new ContentValues();
|
||||
|
||||
if(id == NO_ID)
|
||||
setValues.remove(ID_PROPERTY_NAME);
|
||||
else
|
||||
setValues.put(ID_PROPERTY_NAME, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this model has found Jesus (i.e. the database)
|
||||
*/
|
||||
public boolean isSaved() {
|
||||
return getId() != NO_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param property
|
||||
* @return true if setValues or values contains this property
|
||||
*/
|
||||
public boolean containsValue(Property<?> property) {
|
||||
if(setValues != null && setValues.containsKey(property.name))
|
||||
return true;
|
||||
if(values != null && values.containsKey(property.name))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param property
|
||||
* @return true if setValues or values contains this property, and the value
|
||||
* stored is not null
|
||||
*/
|
||||
public boolean containsNonNullValue(Property<?> property) {
|
||||
if(setValues != null && setValues.containsKey(property.name))
|
||||
return setValues.get(property.name) != null;
|
||||
if(values != null && values.containsKey(property.name))
|
||||
return values.get(property.name) != null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- data storage
|
||||
|
||||
/**
|
||||
* Check whether the user has changed this property value and it should be
|
||||
* stored for saving in the database
|
||||
*/
|
||||
protected synchronized <TYPE> boolean shouldSaveValue(
|
||||
Property<TYPE> property, TYPE newValue) {
|
||||
|
||||
// we've already decided to save it, so overwrite old value
|
||||
if (setValues.containsKey(property.name))
|
||||
return true;
|
||||
|
||||
// values contains this key, we should check it out
|
||||
if(values != null && values.containsKey(property.name)) {
|
||||
TYPE value = getValue(property);
|
||||
if (value == null) {
|
||||
if (newValue == null)
|
||||
return false;
|
||||
} else if (value.equals(newValue))
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise, good to save
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given property. Make sure this model has this property!
|
||||
*/
|
||||
public synchronized <TYPE> void setValue(Property<TYPE> property,
|
||||
TYPE value) {
|
||||
if (setValues == null)
|
||||
setValues = new ContentValues();
|
||||
if (!shouldSaveValue(property, value))
|
||||
return;
|
||||
|
||||
saver.save(property, setValues, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges content values with those coming from another source
|
||||
*/
|
||||
public synchronized <TYPE> void mergeWith(ContentValues other) {
|
||||
if (setValues == null)
|
||||
setValues = new ContentValues();
|
||||
setValues.putAll(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the key for the given property
|
||||
* @param property
|
||||
*/
|
||||
public synchronized void clearValue(Property<?> property) {
|
||||
if(setValues != null && setValues.containsKey(property.name))
|
||||
setValues.remove(property.name);
|
||||
else if(values != null && values.containsKey(property.name))
|
||||
values.remove(property.name);
|
||||
else if(getDefaultValues().containsKey(property.name))
|
||||
throw new IllegalArgumentException("Property has a default value"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
// --- property management
|
||||
|
||||
/**
|
||||
* Looks inside the given class and finds all declared properties
|
||||
*/
|
||||
protected static Property<?>[] generateProperties(Class<? extends AbstractModel> cls) {
|
||||
ArrayList<Property<?>> properties = new ArrayList<Property<?>>();
|
||||
if(cls.getSuperclass() != AbstractModel.class)
|
||||
properties.addAll(Arrays.asList(generateProperties(
|
||||
(Class<? extends AbstractModel>) cls.getSuperclass())));
|
||||
|
||||
// a property is public, static & extends Property
|
||||
for(Field field : cls.getFields()) {
|
||||
if((field.getModifiers() & Modifier.STATIC) == 0)
|
||||
continue;
|
||||
if(!Property.class.isAssignableFrom(field.getType()))
|
||||
continue;
|
||||
try {
|
||||
properties.add((Property<?>) field.get(null));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return properties.toArray(new Property<?>[properties.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor that saves a value into a content values store
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public static class ContentValuesSavingVisitor implements PropertyVisitor<Void, Object> {
|
||||
|
||||
private ContentValues store;
|
||||
|
||||
public synchronized void save(Property<?> property, ContentValues newStore, Object value) {
|
||||
this.store = newStore;
|
||||
|
||||
// we don't allow null values, as they indicate unset properties
|
||||
// when the database was written
|
||||
|
||||
if(value != null)
|
||||
property.accept(this, value);
|
||||
}
|
||||
|
||||
public Void visitDouble(Property<Double> property, Object value) {
|
||||
store.put(property.name, (Double) value);
|
||||
return null;
|
||||
}
|
||||
|
||||
public Void visitInteger(Property<Integer> property, Object value) {
|
||||
store.put(property.name, (Integer) value);
|
||||
return null;
|
||||
}
|
||||
|
||||
public Void visitLong(Property<Long> property, Object value) {
|
||||
store.put(property.name, (Long) value);
|
||||
return null;
|
||||
}
|
||||
|
||||
public Void visitString(Property<String> property, Object value) {
|
||||
store.put(property.name, (String) value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// --- parcelable helpers
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeParcelable(setValues, 0);
|
||||
dest.writeParcelable(values, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* In addition to overriding this class, model classes should create
|
||||
* a static final variable named "CREATOR" in order to satisfy the
|
||||
* requirements of the Parcelable interface.
|
||||
*/
|
||||
abstract protected Parcelable.Creator<? extends AbstractModel> getCreator();
|
||||
|
||||
/**
|
||||
* Parcelable creator helper
|
||||
*/
|
||||
protected static final class ModelCreator<TYPE extends AbstractModel>
|
||||
implements Parcelable.Creator<TYPE> {
|
||||
|
||||
private final Class<TYPE> cls;
|
||||
|
||||
public ModelCreator(Class<TYPE> cls) {
|
||||
super();
|
||||
this.cls = cls;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public TYPE createFromParcel(Parcel source) {
|
||||
TYPE model;
|
||||
try {
|
||||
model = cls.newInstance();
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
model.setValues = source.readParcelable(ContentValues.class.getClassLoader());
|
||||
model.values = source.readParcelable(ContentValues.class.getClassLoader());
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public TYPE[] newArray(int size) {
|
||||
return (TYPE[]) Array.newInstance(cls, size);
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@ -1,250 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Todoroo Inc
|
||||
* All Rights Reserved
|
||||
* http://www.todoroo.com
|
||||
*/
|
||||
package com.todoroo.andlib.data;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.util.Log;
|
||||
|
||||
import com.todoroo.andlib.sql.Criterion;
|
||||
import com.todoroo.andlib.sql.Query;
|
||||
import com.todoroo.astrid.utility.Constants;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Abstract data access object
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class GenericDao<TYPE extends AbstractModel> {
|
||||
|
||||
private final Class<TYPE> modelClass;
|
||||
|
||||
private Table table;
|
||||
|
||||
private AbstractDatabase database;
|
||||
|
||||
public GenericDao(Class<TYPE> modelClass) {
|
||||
this.modelClass = modelClass;
|
||||
}
|
||||
|
||||
public GenericDao(Class<TYPE> modelClass, AbstractDatabase database) {
|
||||
this.modelClass = modelClass;
|
||||
setDatabase(database);
|
||||
}
|
||||
|
||||
/** Gets table associated with this DAO */
|
||||
public Table getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets database accessed by this DAO. Used for dependency-injected
|
||||
* initialization by child classes and unit tests
|
||||
*
|
||||
* @param database
|
||||
*/
|
||||
public void setDatabase(AbstractDatabase database) {
|
||||
if(database == this.database)
|
||||
return;
|
||||
this.database = database;
|
||||
table = database.getTable(modelClass);
|
||||
}
|
||||
|
||||
// --- dao methods
|
||||
|
||||
/**
|
||||
* Construct a query with SQL DSL objects
|
||||
*
|
||||
* @param query
|
||||
* @return
|
||||
*/
|
||||
public TodorooCursor<TYPE> query(Query query) {
|
||||
query.from(table);
|
||||
if(Constants.DEBUG)
|
||||
Log.i("SQL-" + modelClass.getSimpleName(), query.toString()); //$NON-NLS-1$
|
||||
Cursor cursor = database.rawQuery(query.toString(), null);
|
||||
return new TodorooCursor<TYPE>(cursor, query.getFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a query with raw SQL
|
||||
*
|
||||
* @param properties
|
||||
* @param selection
|
||||
* @param selectionArgs
|
||||
* @return
|
||||
*/
|
||||
public TodorooCursor<TYPE> rawQuery(String selection, String[] selectionArgs, Property<?>... properties) {
|
||||
String[] fields = new String[properties.length];
|
||||
for(int i = 0; i < properties.length; i++)
|
||||
fields[i] = properties[i].name;
|
||||
return new TodorooCursor<TYPE>(database.getDatabase().query(table.name,
|
||||
fields, selection, selectionArgs, null, null, null),
|
||||
properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns object corresponding to the given identifier
|
||||
*
|
||||
* @param database
|
||||
* @param table
|
||||
* name of table
|
||||
* @param properties
|
||||
* properties to read
|
||||
* @param id
|
||||
* id of item
|
||||
* @return null if no item found
|
||||
*/
|
||||
public TYPE fetch(long id, Property<?>... properties) {
|
||||
TodorooCursor<TYPE> cursor = fetchItem(id, properties);
|
||||
try {
|
||||
if (cursor.getCount() == 0)
|
||||
return null;
|
||||
Constructor<TYPE> constructor = modelClass.getConstructor(TodorooCursor.class);
|
||||
return constructor.newInstance(cursor);
|
||||
} catch (SecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given id
|
||||
*
|
||||
* @param database
|
||||
* @param id
|
||||
* @return true if delete was successful
|
||||
*/
|
||||
public boolean delete(long id) {
|
||||
return database.delete(table.name,
|
||||
AbstractModel.ID_PROPERTY.eq(id).toString(), null) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all matching a clause
|
||||
* @param database
|
||||
* @param where
|
||||
* @return # of deleted items
|
||||
*/
|
||||
public int deleteWhere(Criterion where) {
|
||||
return database.delete(table.name,
|
||||
where.toString(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the given object to the database. Creates a new object if
|
||||
* model id property has not been set
|
||||
*
|
||||
* @return true on success.
|
||||
*/
|
||||
public boolean persist(TYPE item) {
|
||||
if (item.getId() == AbstractModel.NO_ID) {
|
||||
return createNew(item);
|
||||
} else {
|
||||
ContentValues values = item.getSetValues();
|
||||
|
||||
if (values.size() == 0) // nothing changed
|
||||
return true;
|
||||
|
||||
return saveExisting(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the given item.
|
||||
*
|
||||
* @param database
|
||||
* @param table
|
||||
* table name
|
||||
* @param item
|
||||
* item model
|
||||
* @return returns true on success.
|
||||
*/
|
||||
public boolean createNew(TYPE item) {
|
||||
long newRow = database.insert(table.name,
|
||||
AbstractModel.ID_PROPERTY.name, item.getMergedValues());
|
||||
boolean result = newRow >= 0;
|
||||
if(result) {
|
||||
item.markSaved();
|
||||
item.setId(newRow);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the given item. Will not create a new item!
|
||||
*
|
||||
* @param database
|
||||
* @param table
|
||||
* table name
|
||||
* @param item
|
||||
* item model
|
||||
* @return returns true on success.
|
||||
*/
|
||||
public boolean saveExisting(TYPE item) {
|
||||
ContentValues values = item.getSetValues();
|
||||
if(values == null || values.size() == 0) // nothing changed
|
||||
return true;
|
||||
boolean result = database.update(table.name, values,
|
||||
AbstractModel.ID_PROPERTY.eq(item.getId()).toString(), null) > 0;
|
||||
if(result)
|
||||
item.markSaved();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates multiple rows of the database based on model set values
|
||||
*
|
||||
* @param item
|
||||
* item model
|
||||
* @param criterion
|
||||
* @return returns true on success.
|
||||
*/
|
||||
public int updateMultiple(ContentValues values, Criterion criterion) {
|
||||
if(values.size() == 0) // nothing changed
|
||||
return 0;
|
||||
return database.update(table.name, values, criterion.toString(), null);
|
||||
}
|
||||
|
||||
// --- helper methods
|
||||
|
||||
|
||||
/**
|
||||
* Returns cursor to object corresponding to the given identifier
|
||||
*
|
||||
* @param database
|
||||
* @param table
|
||||
* name of table
|
||||
* @param properties
|
||||
* properties to read
|
||||
* @param id
|
||||
* id of item
|
||||
* @return
|
||||
*/
|
||||
protected TodorooCursor<TYPE> fetchItem(long id, Property<?>... properties) {
|
||||
TodorooCursor<TYPE> cursor = query(
|
||||
Query.select(properties).where(AbstractModel.ID_PROPERTY.eq(id)));
|
||||
cursor.moveToFirst();
|
||||
return new TodorooCursor<TYPE>(cursor, properties);
|
||||
}
|
||||
}
|
||||
@ -1,207 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Todoroo Inc
|
||||
* All Rights Reserved
|
||||
* http://www.todoroo.com
|
||||
*/
|
||||
package com.todoroo.andlib.data;
|
||||
|
||||
import com.todoroo.andlib.sql.Field;
|
||||
|
||||
/**
|
||||
* Property represents a typed column in a database.
|
||||
*
|
||||
* Within a given database row, the parameter may not exist, in which case the
|
||||
* value is null, it may be of an incorrect type, in which case an exception is
|
||||
* thrown, or the correct type, in which case the value is returned.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
* @param <TYPE>
|
||||
* a database supported type, such as String or Integer
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public abstract class Property<TYPE> extends Field implements Cloneable {
|
||||
|
||||
// --- implementation
|
||||
|
||||
/** The database table name this property */
|
||||
public final Table table;
|
||||
|
||||
/** The database column name for this property */
|
||||
public final String name;
|
||||
|
||||
/**
|
||||
* Create a property by table and column name. Uses the default property
|
||||
* expression which is derived from default table name
|
||||
*/
|
||||
protected Property(Table table, String columnName) {
|
||||
this(table, columnName, (table == null) ? (columnName) : (table.name + "." + columnName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a property by table and column name, manually specifying an
|
||||
* expression to use in SQL
|
||||
*/
|
||||
protected Property(Table table, String columnName, String expression) {
|
||||
super(expression);
|
||||
this.table = table;
|
||||
this.name = columnName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a visitor
|
||||
*/
|
||||
abstract public <RETURN, PARAMETER> RETURN accept(
|
||||
PropertyVisitor<RETURN, PARAMETER> visitor, PARAMETER data);
|
||||
|
||||
/**
|
||||
* Return a clone of this property
|
||||
*/
|
||||
@Override
|
||||
public Property<TYPE> clone() {
|
||||
try {
|
||||
return (Property<TYPE>) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// --- helper classes and interfaces
|
||||
|
||||
/**
|
||||
* Visitor interface for property classes
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public interface PropertyVisitor<RETURN, PARAMETER> {
|
||||
public RETURN visitInteger(Property<Integer> property, PARAMETER data);
|
||||
|
||||
public RETURN visitLong(Property<Long> property, PARAMETER data);
|
||||
|
||||
public RETURN visitDouble(Property<Double> property, PARAMETER data);
|
||||
|
||||
public RETURN visitString(Property<String> property, PARAMETER data);
|
||||
}
|
||||
|
||||
// --- children
|
||||
|
||||
/**
|
||||
* Integer property type. See {@link Property}
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public static class IntegerProperty extends Property<Integer> {
|
||||
|
||||
public IntegerProperty(Table table, String name) {
|
||||
super(table, name);
|
||||
}
|
||||
|
||||
protected IntegerProperty(Table table, String name, String expression) {
|
||||
super(table, name, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RETURN, PARAMETER> RETURN accept(
|
||||
PropertyVisitor<RETURN, PARAMETER> visitor, PARAMETER data) {
|
||||
return visitor.visitInteger(this, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* String property type. See {@link Property}
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public static class StringProperty extends Property<String> {
|
||||
|
||||
public StringProperty(Table table, String name) {
|
||||
super(table, name);
|
||||
}
|
||||
|
||||
protected StringProperty(Table table, String name, String expression) {
|
||||
super(table, name, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RETURN, PARAMETER> RETURN accept(
|
||||
PropertyVisitor<RETURN, PARAMETER> visitor, PARAMETER data) {
|
||||
return visitor.visitString(this, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Double property type. See {@link Property}
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public static class DoubleProperty extends Property<Double> {
|
||||
|
||||
public DoubleProperty(Table table, String name) {
|
||||
super(table, name);
|
||||
}
|
||||
|
||||
protected DoubleProperty(Table table, String name, String expression) {
|
||||
super(table, name, expression);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <RETURN, PARAMETER> RETURN accept(
|
||||
PropertyVisitor<RETURN, PARAMETER> visitor, PARAMETER data) {
|
||||
return visitor.visitDouble(this, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Long property type. See {@link Property}
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public static class LongProperty extends Property<Long> {
|
||||
|
||||
public LongProperty(Table table, String name) {
|
||||
super(table, name);
|
||||
}
|
||||
|
||||
protected LongProperty(Table table, String name, String expression) {
|
||||
super(table, name, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <RETURN, PARAMETER> RETURN accept(
|
||||
PropertyVisitor<RETURN, PARAMETER> visitor, PARAMETER data) {
|
||||
return visitor.visitLong(this, data);
|
||||
}
|
||||
}
|
||||
|
||||
// --- pseudo-properties
|
||||
|
||||
/** Runs a SQL function and returns the result as a string */
|
||||
public static class StringFunctionProperty extends StringProperty {
|
||||
public StringFunctionProperty(String function, String columnName) {
|
||||
super(null, columnName, function);
|
||||
alias = columnName;
|
||||
}
|
||||
}
|
||||
|
||||
/** Runs a SQL function and returns the result as a string */
|
||||
public static class IntegerFunctionProperty extends IntegerProperty {
|
||||
public IntegerFunctionProperty(String function, String columnName) {
|
||||
super(null, columnName, function);
|
||||
alias = columnName;
|
||||
}
|
||||
}
|
||||
|
||||
/** Counting in aggregated tables. Returns the result of COUNT(1) */
|
||||
public static final class CountProperty extends IntegerFunctionProperty {
|
||||
public CountProperty() {
|
||||
super("COUNT(1)", "count");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
package com.todoroo.andlib.data;
|
||||
|
||||
import com.todoroo.andlib.sql.Field;
|
||||
import com.todoroo.andlib.sql.SqlTable;
|
||||
|
||||
/**
|
||||
* Table class. Most fields are final, so methods such as <code>as</code> will
|
||||
* clone the table when it returns.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public final class Table extends SqlTable {
|
||||
public final String name;
|
||||
public final Class<? extends AbstractModel> modelClass;
|
||||
|
||||
public Table(String name, Class<? extends AbstractModel> modelClass) {
|
||||
this(name, modelClass, null);
|
||||
}
|
||||
|
||||
public Table(String name, Class<? extends AbstractModel> modelClass, String alias) {
|
||||
super(name);
|
||||
this.name = name;
|
||||
this.alias = alias;
|
||||
this.modelClass = modelClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a list of properties from model class by reflection
|
||||
* @return property array
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public Property<?>[] getProperties() {
|
||||
try {
|
||||
return (Property<?>[])modelClass.getField("PROPERTIES").get(null);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (SecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// --- for sql-dsl
|
||||
|
||||
/**
|
||||
* Create a new join table based on this table, but with an alias
|
||||
*/
|
||||
@Override
|
||||
public Table as(String newAlias) {
|
||||
return new Table(name, modelClass, newAlias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a field object based on the given property
|
||||
* @param property
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public Field field(Property<?> property) {
|
||||
if(alias != null)
|
||||
return Field.field(alias + "." + property.name);
|
||||
return Field.field(name + "." + property.name);
|
||||
}
|
||||
}
|
||||
@ -1,109 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.andlib.data;
|
||||
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.CursorWrapper;
|
||||
|
||||
import com.todoroo.andlib.data.Property.PropertyVisitor;
|
||||
|
||||
/**
|
||||
* AstridCursor wraps a cursor and allows users to query for individual
|
||||
* {@link Property} types or read an entire {@link AbstractModel} from
|
||||
* a database row.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
* @param <TYPE> a model type that is returned by this cursor
|
||||
*/
|
||||
public class TodorooCursor<TYPE extends AbstractModel> extends CursorWrapper {
|
||||
|
||||
/** Properties read by this cursor */
|
||||
private final Property<?>[] properties;
|
||||
|
||||
/** Weakly cache field name to column id references for this cursor.
|
||||
* Because it's a weak hash map, entire keys can be discarded by GC */
|
||||
private final WeakHashMap<String, Integer> columnIndexCache;
|
||||
|
||||
/** Property reading visitor */
|
||||
private static final CursorReadingVisitor reader = new CursorReadingVisitor();
|
||||
|
||||
/**
|
||||
* Create an <code>AstridCursor</code> from the supplied {@link Cursor}
|
||||
* object.
|
||||
*
|
||||
* @param cursor
|
||||
* @param properties properties read from this cursor
|
||||
*/
|
||||
public TodorooCursor(Cursor cursor, Property<?>[] properties) {
|
||||
super(cursor);
|
||||
|
||||
this.properties = properties;
|
||||
columnIndexCache = new WeakHashMap<String, Integer>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value for the given property on the underlying {@link Cursor}
|
||||
*
|
||||
* @param <PROPERTY_TYPE> type to return
|
||||
* @param property to retrieve
|
||||
* @return
|
||||
*/
|
||||
public <PROPERTY_TYPE> PROPERTY_TYPE get(Property<PROPERTY_TYPE> property) {
|
||||
return (PROPERTY_TYPE)property.accept(reader, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets entire property list
|
||||
* @return
|
||||
*/
|
||||
public Property<?>[] getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use cache to get the column index for the given field name
|
||||
*/
|
||||
public synchronized int getColumnIndexFromCache(String field) {
|
||||
Integer index = columnIndexCache.get(field);
|
||||
if(index == null) {
|
||||
index = getColumnIndexOrThrow(field);
|
||||
columnIndexCache.put(field, index);
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor that reads the given property from a cursor
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public static class CursorReadingVisitor implements PropertyVisitor<Object, TodorooCursor<?>> {
|
||||
|
||||
public Object visitDouble(Property<Double> property,
|
||||
TodorooCursor<?> cursor) {
|
||||
return cursor.getDouble(cursor.getColumnIndexFromCache(property.name));
|
||||
}
|
||||
|
||||
public Object visitInteger(Property<Integer> property,
|
||||
TodorooCursor<?> cursor) {
|
||||
return cursor.getInt(cursor.getColumnIndexFromCache(property.name));
|
||||
}
|
||||
|
||||
public Object visitLong(Property<Long> property, TodorooCursor<?> cursor) {
|
||||
return cursor.getLong(cursor.getColumnIndexFromCache(property.name));
|
||||
}
|
||||
|
||||
public Object visitString(Property<String> property,
|
||||
TodorooCursor<?> cursor) {
|
||||
return cursor.getString(cursor.getColumnIndexFromCache(property.name));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* A Dependency Injector knows how to inject certain dependencies based
|
||||
* on the field that is passed in.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public interface AbstractDependencyInjector {
|
||||
|
||||
/**
|
||||
* Gets the injected object for this field. If implementing class does not
|
||||
* know how to handle this dependency, it should return null
|
||||
*
|
||||
* @param object
|
||||
* object to perform dependency injection on
|
||||
* @param field
|
||||
* field tagged with {link Autowired} annotation
|
||||
* @return object to assign to this field, or null
|
||||
*/
|
||||
abstract Object getInjection(Object object, Field field);
|
||||
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Autowired is an annotation that tells the dependency injector to
|
||||
* set up the class as appropriate.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Autowired {
|
||||
//
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
/**
|
||||
* Singleton class to manage current application context
|
||||
* b
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public final class ContextManager {
|
||||
|
||||
/**
|
||||
* Global application context
|
||||
*/
|
||||
private static Context context = null;
|
||||
|
||||
/**
|
||||
* Sets the global context
|
||||
* @param context
|
||||
*/
|
||||
public static void setContext(Context context) {
|
||||
ContextManager.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the global context
|
||||
*/
|
||||
public static Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to read a string from the resources
|
||||
*
|
||||
* @param resid
|
||||
* @param parameters
|
||||
* @return
|
||||
*/
|
||||
public static String getString(int resId, Object... formatArgs) {
|
||||
return context.getString(resId, formatArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to read resources
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Resources getResources() {
|
||||
return context.getResources();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,149 +0,0 @@
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.todoroo.astrid.utility.Constants;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Simple Dependency Injection Service for Android.
|
||||
* <p>
|
||||
* Add dependency injectors to the injector chain, then invoke this method
|
||||
* against classes you wish to perform dependency injection for.
|
||||
* <p>
|
||||
* All errors encountered are handled as warnings, so if dependency injection
|
||||
* seems to be failing, check the logs for more information.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class DependencyInjectionService {
|
||||
|
||||
private static final String QUALIFIED_PACKAGE = "com.t"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Dependency injectors. Use getters and setters to modify this list
|
||||
*/
|
||||
private AbstractDependencyInjector[] injectors = {};
|
||||
|
||||
/**
|
||||
* Perform dependency injection in the caller object
|
||||
*
|
||||
* @param caller
|
||||
* object to perform DI on
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public void inject(Object caller) {
|
||||
|
||||
// Traverse through class and all parent classes, looking for
|
||||
// fields declared with the @Autowired annotation and using
|
||||
// dependency injection to set them as appropriate
|
||||
|
||||
Class<?> cls = caller.getClass();
|
||||
while(cls != null) {
|
||||
String packageName = cls.getPackage().getName();
|
||||
if(!packageName.startsWith(QUALIFIED_PACKAGE))
|
||||
break;
|
||||
|
||||
for(Field field : cls.getDeclaredFields()) {
|
||||
if(field.getAnnotation(Autowired.class) != null) {
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
handleField(caller, field);
|
||||
} catch (IllegalStateException e) {
|
||||
throw new RuntimeException(String.format("Unable to set field '%s' of type '%s'",
|
||||
field.getName(), field.getType()), e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException(String.format("Unable to set field '%s' of type '%s'",
|
||||
field.getName(), field.getType()), e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(String.format("Unable to set field '%s' of type '%s'",
|
||||
field.getName(), field.getType()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cls = cls.getSuperclass();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the appropriate dependency object based on the type
|
||||
* that this autowired field accepts
|
||||
*
|
||||
* @param caller
|
||||
* calling object
|
||||
* @param field
|
||||
* field to inject
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
private synchronized void handleField(Object caller, Field field)
|
||||
throws IllegalStateException, IllegalArgumentException,
|
||||
IllegalAccessException {
|
||||
|
||||
if(field.getType().isPrimitive())
|
||||
throw new IllegalStateException(String.format(
|
||||
"Tried to dependency-inject primative field '%s' of type '%s'",
|
||||
field.getName(), field.getType()));
|
||||
|
||||
// field has already been processed, ignore
|
||||
if (field.get(caller) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (AbstractDependencyInjector injector : injectors) {
|
||||
Object injection = injector.getInjection(caller, field);
|
||||
if (injection != null) {
|
||||
if(Constants.DEBUG)
|
||||
Log.d("INJECTOR", injector + ":" + caller + "." + field.getName() + " => " + injection);
|
||||
field.set(caller, injection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
String.format("No dependency injector found for autowired " +
|
||||
"field '%s' in class '%s'. Injectors: %s",
|
||||
field.getName(), caller.getClass().getName(),
|
||||
Arrays.asList(getInjectors())));
|
||||
}
|
||||
|
||||
// --- static methods
|
||||
|
||||
private static DependencyInjectionService instance = null;
|
||||
|
||||
DependencyInjectionService() {
|
||||
// prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the singleton instance of the dependency injection service.
|
||||
* @return
|
||||
*/
|
||||
public synchronized static DependencyInjectionService getInstance() {
|
||||
if(instance == null)
|
||||
instance = new DependencyInjectionService();
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the array of installed injectors
|
||||
* @return
|
||||
*/
|
||||
public synchronized AbstractDependencyInjector[] getInjectors() {
|
||||
return injectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the array of installed injectors
|
||||
* @param injectors
|
||||
*/
|
||||
public synchronized void setInjectors(AbstractDependencyInjector[] injectors) {
|
||||
this.injectors = injectors;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,165 +0,0 @@
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Exception handling utility class - reports and logs errors
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class ExceptionService {
|
||||
|
||||
@Autowired
|
||||
public ErrorReporter[] errorReporters;
|
||||
|
||||
@Autowired
|
||||
public Integer errorDialogTitleResource;
|
||||
|
||||
@Autowired
|
||||
public Integer errorDialogBodyGeneric;
|
||||
|
||||
@Autowired
|
||||
public Integer errorDialogBodyNullError;
|
||||
|
||||
@Autowired
|
||||
public Integer errorDialogBodySocketTimeout;
|
||||
|
||||
public ExceptionService() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the error via registered error handlers
|
||||
*
|
||||
* @param name Internal error name. Not displayed to user
|
||||
* @param error Exception encountered. Message will be displayed to user
|
||||
*/
|
||||
public void reportError(String name, Throwable error) {
|
||||
if(errorReporters == null)
|
||||
return;
|
||||
|
||||
for(ErrorReporter reporter : errorReporters)
|
||||
reporter.handleError(name, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display error dialog if context is activity and report error
|
||||
*
|
||||
* @param context Application Context
|
||||
* @param name Internal error name. Not displayed to user
|
||||
* @param error Exception encountered. Message will be displayed to user
|
||||
*/
|
||||
public void displayAndReportError(final Context context, String name, Throwable error) {
|
||||
if(context instanceof Activity) {
|
||||
final String messageToDisplay;
|
||||
|
||||
// pretty up the message when displaying to user
|
||||
if(error == null)
|
||||
messageToDisplay = context.getString(errorDialogBodyNullError);
|
||||
else if(error instanceof SocketTimeoutException)
|
||||
messageToDisplay = context.getString(errorDialogBodySocketTimeout);
|
||||
else
|
||||
messageToDisplay = context.getString(errorDialogBodyGeneric, error);
|
||||
|
||||
((Activity)context).runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(errorDialogTitleResource)
|
||||
.setMessage(messageToDisplay)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
} catch (Exception e) {
|
||||
// suppress errors during dialog creation
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
reportError(name, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error reporter interface
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public interface ErrorReporter {
|
||||
public void handleError(String name, Throwable error);
|
||||
}
|
||||
|
||||
/**
|
||||
* AndroidLogReporter reports errors to LogCat
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public static class AndroidLogReporter implements ErrorReporter {
|
||||
|
||||
/**
|
||||
* Report the error to the logs
|
||||
*
|
||||
* @param name
|
||||
* @param error
|
||||
*/
|
||||
public void handleError(String name, Throwable error) {
|
||||
String tag = null;
|
||||
if(ContextManager.getContext() != null) {
|
||||
PackageManager pm = ContextManager.getContext().getPackageManager();
|
||||
try {
|
||||
String appName = pm.getApplicationInfo(ContextManager.getContext().
|
||||
getPackageName(), 0).name;
|
||||
tag = appName + "-" + name; //$NON-NLS-1$
|
||||
} catch (NameNotFoundException e) {
|
||||
// give up
|
||||
}
|
||||
}
|
||||
|
||||
if(tag == null)
|
||||
tag = "unknown-" + name; //$NON-NLS-1$
|
||||
|
||||
if(error == null)
|
||||
Log.e(tag, "Exception: " + name); //$NON-NLS-1$
|
||||
else
|
||||
Log.e(tag, error.toString(), error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uncaught exception handler uses the exception utilities class to
|
||||
* report errors
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public static class TodorooUncaughtExceptionHandler implements UncaughtExceptionHandler {
|
||||
private final UncaughtExceptionHandler defaultUEH;
|
||||
|
||||
@Autowired
|
||||
protected ExceptionService exceptionService;
|
||||
|
||||
public TodorooUncaughtExceptionHandler() {
|
||||
defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
public void uncaughtException(Thread thread, Throwable ex) {
|
||||
if(exceptionService != null)
|
||||
exceptionService.reportError("uncaught", ex); //$NON-NLS-1$
|
||||
defaultUEH.uncaughtException(thread, ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HttpErrorException extends IOException {
|
||||
|
||||
private static final long serialVersionUID = 5373340422464657279L;
|
||||
|
||||
public HttpErrorException(int code, String message) {
|
||||
super(String.format("%d %s", code, message)); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,165 +0,0 @@
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.params.BasicHttpParams;
|
||||
import org.apache.http.params.HttpConnectionParams;
|
||||
import org.apache.http.params.HttpParams;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* RestClient allows Android to consume web requests.
|
||||
* <p>
|
||||
* Portions by Praeda:
|
||||
* http://senior.ceng.metu.edu.tr/2009/praeda/2009/01/11/a-simple
|
||||
* -restful-client-at-android/
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class HttpRestClient implements RestClient {
|
||||
|
||||
private static final int HTTP_UNAVAILABLE_END = 599;
|
||||
private static final int HTTP_UNAVAILABLE_START = 500;
|
||||
private static final int HTTP_OK = 200;
|
||||
|
||||
private static final int TIMEOUT_MILLIS = 30000;
|
||||
|
||||
private static WeakReference<HttpClient> httpClient = null;
|
||||
|
||||
@Autowired
|
||||
private Boolean debug;
|
||||
|
||||
public HttpRestClient() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
private static String convertStreamToString(InputStream is) {
|
||||
/*
|
||||
* To convert the InputStream to String we use the
|
||||
* BufferedReader.readLine() method. We iterate until the BufferedReader
|
||||
* return null which means there's no more data to read. Each line will
|
||||
* appended to a StringBuilder and returned as String.
|
||||
*/
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is), 16384);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
String line = null;
|
||||
try {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line + "\n"); //$NON-NLS-1$
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private synchronized static HttpClient getClient() {
|
||||
if (httpClient == null || httpClient.get() == null) {
|
||||
HttpParams params = new BasicHttpParams();
|
||||
HttpConnectionParams.setConnectionTimeout(params, TIMEOUT_MILLIS);
|
||||
HttpConnectionParams.setSoTimeout(params, TIMEOUT_MILLIS);
|
||||
HttpClient client = new DefaultHttpClient(params);
|
||||
httpClient = new WeakReference<HttpClient>(client);
|
||||
return client;
|
||||
} else {
|
||||
return httpClient.get();
|
||||
}
|
||||
}
|
||||
|
||||
private String processHttpResponse(HttpResponse response) throws IOException {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if(statusCode >= HTTP_UNAVAILABLE_START && statusCode <= HTTP_UNAVAILABLE_END) {
|
||||
throw new HttpUnavailableException();
|
||||
} else if(statusCode != HTTP_OK) {
|
||||
throw new HttpErrorException(response.getStatusLine().getStatusCode(),
|
||||
response.getStatusLine().getReasonPhrase());
|
||||
}
|
||||
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
||||
if (entity != null) {
|
||||
InputStream contentStream = entity.getContent();
|
||||
try {
|
||||
return convertStreamToString(contentStream);
|
||||
} finally {
|
||||
contentStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue an HTTP GET for the given URL, return the response
|
||||
*
|
||||
* @param url url with url-encoded params
|
||||
* @return response, or null if there was no response
|
||||
* @throws IOException
|
||||
*/
|
||||
public synchronized String get(String url) throws IOException {
|
||||
if(debug)
|
||||
Log.d("http-rest-client-get", url); //$NON-NLS-1$
|
||||
|
||||
try {
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
HttpResponse response = getClient().execute(httpGet);
|
||||
|
||||
return processHttpResponse(response);
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
IOException ioException = new IOException(e.getMessage());
|
||||
ioException.initCause(e);
|
||||
throw ioException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue an HTTP POST for the given URL, return the response
|
||||
*
|
||||
* @param url
|
||||
* @param data
|
||||
* url-encoded data
|
||||
* @throws IOException
|
||||
*/
|
||||
public synchronized String post(String url, String data) throws IOException {
|
||||
if(debug)
|
||||
Log.d("http-rest-client-post", url + " | " + data); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
try {
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
httpPost.setEntity(new StringEntity(data));
|
||||
HttpResponse response = getClient().execute(httpPost);
|
||||
|
||||
return processHttpResponse(response);
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
IOException ioException = new IOException(e.getMessage());
|
||||
ioException.initCause(e);
|
||||
throw ioException;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Exception displayed when a 500 error is received on an HTTP invocation
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class HttpUnavailableException extends IOException {
|
||||
|
||||
private static final long serialVersionUID = 5373340422464657279L;
|
||||
|
||||
public HttpUnavailableException() {
|
||||
super();
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "Sorry, our servers are experiencing some issues. Please try again later!"; //$NON-NLS-1$ // FIXME
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Notification Manager stub
|
||||
*
|
||||
* @author timsu
|
||||
*
|
||||
*/
|
||||
public interface NotificationManager {
|
||||
|
||||
public void cancel(int id);
|
||||
|
||||
public void cancelAll();
|
||||
|
||||
public void notify(int id, Notification notification);
|
||||
|
||||
/**
|
||||
* Instantiation of notification manager that passes through to
|
||||
* Android's notification manager
|
||||
*
|
||||
* @author timsu
|
||||
*
|
||||
*/
|
||||
public static class AndroidNotificationManager implements NotificationManager {
|
||||
private final android.app.NotificationManager nm;
|
||||
public AndroidNotificationManager(Context context) {
|
||||
nm = (android.app.NotificationManager)
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
public void cancel(int id) {
|
||||
nm.cancel(id);
|
||||
}
|
||||
|
||||
public void cancelAll() {
|
||||
nm.cancelAll();
|
||||
}
|
||||
|
||||
public void notify(int id, Notification notification) {
|
||||
nm.notify(id, notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package com.todoroo.andlib.service;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* RestClient stub invokes the HTML requests as desired
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public interface RestClient {
|
||||
public String get(String url) throws IOException;
|
||||
public String post(String url, String data) throws IOException;
|
||||
}
|
||||
@ -1,89 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.sql.SqlConstants.AND;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.EXISTS;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.LEFT_PARENTHESIS;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.NOT;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.OR;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.RIGHT_PARENTHESIS;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
||||
|
||||
public abstract class Criterion {
|
||||
protected final Operator operator;
|
||||
|
||||
Criterion(Operator operator) {
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
public static Criterion all = new Criterion(Operator.exists) {
|
||||
@Override
|
||||
protected void populate(StringBuilder sb) {
|
||||
sb.append(1);
|
||||
}
|
||||
};
|
||||
|
||||
public static Criterion none = new Criterion(Operator.exists) {
|
||||
@Override
|
||||
protected void populate(StringBuilder sb) {
|
||||
sb.append(0);
|
||||
}
|
||||
};
|
||||
|
||||
public static Criterion and(final Criterion criterion, final Criterion... criterions) {
|
||||
return new Criterion(Operator.and) {
|
||||
|
||||
@Override
|
||||
protected void populate(StringBuilder sb) {
|
||||
sb.append(criterion);
|
||||
for (Criterion c : criterions) {
|
||||
sb.append(SPACE).append(AND).append(SPACE).append(c);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Criterion or(final Criterion criterion, final Criterion... criterions) {
|
||||
return new Criterion(Operator.or) {
|
||||
|
||||
@Override
|
||||
protected void populate(StringBuilder sb) {
|
||||
sb.append(criterion);
|
||||
for (Criterion c : criterions) {
|
||||
sb.append(SPACE).append(OR).append(SPACE).append(c.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Criterion exists(final Query query) {
|
||||
return new Criterion(Operator.exists) {
|
||||
|
||||
@Override
|
||||
protected void populate(StringBuilder sb) {
|
||||
sb.append(EXISTS).append(SPACE).append(LEFT_PARENTHESIS).append(query).append(RIGHT_PARENTHESIS);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Criterion not(final Criterion criterion) {
|
||||
return new Criterion(Operator.not) {
|
||||
|
||||
@Override
|
||||
protected void populate(StringBuilder sb) {
|
||||
sb.append(NOT).append(SPACE);
|
||||
criterion.populate(sb);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract void populate(StringBuilder sb);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder(LEFT_PARENTHESIS);
|
||||
populate(builder);
|
||||
builder.append(RIGHT_PARENTHESIS);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.sql.SqlConstants.AS;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
||||
|
||||
public abstract class DBObject<T extends DBObject<?>> implements Cloneable {
|
||||
protected String alias;
|
||||
protected final String expression;
|
||||
|
||||
protected DBObject(String expression){
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public T as(String newAlias) {
|
||||
try {
|
||||
T clone = (T) clone();
|
||||
clone.alias = newAlias;
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasAlias() {
|
||||
return alias != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
DBObject<?> dbObject = (DBObject<?>) o;
|
||||
|
||||
if (alias != null ? !alias.equals(dbObject.alias) : dbObject.alias != null) return false;
|
||||
if (expression != null ? !expression.equals(dbObject.expression) : dbObject.expression != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = alias != null ? alias.hashCode() : 0;
|
||||
result = 31 * result + (expression != null ? expression.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
if (hasAlias()) {
|
||||
return alias;
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
public final String toStringInSelect() {
|
||||
StringBuilder sb = new StringBuilder(expression);
|
||||
if (hasAlias()) {
|
||||
sb.append(SPACE).append(AS).append(SPACE).append(alias);
|
||||
} else {
|
||||
int pos = expression.indexOf('.');
|
||||
if(pos > 0)
|
||||
sb.append(SPACE).append(AS).append(SPACE).append(expression.substring(pos + 1));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
public class EqCriterion extends UnaryCriterion {
|
||||
EqCriterion(Field field, Object value) {
|
||||
super(field, Operator.eq, value);
|
||||
}
|
||||
}
|
||||
@ -1,99 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.sql.SqlConstants.AND;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.BETWEEN;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.COMMA;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.LEFT_PARENTHESIS;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.RIGHT_PARENTHESIS;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
||||
|
||||
public class Field extends DBObject<Field> {
|
||||
|
||||
protected Field(String expression) {
|
||||
super(expression);
|
||||
}
|
||||
|
||||
public static Field field(String expression) {
|
||||
return new Field(expression);
|
||||
}
|
||||
|
||||
public Criterion eq(Object value) {
|
||||
if(value == null)
|
||||
return UnaryCriterion.isNull(this);
|
||||
return UnaryCriterion.eq(this, value);
|
||||
}
|
||||
|
||||
public Criterion neq(Object value) {
|
||||
if(value == null)
|
||||
return UnaryCriterion.isNotNull(this);
|
||||
return UnaryCriterion.neq(this, value);
|
||||
}
|
||||
|
||||
public Criterion gt(Object value) {
|
||||
return UnaryCriterion.gt(this, value);
|
||||
}
|
||||
|
||||
public Criterion lt(final Object value) {
|
||||
return UnaryCriterion.lt(this, value);
|
||||
}
|
||||
|
||||
public Criterion lte(final Object value) {
|
||||
return UnaryCriterion.lte(this, value);
|
||||
}
|
||||
|
||||
public Criterion isNull() {
|
||||
return UnaryCriterion.isNull(this);
|
||||
}
|
||||
|
||||
public Criterion isNotNull() {
|
||||
return UnaryCriterion.isNotNull(this);
|
||||
}
|
||||
|
||||
public Criterion between(final Object lower, final Object upper) {
|
||||
final Field field = this;
|
||||
return new Criterion(null) {
|
||||
|
||||
@Override
|
||||
protected void populate(StringBuilder sb) {
|
||||
sb.append(field).append(SPACE).append(BETWEEN).append(SPACE).append(lower).append(SPACE).append(AND)
|
||||
.append(SPACE).append(upper);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Criterion like(final String value) {
|
||||
return UnaryCriterion.like(this, value);
|
||||
}
|
||||
|
||||
public Criterion like(String value, String escape) {
|
||||
return UnaryCriterion.like(this, value, escape);
|
||||
}
|
||||
|
||||
|
||||
public <T> Criterion in(final T... value) {
|
||||
final Field field = this;
|
||||
return new Criterion(Operator.in) {
|
||||
|
||||
@Override
|
||||
protected void populate(StringBuilder sb) {
|
||||
sb.append(field).append(SPACE).append(Operator.in).append(SPACE).append(LEFT_PARENTHESIS).append(SPACE);
|
||||
for (T t : value) {
|
||||
sb.append(t.toString()).append(COMMA);
|
||||
}
|
||||
sb.deleteCharAt(sb.length() - 1).append(RIGHT_PARENTHESIS);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Criterion in(final Query query) {
|
||||
final Field field = this;
|
||||
return new Criterion(Operator.in) {
|
||||
|
||||
@Override
|
||||
protected void populate(StringBuilder sb) {
|
||||
sb.append(field).append(SPACE).append(Operator.in).append(SPACE).append(LEFT_PARENTHESIS).append(query)
|
||||
.append(RIGHT_PARENTHESIS);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public final class Functions {
|
||||
|
||||
public static String caseStatement(Criterion when, Object ifTrue, Object ifFalse) {
|
||||
return new StringBuilder("(CASE WHEN ").
|
||||
append(when.toString()).append(" THEN ").append(value(ifTrue)).
|
||||
append(" ELSE ").append(value(ifFalse)).append(" END)").toString();
|
||||
}
|
||||
|
||||
private static String value(Object value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
public static Field upper(Field title) {
|
||||
return new Field("UPPER(" + title.toString() + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SQL now (in milliseconds)
|
||||
*/
|
||||
public static Field now() {
|
||||
return new Field("(strftime('%s','now')*1000)");
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GroupBy {
|
||||
private List<Field> fields = new ArrayList<Field>();
|
||||
|
||||
public static GroupBy groupBy(Field field) {
|
||||
GroupBy groupBy = new GroupBy();
|
||||
groupBy.fields.add(field);
|
||||
return groupBy;
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.sql.SqlConstants.JOIN;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.ON;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
||||
|
||||
public class Join {
|
||||
private final SqlTable joinTable;
|
||||
private final JoinType joinType;
|
||||
private final Criterion[] criterions;
|
||||
|
||||
private Join(SqlTable table, JoinType joinType, Criterion... criterions) {
|
||||
joinTable = table;
|
||||
this.joinType = joinType;
|
||||
this.criterions = criterions;
|
||||
}
|
||||
|
||||
public static Join inner(SqlTable expression, Criterion... criterions) {
|
||||
return new Join(expression, JoinType.INNER, criterions);
|
||||
}
|
||||
|
||||
public static Join left(SqlTable table, Criterion... criterions) {
|
||||
return new Join(table, JoinType.LEFT, criterions);
|
||||
}
|
||||
|
||||
public static Join right(SqlTable table, Criterion... criterions) {
|
||||
return new Join(table, JoinType.RIGHT, criterions);
|
||||
}
|
||||
|
||||
public static Join out(SqlTable table, Criterion... criterions) {
|
||||
return new Join(table, JoinType.OUT, criterions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(joinType).append(SPACE).append(JOIN).append(SPACE).append(joinTable).append(SPACE).append(ON);
|
||||
for (Criterion criterion : criterions) {
|
||||
sb.append(SPACE).append(criterion);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
public enum JoinType {
|
||||
INNER, LEFT, RIGHT, OUT
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public final class Operator {
|
||||
|
||||
private final String operator;
|
||||
public static final Operator eq = new Operator("=");
|
||||
public static final Operator neq = new Operator("<>");
|
||||
public static final Operator isNull = new Operator("IS NULL");
|
||||
public static final Operator isNotNull = new Operator("IS NOT NULL");
|
||||
public static final Operator gt = new Operator(">");
|
||||
public static final Operator lt = new Operator("<");
|
||||
public static final Operator gte = new Operator(">=");
|
||||
public static final Operator lte = new Operator("<=");
|
||||
public static final Operator and = new Operator("AND");
|
||||
public static final Operator or = new Operator("OR");
|
||||
public static final Operator not = new Operator("NOT");
|
||||
public static final Operator exists = new Operator("EXISTS");
|
||||
public static final Operator like = new Operator("LIKE");
|
||||
public static final Operator in = new Operator("IN");
|
||||
|
||||
private static final Map<Operator, Operator> contraryRegistry = new HashMap<Operator, Operator>();
|
||||
|
||||
static {
|
||||
contraryRegistry.put(eq, neq);
|
||||
contraryRegistry.put(neq, eq);
|
||||
contraryRegistry.put(isNull, isNotNull);
|
||||
contraryRegistry.put(isNotNull, isNull);
|
||||
contraryRegistry.put(gt, lte);
|
||||
contraryRegistry.put(lte, gt);
|
||||
contraryRegistry.put(lt, gte);
|
||||
contraryRegistry.put(gte, lt);
|
||||
}
|
||||
|
||||
private Operator(String operator) {
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
public Operator getContrary() {
|
||||
if(!contraryRegistry.containsKey(this)){
|
||||
Operator opposite = new Operator(not.toString() + SPACE + this.toString());
|
||||
contraryRegistry.put(this, opposite);
|
||||
contraryRegistry.put(opposite, this);
|
||||
}
|
||||
return contraryRegistry.get(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.operator.toString();
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
||||
|
||||
public class Order {
|
||||
private final Object expression;
|
||||
private final OrderType orderType;
|
||||
|
||||
private Order(Object expression) {
|
||||
this(expression, OrderType.ASC);
|
||||
}
|
||||
|
||||
private Order(Object expression, OrderType orderType) {
|
||||
this.expression = expression;
|
||||
this.orderType = orderType;
|
||||
}
|
||||
|
||||
public static Order asc(Object expression) {
|
||||
return new Order(expression);
|
||||
}
|
||||
|
||||
public static Order desc(Object expression) {
|
||||
return new Order(expression, OrderType.DESC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return expression + SPACE + orderType;
|
||||
}
|
||||
|
||||
public Order reverse() {
|
||||
if(orderType == OrderType.ASC)
|
||||
return new Order(expression, OrderType.DESC);
|
||||
else
|
||||
return new Order(expression, OrderType.ASC);
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
public enum OrderType {
|
||||
DESC, ASC
|
||||
}
|
||||
@ -1,205 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.sql.SqlConstants.ALL;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.COMMA;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.FROM;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.GROUP_BY;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.LEFT_PARENTHESIS;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.LIMIT;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.ORDER_BY;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.RIGHT_PARENTHESIS;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.SELECT;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.WHERE;
|
||||
import static com.todoroo.andlib.sql.SqlTable.table;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.todoroo.andlib.data.Property;
|
||||
|
||||
public final class Query {
|
||||
|
||||
private SqlTable table;
|
||||
private String queryTemplate = null;
|
||||
private final ArrayList<Criterion> criterions = new ArrayList<Criterion>();
|
||||
private final ArrayList<Field> fields = new ArrayList<Field>();
|
||||
private final ArrayList<Join> joins = new ArrayList<Join>();
|
||||
private final ArrayList<Field> groupBies = new ArrayList<Field>();
|
||||
private final ArrayList<Order> orders = new ArrayList<Order>();
|
||||
private final ArrayList<Criterion> havings = new ArrayList<Criterion>();
|
||||
private int limits = -1;
|
||||
|
||||
private Query(Field... fields) {
|
||||
this.fields.addAll(asList(fields));
|
||||
}
|
||||
|
||||
public static Query select(Field... fields) {
|
||||
return new Query(fields);
|
||||
}
|
||||
|
||||
public Query from(SqlTable fromTable) {
|
||||
this.table = fromTable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Query join(Join... join) {
|
||||
joins.addAll(asList(join));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Query where(Criterion criterion) {
|
||||
criterions.add(criterion);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Query groupBy(Field... groupBy) {
|
||||
groupBies.addAll(asList(groupBy));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Query orderBy(Order... order) {
|
||||
orders.addAll(asList(order));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Query limit(int limit) {
|
||||
limits = limit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Query appendSelectFields(Property<?>... selectFields) {
|
||||
this.fields.addAll(asList(selectFields));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || !(o == null || getClass() != o.getClass()) && this.toString().equals(o.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
visitSelectClause(sql);
|
||||
visitFromClause(sql);
|
||||
|
||||
visitJoinClause(sql);
|
||||
if(queryTemplate == null) {
|
||||
visitWhereClause(sql);
|
||||
visitGroupByClause(sql);
|
||||
visitOrderByClause(sql);
|
||||
visitLimitClause(sql);
|
||||
} else {
|
||||
if(groupBies.size() > 0 || orders.size() > 0 ||
|
||||
havings.size() > 0)
|
||||
throw new IllegalStateException("Can't have extras AND query template"); //$NON-NLS-1$
|
||||
sql.append(queryTemplate);
|
||||
}
|
||||
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
private void visitOrderByClause(StringBuilder sql) {
|
||||
if (orders.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sql.append(ORDER_BY);
|
||||
for (Order order : orders) {
|
||||
sql.append(SPACE).append(order).append(COMMA);
|
||||
}
|
||||
sql.deleteCharAt(sql.length() - 1).append(SPACE);
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private void visitGroupByClause(StringBuilder sql) {
|
||||
if (groupBies.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sql.append(GROUP_BY);
|
||||
for (Field groupBy : groupBies) {
|
||||
sql.append(SPACE).append(groupBy).append(COMMA);
|
||||
}
|
||||
sql.deleteCharAt(sql.length() - 1).append(SPACE);
|
||||
if (havings.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sql.append("HAVING");
|
||||
for (Criterion havingCriterion : havings) {
|
||||
sql.append(SPACE).append(havingCriterion).append(COMMA);
|
||||
}
|
||||
sql.deleteCharAt(sql.length() - 1).append(SPACE);
|
||||
}
|
||||
|
||||
private void visitWhereClause(StringBuilder sql) {
|
||||
if (criterions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sql.append(WHERE);
|
||||
for (Criterion criterion : criterions) {
|
||||
sql.append(SPACE).append(criterion).append(SPACE);
|
||||
}
|
||||
}
|
||||
|
||||
private void visitJoinClause(StringBuilder sql) {
|
||||
for (Join join : joins) {
|
||||
sql.append(join).append(SPACE);
|
||||
}
|
||||
}
|
||||
|
||||
private void visitFromClause(StringBuilder sql) {
|
||||
if (table == null) {
|
||||
return;
|
||||
}
|
||||
sql.append(FROM).append(SPACE).append(table).append(SPACE);
|
||||
}
|
||||
|
||||
private void visitSelectClause(StringBuilder sql) {
|
||||
sql.append(SELECT).append(SPACE);
|
||||
if (fields.isEmpty()) {
|
||||
sql.append(ALL).append(SPACE);
|
||||
return;
|
||||
}
|
||||
for (Field field : fields) {
|
||||
sql.append(field.toStringInSelect()).append(COMMA);
|
||||
}
|
||||
sql.deleteCharAt(sql.length() - 1).append(SPACE);
|
||||
}
|
||||
|
||||
private void visitLimitClause(StringBuilder sql) {
|
||||
if(limits > -1)
|
||||
sql.append(LIMIT).append(SPACE).append(limits).append(SPACE);
|
||||
}
|
||||
|
||||
public SqlTable as(String alias) {
|
||||
return table(LEFT_PARENTHESIS + this.toString() + RIGHT_PARENTHESIS).as(alias);
|
||||
}
|
||||
|
||||
public Query having(Criterion criterion) {
|
||||
this.havings.add(criterion);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of fields returned by this query
|
||||
* @return
|
||||
*/
|
||||
public Property<?>[] getFields() {
|
||||
return fields.toArray(new Property<?>[fields.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the SQL query template (comes after the "from")
|
||||
* @param sqlQuery
|
||||
* @return
|
||||
*/
|
||||
public Query withQueryTemplate(String template) {
|
||||
queryTemplate = template;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@ -1,117 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.sql.SqlConstants.COMMA;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.GROUP_BY;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.LIMIT;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.ORDER_BY;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
||||
import static com.todoroo.andlib.sql.SqlConstants.WHERE;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Query Template returns a bunch of criteria that allows a query to be
|
||||
* constructed
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public final class QueryTemplate {
|
||||
|
||||
private final ArrayList<Criterion> criterions = new ArrayList<Criterion>();
|
||||
private final ArrayList<Join> joins = new ArrayList<Join>();
|
||||
private final ArrayList<Field> groupBies = new ArrayList<Field>();
|
||||
private final ArrayList<Order> orders = new ArrayList<Order>();
|
||||
private final ArrayList<Criterion> havings = new ArrayList<Criterion>();
|
||||
private Integer limit = null;
|
||||
|
||||
public QueryTemplate join(Join... join) {
|
||||
joins.addAll(asList(join));
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryTemplate where(Criterion criterion) {
|
||||
criterions.add(criterion);
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryTemplate groupBy(Field... groupBy) {
|
||||
groupBies.addAll(asList(groupBy));
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryTemplate orderBy(Order... order) {
|
||||
orders.addAll(asList(order));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
visitJoinClause(sql);
|
||||
visitWhereClause(sql);
|
||||
visitGroupByClause(sql);
|
||||
visitOrderByClause(sql);
|
||||
if(limit != null)
|
||||
sql.append(LIMIT).append(SPACE).append(limit);
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
private void visitOrderByClause(StringBuilder sql) {
|
||||
if (orders.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sql.append(ORDER_BY);
|
||||
for (Order order : orders) {
|
||||
sql.append(SPACE).append(order).append(COMMA);
|
||||
}
|
||||
sql.deleteCharAt(sql.length() - 1).append(SPACE);
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private void visitGroupByClause(StringBuilder sql) {
|
||||
if (groupBies.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sql.append(GROUP_BY);
|
||||
for (Field groupBy : groupBies) {
|
||||
sql.append(SPACE).append(groupBy).append(COMMA);
|
||||
}
|
||||
sql.deleteCharAt(sql.length() - 1).append(SPACE);
|
||||
if (havings.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sql.append("HAVING");
|
||||
for (Criterion havingCriterion : havings) {
|
||||
sql.append(SPACE).append(havingCriterion).append(COMMA);
|
||||
}
|
||||
sql.deleteCharAt(sql.length() - 1).append(SPACE);
|
||||
}
|
||||
|
||||
private void visitWhereClause(StringBuilder sql) {
|
||||
if (criterions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
sql.append(WHERE);
|
||||
for (Criterion criterion : criterions) {
|
||||
sql.append(SPACE).append(criterion).append(SPACE);
|
||||
}
|
||||
}
|
||||
|
||||
private void visitJoinClause(StringBuilder sql) {
|
||||
for (Join join : joins) {
|
||||
sql.append(join).append(SPACE);
|
||||
}
|
||||
}
|
||||
|
||||
public QueryTemplate having(Criterion criterion) {
|
||||
this.havings.add(criterion);
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryTemplate limit(int limitValue) {
|
||||
this.limit = limitValue;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public final class SqlConstants {
|
||||
static final String SELECT = "SELECT";
|
||||
static final String SPACE = " ";
|
||||
static final String AS = "AS";
|
||||
static final String COMMA = ",";
|
||||
static final String FROM = "FROM";
|
||||
static final String ON = "ON";
|
||||
static final String JOIN = "JOIN";
|
||||
static final String ALL = "*";
|
||||
static final String LEFT_PARENTHESIS = "(";
|
||||
static final String RIGHT_PARENTHESIS = ")";
|
||||
static final String AND = "AND";
|
||||
static final String BETWEEN = "BETWEEN";
|
||||
static final String LIKE = "LIKE";
|
||||
static final String OR = "OR";
|
||||
static final String ORDER_BY = "ORDER BY";
|
||||
static final String GROUP_BY = "GROUP BY";
|
||||
static final String WHERE = "WHERE";
|
||||
public static final String EXISTS = "EXISTS";
|
||||
public static final String NOT = "NOT";
|
||||
public static final String LIMIT = "LIMIT";
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
public class SqlTable extends DBObject<SqlTable> {
|
||||
|
||||
protected SqlTable(String expression) {
|
||||
super(expression);
|
||||
}
|
||||
|
||||
public static SqlTable table(String table) {
|
||||
return new SqlTable(table);
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
protected String fieldExpression(String fieldName) {
|
||||
if (hasAlias()) {
|
||||
return alias + "." + fieldName;
|
||||
}
|
||||
return expression+"."+fieldName;
|
||||
}
|
||||
}
|
||||
@ -1,111 +0,0 @@
|
||||
package com.todoroo.andlib.sql;
|
||||
|
||||
import static com.todoroo.andlib.sql.SqlConstants.SPACE;
|
||||
|
||||
public class UnaryCriterion extends Criterion {
|
||||
protected final Field expression;
|
||||
protected final Object value;
|
||||
|
||||
UnaryCriterion(Field expression, Operator operator, Object value) {
|
||||
super(operator);
|
||||
this.expression = expression;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populate(StringBuilder sb) {
|
||||
beforePopulateOperator(sb);
|
||||
populateOperator(sb);
|
||||
afterPopulateOperator(sb);
|
||||
}
|
||||
|
||||
public static Criterion eq(Field expression, Object value) {
|
||||
return new UnaryCriterion(expression, Operator.eq, value);
|
||||
}
|
||||
|
||||
protected void beforePopulateOperator(StringBuilder sb) {
|
||||
sb.append(expression);
|
||||
}
|
||||
|
||||
protected void populateOperator(StringBuilder sb) {
|
||||
sb.append(operator);
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
protected void afterPopulateOperator(StringBuilder sb) {
|
||||
if(value == null)
|
||||
return;
|
||||
else if(value instanceof String)
|
||||
sb.append("'").append(sanitize((String) value)).append("'");
|
||||
else
|
||||
sb.append(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the given input for SQL
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public static String sanitize(String input) {
|
||||
return input.replace("'", "''");
|
||||
}
|
||||
|
||||
public static Criterion neq(Field field, Object value) {
|
||||
return new UnaryCriterion(field, Operator.neq, value);
|
||||
}
|
||||
|
||||
public static Criterion gt(Field field, Object value) {
|
||||
return new UnaryCriterion(field, Operator.gt, value);
|
||||
}
|
||||
|
||||
public static Criterion lt(Field field, Object value) {
|
||||
return new UnaryCriterion(field, Operator.lt, value);
|
||||
}
|
||||
|
||||
public static Criterion lte(Field field, Object value) {
|
||||
return new UnaryCriterion(field, Operator.lte, value);
|
||||
}
|
||||
|
||||
public static Criterion isNull(Field field) {
|
||||
return new UnaryCriterion(field, Operator.isNull, null) {
|
||||
@Override
|
||||
protected void populateOperator(StringBuilder sb) {
|
||||
sb.append(SPACE).append(operator);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Criterion isNotNull(Field field) {
|
||||
return new UnaryCriterion(field, Operator.isNotNull, null) {
|
||||
@Override
|
||||
protected void populateOperator(StringBuilder sb) {
|
||||
sb.append(SPACE).append(operator);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Criterion like(Field field, String value) {
|
||||
return new UnaryCriterion(field, Operator.like, value) {
|
||||
@Override
|
||||
protected void populateOperator(StringBuilder sb) {
|
||||
sb.append(SPACE).append(operator).append(SPACE);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Criterion like(Field field, String value, final String escape) {
|
||||
return new UnaryCriterion(field, Operator.like, value) {
|
||||
@Override
|
||||
protected void populateOperator(StringBuilder sb) {
|
||||
sb.append(SPACE).append(operator).append(SPACE);
|
||||
}
|
||||
@SuppressWarnings("nls")
|
||||
@Override
|
||||
protected void afterPopulateOperator(StringBuilder sb) {
|
||||
super.afterPopulateOperator(sb);
|
||||
sb.append(SPACE).append("ESCAPE").append(" '").append(sanitize(escape)).append("'");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,385 +0,0 @@
|
||||
package com.todoroo.andlib.utility;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.NetworkInfo.State;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.service.ExceptionService;
|
||||
|
||||
/**
|
||||
* Android Utility Classes
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class AndroidUtilities {
|
||||
|
||||
public static final String SEPARATOR_ESCAPE = "!PIPE!"; //$NON-NLS-1$
|
||||
public static final String SERIALIZATION_SEPARATOR = "|"; //$NON-NLS-1$
|
||||
|
||||
// --- utility methods
|
||||
|
||||
private static class ExceptionHelper {
|
||||
@Autowired
|
||||
public ExceptionService exceptionService;
|
||||
|
||||
public ExceptionHelper() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
}
|
||||
|
||||
/** Suppress virtual keyboard until user's first tap */
|
||||
public static void suppressVirtualKeyboard(final TextView editor) {
|
||||
final int inputType = editor.getInputType();
|
||||
editor.setInputType(InputType.TYPE_NULL);
|
||||
editor.setOnTouchListener(new OnTouchListener() {
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
editor.setInputType(inputType);
|
||||
editor.setOnTouchListener(null);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if we're connected to the internet
|
||||
*/
|
||||
public static boolean isConnected(Context context) {
|
||||
ConnectivityManager manager = (ConnectivityManager)
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo info = manager.getActiveNetworkInfo();
|
||||
if (info == null)
|
||||
return false;
|
||||
if (info.getState() != State.CONNECTED)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Fetch the image specified by the given url */
|
||||
public static Bitmap fetchImage(URL url) throws IOException {
|
||||
InputStream is = null;
|
||||
try {
|
||||
URLConnection conn = url.openConnection();
|
||||
conn.connect();
|
||||
is = conn.getInputStream();
|
||||
BufferedInputStream bis = new BufferedInputStream(is, 16384);
|
||||
try {
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(bis);
|
||||
return bitmap;
|
||||
} finally {
|
||||
bis.close();
|
||||
}
|
||||
} finally {
|
||||
if(is != null)
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the given intent, handling security exceptions if they arise
|
||||
*
|
||||
* @param context
|
||||
* @param intent
|
||||
* @param request request code. if negative, no request.
|
||||
*/
|
||||
public static void startExternalIntent(Context context, Intent intent, int request) {
|
||||
try {
|
||||
if(request > -1 && context instanceof Activity)
|
||||
((Activity)context).startActivityForResult(intent, request);
|
||||
else
|
||||
context.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
ExceptionHelper helper = new ExceptionHelper();
|
||||
helper.exceptionService.displayAndReportError(context,
|
||||
"start-external-intent-" + intent.toString(), //$NON-NLS-1$
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the given intent, handling security exceptions if they arise
|
||||
*
|
||||
* @param activity
|
||||
* @param intent
|
||||
* @param requestCode
|
||||
*/
|
||||
public static void startExternalIntentForResult(
|
||||
Activity activity, Intent intent, int requestCode) {
|
||||
try {
|
||||
activity.startActivityForResult(intent, requestCode);
|
||||
} catch (SecurityException e) {
|
||||
ExceptionHelper helper = new ExceptionHelper();
|
||||
helper.exceptionService.displayAndReportError(activity,
|
||||
"start-external-intent-" + intent.toString(), //$NON-NLS-1$
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an arbitrary object into a {@link ContentValues}
|
||||
* @param target
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
public static void putInto(ContentValues target, String key, Object value) {
|
||||
if(value instanceof String)
|
||||
target.put(key, (String) value);
|
||||
else if(value instanceof Long)
|
||||
target.put(key, (Long) value);
|
||||
else if(value instanceof Integer)
|
||||
target.put(key, (Integer) value);
|
||||
else if(value instanceof Double)
|
||||
target.put(key, (Double) value);
|
||||
else
|
||||
throw new UnsupportedOperationException("Could not handle type " + //$NON-NLS-1$
|
||||
value.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Rips apart a content value into two string arrays, keys and value
|
||||
*/
|
||||
public static String[][] contentValuesToStringArrays(ContentValues source) {
|
||||
String[][] result = new String[2][source.size()];
|
||||
int i = 0;
|
||||
for(Entry<String, Object> entry : source.valueSet()) {
|
||||
result[0][i] = entry.getKey();
|
||||
result[1][i++] = entry.getValue().toString();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return index of value in array
|
||||
* @param array array to search
|
||||
* @param value value to look for
|
||||
* @return
|
||||
*/
|
||||
public static <TYPE> int indexOf(TYPE[] array, TYPE value) {
|
||||
for(int i = 0; i < array.length; i++)
|
||||
if(array[i].equals(value))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a content value into a string
|
||||
*/
|
||||
public static String contentValuesToSerializedString(ContentValues source) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for(Entry<String, Object> entry : source.valueSet()) {
|
||||
result.append(entry.getKey().replace(SERIALIZATION_SEPARATOR, SEPARATOR_ESCAPE)).append(
|
||||
SERIALIZATION_SEPARATOR);
|
||||
Object value = entry.getValue();
|
||||
if(value instanceof Integer)
|
||||
result.append('i').append(value);
|
||||
else if(value instanceof Double)
|
||||
result.append('d').append(value);
|
||||
else if(value instanceof Long)
|
||||
result.append('l').append(value);
|
||||
else if(value instanceof String)
|
||||
result.append('s').append(value.toString());
|
||||
else
|
||||
throw new UnsupportedOperationException(value.getClass().toString());
|
||||
result.append(SERIALIZATION_SEPARATOR);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn ContentValues into a string
|
||||
* @param string
|
||||
* @return
|
||||
*/
|
||||
public static ContentValues contentValuesFromSerializedString(String string) {
|
||||
if(string == null)
|
||||
return new ContentValues();
|
||||
|
||||
String[] pairs = string.split("\\" + SERIALIZATION_SEPARATOR); //$NON-NLS-1$
|
||||
ContentValues result = new ContentValues();
|
||||
for(int i = 0; i < pairs.length; i += 2) {
|
||||
String key = pairs[i].replaceAll(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR);
|
||||
String value = pairs[i+1].substring(1);
|
||||
switch(pairs[i+1].charAt(0)) {
|
||||
case 'i':
|
||||
result.put(key, Integer.parseInt(value));
|
||||
break;
|
||||
case 'd':
|
||||
result.put(key, Double.parseDouble(value));
|
||||
break;
|
||||
case 'l':
|
||||
result.put(key, Long.parseLong(value));
|
||||
break;
|
||||
case 's':
|
||||
result.put(key, value.replace(SEPARATOR_ESCAPE, SERIALIZATION_SEPARATOR));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn ContentValues into a string
|
||||
* @param string
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public static ContentValues contentValuesFromString(String string) {
|
||||
if(string == null)
|
||||
return null;
|
||||
|
||||
String[] pairs = string.split("=");
|
||||
ContentValues result = new ContentValues();
|
||||
String key = null;
|
||||
for(int i = 0; i < pairs.length; i++) {
|
||||
String newKey = null;
|
||||
int lastSpace = pairs[i].lastIndexOf(' ');
|
||||
if(lastSpace != -1) {
|
||||
newKey = pairs[i].substring(lastSpace + 1);
|
||||
pairs[i] = pairs[i].substring(0, lastSpace);
|
||||
} else {
|
||||
newKey = pairs[i];
|
||||
}
|
||||
if(key != null)
|
||||
result.put(key.trim(), pairs[i].trim());
|
||||
key = newKey;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a and b or null or a.equals(b)
|
||||
* @param a
|
||||
* @param b
|
||||
* @return
|
||||
*/
|
||||
public static boolean equals(Object a, Object b) {
|
||||
if(a == null && b == null)
|
||||
return true;
|
||||
if(a == null)
|
||||
return false;
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file from one place to another
|
||||
*
|
||||
* @param in
|
||||
* @param out
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void copyFile(File in, File out) throws Exception {
|
||||
FileInputStream fis = new FileInputStream(in);
|
||||
FileOutputStream fos = new FileOutputStream(out);
|
||||
try {
|
||||
byte[] buf = new byte[1024];
|
||||
int i = 0;
|
||||
while ((i = fis.read(buf)) != -1) {
|
||||
fos.write(buf, 0, i);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
} finally {
|
||||
fis.close();
|
||||
fos.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a child view of a certain type
|
||||
* @param view
|
||||
* @param type
|
||||
* @return first view (by DFS) if found, or null if none
|
||||
*/
|
||||
public static <TYPE> TYPE findViewByType(View view, Class<TYPE> type) {
|
||||
if(view == null)
|
||||
return null;
|
||||
if(type.isInstance(view))
|
||||
return (TYPE) view;
|
||||
if(view instanceof ViewGroup) {
|
||||
ViewGroup group = (ViewGroup) view;
|
||||
for(int i = 0; i < group.getChildCount(); i++) {
|
||||
TYPE v = findViewByType(group.getChildAt(i), type);
|
||||
if(v != null)
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Android SDK version as an integer. Works on all versions
|
||||
*/
|
||||
public static int getSdkVersion() {
|
||||
return Integer.parseInt(android.os.Build.VERSION.SDK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy databases to a given folder. Useful for debugging
|
||||
* @param folder
|
||||
*/
|
||||
public static void copyDatabases(Context context, String folder) {
|
||||
File folderFile = new File(folder);
|
||||
if(!folderFile.exists())
|
||||
folderFile.mkdir();
|
||||
for(String db : context.databaseList()) {
|
||||
File dbFile = context.getDatabasePath(db);
|
||||
try {
|
||||
copyFile(dbFile, new File(folderFile.getAbsolutePath() +
|
||||
File.separator + db));
|
||||
} catch (Exception e) {
|
||||
Log.e("ERROR", "ERROR COPYING DB " + db, e); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort files by date so the newest file is on top
|
||||
* @param files
|
||||
*/
|
||||
public static void sortFilesByDateDesc(File[] files) {
|
||||
Arrays.sort(files, new Comparator<File>() {
|
||||
public int compare(File o1, File o2) {
|
||||
return Long.valueOf(o2.lastModified()).compareTo(Long.valueOf(o1.lastModified()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep, ignoring interruption
|
||||
* @param l
|
||||
*/
|
||||
public static void sleepDeep(long l) {
|
||||
try {
|
||||
Thread.sleep(l);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,335 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Todoroo Inc
|
||||
* All Rights Reserved
|
||||
* http://www.todoroo.com
|
||||
*/
|
||||
package com.todoroo.andlib.utility;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
|
||||
|
||||
public class DateUtilities {
|
||||
|
||||
@Autowired
|
||||
public Integer yearsResource;
|
||||
|
||||
@Autowired
|
||||
public Integer monthsResource;
|
||||
|
||||
@Autowired
|
||||
public Integer weeksResource;
|
||||
|
||||
@Autowired
|
||||
public Integer daysResource;
|
||||
|
||||
@Autowired
|
||||
public Integer hoursResource;
|
||||
|
||||
@Autowired
|
||||
public Integer minutesResource;
|
||||
|
||||
@Autowired
|
||||
public Integer secondsResource;
|
||||
|
||||
@Autowired
|
||||
public Integer daysAbbrevResource;
|
||||
|
||||
@Autowired
|
||||
public Integer hoursAbbrevResource;
|
||||
|
||||
@Autowired
|
||||
public Integer minutesAbbrevResource;
|
||||
|
||||
@Autowired
|
||||
public Integer secondsAbbrevResource;
|
||||
|
||||
public DateUtilities() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
* ============================================================ long time
|
||||
* ====================================================================== */
|
||||
|
||||
/** Convert unixtime into date */
|
||||
public static final Date unixtimeToDate(long millis) {
|
||||
if(millis == 0)
|
||||
return null;
|
||||
return new Date(millis);
|
||||
}
|
||||
|
||||
/** Convert date into unixtime */
|
||||
public static final long dateToUnixtime(Date date) {
|
||||
if(date == null)
|
||||
return 0;
|
||||
return date.getTime();
|
||||
}
|
||||
|
||||
/** Returns unixtime for current time */
|
||||
public static final long now() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/** Returns unixtime one month from now */
|
||||
public static final long oneMonthFromNow() {
|
||||
Date date = new Date();
|
||||
date.setMonth(date.getMonth() + 1);
|
||||
return date.getTime();
|
||||
}
|
||||
|
||||
/** Represents a single hour */
|
||||
public static long ONE_HOUR = 3600000L;
|
||||
|
||||
/** Represents a single day */
|
||||
public static long ONE_DAY = 24 * ONE_HOUR;
|
||||
|
||||
/** Represents a single week */
|
||||
public static long ONE_WEEK = 7 * ONE_DAY;
|
||||
|
||||
/* ======================================================================
|
||||
* =========================================================== formatters
|
||||
* ====================================================================== */
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public static boolean is24HourFormat(Context context) {
|
||||
String value = android.provider.Settings.System.getString(context.getContentResolver(),
|
||||
android.provider.Settings.System.TIME_12_24);
|
||||
boolean b24 = !(value == null || value.equals("12"));
|
||||
return b24;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context android context
|
||||
* @param date time to format
|
||||
* @return time, with hours and minutes
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public static String getTimeString(Context context, Date date) {
|
||||
String value;
|
||||
if (is24HourFormat(context)) {
|
||||
value = "H:mm";
|
||||
} else {
|
||||
value = "h:mm a";
|
||||
}
|
||||
return new SimpleDateFormat(value).format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context android context
|
||||
* @param date date to format
|
||||
* @return date, with month, day, and year
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public static String getDateString(Context context, Date date) {
|
||||
String month = "'" + DateUtils.getMonthString(date.getMonth() +
|
||||
Calendar.JANUARY, DateUtils.LENGTH_MEDIUM) + "'";
|
||||
String value;
|
||||
// united states, you are special
|
||||
if (Locale.US.equals(Locale.getDefault())
|
||||
|| Locale.CANADA.equals(Locale.getDefault()))
|
||||
value = month + " d yyyy";
|
||||
else
|
||||
value = "d " + month + " yyyy";
|
||||
return new SimpleDateFormat(value).format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return date format as getDateFormat with weekday
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public static String getDateStringWithWeekday(Context context, Date date) {
|
||||
String weekday = DateUtils.getDayOfWeekString(date.getDay() + Calendar.SUNDAY,
|
||||
DateUtils.LENGTH_LONG);
|
||||
return weekday + ", " + getDateString(context, date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return date format as getDateFormat with weekday
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public static String getDateStringWithTimeAndWeekday(Context context, Date date) {
|
||||
return getDateStringWithWeekday(context, date) + " " + getTimeString(context, date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return date with time at the end
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public static String getDateStringWithTime(Context context, Date date) {
|
||||
return getDateString(context, date) + " " + getTimeString(context, date);
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
* ============================================================= duration
|
||||
* ====================================================================== */
|
||||
|
||||
/**
|
||||
* Convenience method for dropping the preposition argument.
|
||||
* @param duration in millis
|
||||
* @param unitsToShow number of units to show (i.e. if 2, then 5 hours
|
||||
* 3 minutes 2 seconds is truncated to 5 hours 3 minutes)
|
||||
*/
|
||||
public String getDurationString(long duration, int unitsToShow) {
|
||||
return getDurationString(duration, unitsToShow, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a time into the format: 5 days, 3 hours, 2 minutes
|
||||
*
|
||||
* @param duration in millis
|
||||
* @param unitsToShow number of units to show (i.e. if 2, then 5 hours
|
||||
* 3 minutes 2 seconds is truncated to 5 hours 3 minutes)
|
||||
* @param withPreposition whether there is a preceding preposition
|
||||
* @return
|
||||
*/
|
||||
public String getDurationString(long duration, int unitsToShow, boolean withPreposition) {
|
||||
Resources r = ContextManager.getContext().getResources();
|
||||
short unitsDisplayed = 0;
|
||||
duration = Math.abs(duration);
|
||||
|
||||
if(duration == 0)
|
||||
return r.getQuantityString(secondsResource, 0, 0);
|
||||
|
||||
Date now = new Date(80, 1, 1);
|
||||
Date then = new Date(now.getTime() + duration);
|
||||
|
||||
int[] values = new int[] {
|
||||
then.getYear() - now.getYear(),
|
||||
then.getMonth() - now.getMonth(),
|
||||
(then.getDate() - now.getDate())/7,
|
||||
(then.getDate() - now.getDate()) - (then.getDate() - now.getDate())/7*7,
|
||||
then.getHours() - now.getHours(),
|
||||
then.getMinutes() - now.getMinutes(),
|
||||
then.getSeconds() - now.getSeconds(),
|
||||
};
|
||||
int[] maxValues = new int[] {
|
||||
Integer.MAX_VALUE,
|
||||
12,
|
||||
5,
|
||||
7,
|
||||
24,
|
||||
60,
|
||||
60
|
||||
};
|
||||
|
||||
// perform rounding (this is definitely magic... trust the unit tests)
|
||||
int cursor = 0;
|
||||
while(values[cursor] == 0 && ++cursor < values.length)
|
||||
;
|
||||
int postCursor = cursor + unitsToShow;
|
||||
for(int i = values.length - 1; i >= postCursor; i--) {
|
||||
if(values[i] >= maxValues[i]/2) {
|
||||
values[i-1]++;
|
||||
}
|
||||
}
|
||||
for(int i = Math.min(values.length, postCursor) - 1; i >= 1; i--) {
|
||||
if(values[i] == maxValues[i]) {
|
||||
values[i-1]++;
|
||||
for(int j = i; j < values.length; j++)
|
||||
values[j] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
unitsDisplayed = displayUnits(r, yearsResource, unitsToShow, values[0],
|
||||
unitsDisplayed, result);
|
||||
unitsDisplayed = displayUnits(r, monthsResource, unitsToShow, values[1],
|
||||
unitsDisplayed, result);
|
||||
unitsDisplayed = displayUnits(r, weeksResource, unitsToShow, values[2],
|
||||
unitsDisplayed, result);
|
||||
unitsDisplayed = displayUnits(r, daysResource, unitsToShow, values[3],
|
||||
unitsDisplayed, result);
|
||||
unitsDisplayed = displayUnits(r, hoursResource, unitsToShow, values[4],
|
||||
unitsDisplayed, result);
|
||||
unitsDisplayed = displayUnits(r, minutesResource, unitsToShow, values[5],
|
||||
unitsDisplayed, result);
|
||||
unitsDisplayed = displayUnits(r, secondsResource, unitsToShow, values[6],
|
||||
unitsDisplayed, result);
|
||||
|
||||
return result.toString().trim();
|
||||
}
|
||||
|
||||
/** Display units, rounding up if necessary. Returns units to show */
|
||||
private short displayUnits(Resources r, int resource, int unitsToShow, int value,
|
||||
short unitsDisplayed, StringBuilder result) {
|
||||
if(unitsDisplayed < unitsToShow && value > 0) {
|
||||
result.append(r.getQuantityString(resource, value, value)).
|
||||
append(' ');
|
||||
unitsDisplayed++;
|
||||
}
|
||||
return unitsDisplayed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a time into the format: 5 days, 3 hrs, 2 min
|
||||
*
|
||||
* @param r Resources to get strings from
|
||||
* @param timeInSeconds
|
||||
* @param unitsToShow number of units to show (i.e. if 2, then 5 hours
|
||||
* 3 minutes 2 seconds is truncated to 5 hours 3 minutes)
|
||||
* @return
|
||||
*/
|
||||
public String getAbbreviatedDurationString(Resources r, int timeInSeconds,
|
||||
int unitsToShow) {
|
||||
short days, hours, minutes, seconds;
|
||||
short unitsDisplayed = 0;
|
||||
timeInSeconds = Math.abs(timeInSeconds);
|
||||
|
||||
if(timeInSeconds == 0)
|
||||
return r.getQuantityString(secondsAbbrevResource, 0, 0);
|
||||
|
||||
days = (short)(timeInSeconds / 24 / 3600);
|
||||
timeInSeconds -= days*24*3600;
|
||||
hours = (short)(timeInSeconds / 3600);
|
||||
timeInSeconds -= hours * 3600;
|
||||
minutes = (short)(timeInSeconds / 60);
|
||||
timeInSeconds -= minutes * 60;
|
||||
seconds = (short)timeInSeconds;
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
if(days > 0) {
|
||||
// round up if needed
|
||||
if(unitsDisplayed == unitsToShow && hours >= 12)
|
||||
days++;
|
||||
result.append(r.getQuantityString(daysAbbrevResource, days, days)).
|
||||
append(' ');
|
||||
unitsDisplayed++;
|
||||
}
|
||||
if(unitsDisplayed < unitsToShow && hours > 0) {
|
||||
// round up if needed
|
||||
if(unitsDisplayed == unitsToShow && minutes >= 30)
|
||||
days++;
|
||||
result.append(r.getQuantityString(hoursAbbrevResource, hours,
|
||||
hours)).
|
||||
append(' ');
|
||||
unitsDisplayed++;
|
||||
}
|
||||
if(unitsDisplayed < unitsToShow && minutes > 0) {
|
||||
// round up if needed
|
||||
if(unitsDisplayed == unitsToShow && seconds >= 30)
|
||||
days++;
|
||||
result.append(r.getQuantityString(minutesAbbrevResource, minutes,
|
||||
minutes)).append(' ');
|
||||
unitsDisplayed++;
|
||||
}
|
||||
if(unitsDisplayed < unitsToShow && seconds > 0) {
|
||||
result.append(r.getQuantityString(secondsAbbrevResource, seconds,
|
||||
seconds)).append(' ');
|
||||
}
|
||||
|
||||
return result.toString().trim();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,188 +0,0 @@
|
||||
package com.todoroo.andlib.utility;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.View;
|
||||
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
|
||||
public class DialogUtilities {
|
||||
|
||||
@Autowired
|
||||
public Integer informationDialogTitleResource;
|
||||
|
||||
@Autowired
|
||||
public Integer confirmDialogTitleResource;
|
||||
|
||||
public DialogUtilities() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a dialog box with a EditText and an ok / cancel
|
||||
*
|
||||
* @param activity
|
||||
* @param text
|
||||
* @param okListener
|
||||
*/
|
||||
public void viewDialog(final Activity activity, final String text,
|
||||
final View view, final DialogInterface.OnClickListener okListener,
|
||||
final DialogInterface.OnClickListener cancelListener) {
|
||||
if(activity.isFinishing())
|
||||
return;
|
||||
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(confirmDialogTitleResource)
|
||||
.setMessage(text)
|
||||
.setView(view)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(android.R.string.ok, okListener)
|
||||
.setNegativeButton(android.R.string.cancel, cancelListener)
|
||||
.show().setOwnerActivity(activity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a dialog box with an OK button
|
||||
*
|
||||
* @param activity
|
||||
* @param text
|
||||
* @param okListener
|
||||
*/
|
||||
public void okDialog(final Activity activity, final String text,
|
||||
final DialogInterface.OnClickListener okListener) {
|
||||
if(activity.isFinishing())
|
||||
return;
|
||||
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(informationDialogTitleResource)
|
||||
.setMessage(text)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(android.R.string.ok, okListener)
|
||||
.show().setOwnerActivity(activity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a dialog box with an OK button
|
||||
*
|
||||
* @param activity
|
||||
* @param text
|
||||
* @param okListener
|
||||
*/
|
||||
public void okDialog(final Activity activity, final int icon, final CharSequence text,
|
||||
final DialogInterface.OnClickListener okListener) {
|
||||
if(activity.isFinishing())
|
||||
return;
|
||||
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(informationDialogTitleResource)
|
||||
.setMessage(text)
|
||||
.setIcon(icon)
|
||||
.setPositiveButton(android.R.string.ok, okListener)
|
||||
.show().setOwnerActivity(activity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a dialog box with OK and Cancel buttons and custom title
|
||||
*
|
||||
* @param activity
|
||||
* @param title
|
||||
* @param text
|
||||
* @param okListener
|
||||
* @param cancelListener
|
||||
*/
|
||||
public void okCancelDialog(final Activity activity, final String title,
|
||||
final String text, final DialogInterface.OnClickListener okListener,
|
||||
final DialogInterface.OnClickListener cancelListener) {
|
||||
if(activity.isFinishing())
|
||||
return;
|
||||
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(title)
|
||||
.setMessage(text)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(android.R.string.ok, okListener)
|
||||
.setNegativeButton(android.R.string.cancel, cancelListener)
|
||||
.show().setOwnerActivity(activity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a dialog box with OK and Cancel buttons
|
||||
*
|
||||
* @param activity
|
||||
* @param text
|
||||
* @param okListener
|
||||
* @param cancelListener
|
||||
*/
|
||||
public void okCancelDialog(final Activity activity, final String text,
|
||||
final DialogInterface.OnClickListener okListener,
|
||||
final DialogInterface.OnClickListener cancelListener) {
|
||||
if(activity.isFinishing())
|
||||
return;
|
||||
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(confirmDialogTitleResource)
|
||||
.setMessage(text)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(android.R.string.ok, okListener)
|
||||
.setNegativeButton(android.R.string.cancel, cancelListener)
|
||||
.show().setOwnerActivity(activity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a progress dialog. Must be run on the UI thread
|
||||
* @param context
|
||||
* @param text
|
||||
* @return
|
||||
*/
|
||||
public ProgressDialog progressDialog(Context context, String text) {
|
||||
ProgressDialog dialog = new ProgressDialog(context);
|
||||
dialog.setIndeterminate(true);
|
||||
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
dialog.setMessage(text);
|
||||
dialog.show();
|
||||
dialog.setOwnerActivity((Activity)context);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss a dialog off the UI thread
|
||||
*
|
||||
* @param activity
|
||||
* @param dialog
|
||||
*/
|
||||
public void dismissDialog(Activity activity, final ProgressDialog dialog) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
dialog.dismiss();
|
||||
} catch (Exception e) {
|
||||
// could have killed activity
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
package com.todoroo.andlib.utility;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* E-mail Validator Copyright 2008 Les Hazlewood
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public final class EmailValidator {
|
||||
|
||||
// RFC 2822 2.2.2 Structured Header Field Bodies
|
||||
private static final String wsp = "[ \\t]"; // space or tab
|
||||
private static final String fwsp = wsp + "*";
|
||||
|
||||
// RFC 2822 3.2.1 Primitive tokens
|
||||
private static final String dquote = "\\\"";
|
||||
// ASCII Control characters excluding white space:
|
||||
private static final String noWsCtl = "\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F";
|
||||
// all ASCII characters except CR and LF:
|
||||
private static final String asciiText = "[\\x01-\\x09\\x0B\\x0C\\x0E-\\x7F]";
|
||||
|
||||
// RFC 2822 3.2.2 Quoted characters:
|
||||
// single backslash followed by a text char
|
||||
private static final String quotedPair = "(\\\\" + asciiText + ")";
|
||||
|
||||
// RFC 2822 3.2.4 Atom:
|
||||
private static final String atext = "[a-zA-Z0-9\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\_\\`\\{\\|\\}\\~]";
|
||||
private static final String dotAtomText = atext + "+" + "(" + "\\." + atext
|
||||
+ "+)*";
|
||||
private static final String dotAtom = fwsp + "(" + dotAtomText + ")" + fwsp;
|
||||
|
||||
// RFC 2822 3.2.5 Quoted strings:
|
||||
// noWsCtl and the rest of ASCII except the doublequote and backslash
|
||||
// characters:
|
||||
private static final String qtext = "[" + noWsCtl
|
||||
+ "\\x21\\x23-\\x5B\\x5D-\\x7E]";
|
||||
private static final String qcontent = "(" + qtext + "|" + quotedPair + ")";
|
||||
private static final String quotedString = dquote + "(" + fwsp + qcontent
|
||||
+ ")*" + fwsp + dquote;
|
||||
|
||||
// RFC 1035 tokens for domain names:
|
||||
private static final String letter = "[a-zA-Z]";
|
||||
private static final String letDig = "[a-zA-Z0-9]";
|
||||
private static final String letDigHyp = "[a-zA-Z0-9-]";
|
||||
private static final String rfcLabel = letDig + "(" + letDigHyp + "{0,61}"
|
||||
+ letDig + ")?";
|
||||
private static final String rfc1035DomainName = rfcLabel + "(\\."
|
||||
+ rfcLabel + ")*\\." + letter + "{2,6}";
|
||||
|
||||
private static final String domain = rfc1035DomainName;
|
||||
|
||||
private static final String localPart = "((" + dotAtom + ")|("
|
||||
+ quotedString + "))";
|
||||
private static final String addrSpec = localPart + "@" + domain;
|
||||
|
||||
// now compile a pattern for efficient re-use:
|
||||
// if we're allowing quoted identifiers or not:
|
||||
private static final String patternString = addrSpec;
|
||||
public static final Pattern VALID_PATTERN = Pattern.compile(patternString);
|
||||
|
||||
|
||||
public static boolean validateEmail(String value) {
|
||||
return VALID_PATTERN.matcher(value).matches();
|
||||
}
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
package com.todoroo.andlib.utility;
|
||||
|
||||
/**
|
||||
* Pair utility class
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
* @param <L>
|
||||
* @param <R>
|
||||
*/
|
||||
public class Pair<L, R> {
|
||||
|
||||
private final L left;
|
||||
private final R right;
|
||||
|
||||
public R getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
public L getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
public Pair(final L left, final R right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public static <A, B> Pair<A, B> create(A left, B right) {
|
||||
return new Pair<A, B>(left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
if (!(o instanceof Pair<?, ?>))
|
||||
return false;
|
||||
|
||||
final Pair<?, ?> other = (Pair<?, ?>) o;
|
||||
return equal(getLeft(), other.getLeft()) && equal(getRight(), other.getRight());
|
||||
}
|
||||
|
||||
public static final boolean equal(Object o1, Object o2) {
|
||||
if (o1 == null) {
|
||||
return o2 == null;
|
||||
}
|
||||
return o1.equals(o2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hLeft = getLeft() == null ? 0 : getLeft().hashCode();
|
||||
int hRight = getRight() == null ? 0 : getRight().hashCode();
|
||||
|
||||
return hLeft + (57 * hRight);
|
||||
}
|
||||
}
|
||||
@ -1,119 +0,0 @@
|
||||
package com.todoroo.andlib.utility;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* SoftHashMap from javaspecialists.eu Issue 98
|
||||
* <http://www.javaspecialists.eu/archive/Issue098.html>
|
||||
*
|
||||
* @param <K>
|
||||
* @param <V>
|
||||
*/
|
||||
public class SoftHashMap<K, V> extends AbstractMap<K, V> {
|
||||
private static final long serialVersionUID = -3796460667941300642L;
|
||||
|
||||
/** The internal HashMap that will hold the SoftReference. */
|
||||
private final Map<K, SoftReference<V>> hash = new HashMap<K, SoftReference<V>>();
|
||||
|
||||
private final Map<SoftReference<V>, K> reverseLookup = new HashMap<SoftReference<V>, K>();
|
||||
|
||||
/** Reference queue for cleared SoftReference objects. */
|
||||
protected final ReferenceQueue<V> queue = new ReferenceQueue<V>();
|
||||
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
expungeStaleEntries();
|
||||
V result = null;
|
||||
// We get the SoftReference represented by that key
|
||||
SoftReference<V> soft_ref = hash.get(key);
|
||||
if (soft_ref != null) {
|
||||
// From the SoftReference we get the value, which can be
|
||||
// null if it has been garbage collected
|
||||
result = soft_ref.get();
|
||||
if (result == null) {
|
||||
// If the value has been garbage collected, remove the
|
||||
// entry from the HashMap.
|
||||
hash.remove(key);
|
||||
reverseLookup.remove(soft_ref);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void expungeStaleEntries() {
|
||||
Reference<? extends V> sv;
|
||||
while ((sv = queue.poll()) != null) {
|
||||
hash.remove(reverseLookup.remove(sv));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
expungeStaleEntries();
|
||||
SoftReference<V> soft_ref = new SoftReference<V>(value, queue);
|
||||
reverseLookup.put(soft_ref, key);
|
||||
SoftReference<V> result = hash.put(key, soft_ref);
|
||||
if (result == null)
|
||||
return null;
|
||||
reverseLookup.remove(result);
|
||||
return result.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
expungeStaleEntries();
|
||||
SoftReference<V> result = hash.remove(key);
|
||||
if (result == null)
|
||||
return null;
|
||||
return result.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
hash.clear();
|
||||
reverseLookup.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
expungeStaleEntries();
|
||||
return hash.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the key/values in the map at the point of calling.
|
||||
* However, setValue still sets the value in the actual SoftHashMap.
|
||||
*/
|
||||
@Override
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
expungeStaleEntries();
|
||||
Set<Entry<K, V>> result = new LinkedHashSet<Entry<K, V>>();
|
||||
for (final Entry<K, SoftReference<V>> entry : hash.entrySet()) {
|
||||
final V value = entry.getValue().get();
|
||||
if (value != null) {
|
||||
result.add(new Entry<K, V>() {
|
||||
public K getKey() {
|
||||
return entry.getKey();
|
||||
}
|
||||
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public V setValue(V v) {
|
||||
entry.setValue(new SoftReference<V>(v, queue));
|
||||
return value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -1,450 +0,0 @@
|
||||
package com.todoroo.andlib.utility;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2008 Google Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
|
||||
/**
|
||||
* <p>UserTask enables proper and easy use of the UI thread. This class allows to
|
||||
* perform background operations and publish results on the UI thread without
|
||||
* having to manipulate threads and/or handlers.</p>
|
||||
*
|
||||
* <p>A user task is defined by a computation that runs on a background thread and
|
||||
* whose result is published on the UI thread. A user task is defined by 3 generic
|
||||
* types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,
|
||||
* and 4 steps, called <code>begin</code>, <code>doInBackground</code>,
|
||||
* <code>processProgress<code> and <code>end</code>.</p>
|
||||
*
|
||||
* <h2>Usage</h2>
|
||||
* <p>UserTask must be subclassed to be used. The subclass will override at least
|
||||
* one method ({@link #doInBackground(Object[])}), and most often will override a
|
||||
* second one ({@link #end(Object)}.)</p>
|
||||
*
|
||||
* <p>Here is an example of subclassing:</p>
|
||||
* <pre>
|
||||
* private class DownloadFilesTask extends UserTask<URL, Integer, Long> {
|
||||
* public File doInBackground(URL... urls) {
|
||||
* int count = urls.length;
|
||||
* long totalSize = 0;
|
||||
* for (int i = 0; i < count; i++) {
|
||||
* totalSize += Downloader.downloadFile(urls[i]);
|
||||
* publishProgress((int) ((i / (float) count) * 100));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* public void processProgress(Integer... progress) {
|
||||
* setProgressPercent(progress[0]);
|
||||
* }
|
||||
*
|
||||
* public void end(Long result) {
|
||||
* showDialog("Downloaded " + result + " bytes");
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>Once created, a task is executed very simply:</p>
|
||||
* <pre>
|
||||
* new DownloadFilesTask().execute(new URL[] { ... });
|
||||
* </pre>
|
||||
*
|
||||
* <h2>User task's generic types</h2>
|
||||
* <p>The three types used by a user task are the following:</p>
|
||||
* <ol>
|
||||
* <li><code>Params</code>, the type of the parameters sent to the task upon
|
||||
* execution.</li>
|
||||
* <li><code>Progress</code>, the type of the progress units published during
|
||||
* the background computation.</li>
|
||||
* <li><code>Result</code>, the type of the result of the background
|
||||
* computation.</li>
|
||||
* </ol>
|
||||
* <p>Not all types are always used by a user task. To mark a type as unused,
|
||||
* simply use the type {@link Void}:</p>
|
||||
* <pre>
|
||||
* private class MyTask extends UserTask<Void, Void, Void) { ... }
|
||||
* </pre>
|
||||
*
|
||||
* <h2>The 4 steps</h2>
|
||||
* <p>When a user task is executed, the task goes through 4 steps:</p>
|
||||
* <ol>
|
||||
* <li>{@link #begin()}, invoked on the UI thread immediately after the task
|
||||
* is executed. This step is normally used to setup the task, for instance by
|
||||
* showing a progress bar in the user interface.</li>
|
||||
* <li>{@link #doInBackground(Object[])}, invoked on the background thread
|
||||
* immediately after {@link #begin()} finishes executing. This step is used
|
||||
* to perform background computation that can take a long time. The parameters
|
||||
* of the user task are passed to this step. The result of the computation must
|
||||
* be returned by this step and will be passed back to the last step. This step
|
||||
* can also use {@link #publishProgress(Object[])} to publish one or more units
|
||||
* of progress. These values are published on the UI thread, in the
|
||||
* {@link #processProgress(Object[])} step.</li>
|
||||
* <li>{@link #processProgress(Object[])}, invoked on the UI thread after a
|
||||
* call to {@link #publishProgress(Object[])}. The timing of the execution is
|
||||
* undefined. This method is used to display any form of progress in the user
|
||||
* interface while the background computation is still executing. For instance,
|
||||
* it can be used to animate a progress bar or show logs in a text field.</li>
|
||||
* <li>{@link #end(Object)}, invoked on the UI thread after the background
|
||||
* computation finishes. The result of the background computation is passed to
|
||||
* this step as a parameter.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <h2>Threading rules</h2>
|
||||
* <p>There are a few threading rules that must be followed for this class to
|
||||
* work properly:</p>
|
||||
* <ul>
|
||||
* <li>The task instance must be created on the UI thread.</li>
|
||||
* <li>{@link #execute(Object[])} must be invoked on the UI thread.</li>
|
||||
* <li>Do not call {@link #begin()}, {@link #end(Object)},
|
||||
* {@link #doInBackground(Object[])}, {@link #processProgress(Object[])}
|
||||
* manually.</li>
|
||||
* <li>The task can be executed only once (an exception will be thrown if
|
||||
* a second execution is attempted.)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public abstract class UserTask<Params, Progress, Result> {
|
||||
private static final String LOG_TAG = "UserTask";
|
||||
|
||||
private static final int CORE_POOL_SIZE = 4;
|
||||
private static final int MAXIMUM_POOL_SIZE = 10;
|
||||
private static final int KEEP_ALIVE = 10;
|
||||
|
||||
private static final BlockingQueue<Runnable> sWorkQueue =
|
||||
new LinkedBlockingQueue<Runnable>(MAXIMUM_POOL_SIZE);
|
||||
|
||||
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
|
||||
private final AtomicInteger mCount = new AtomicInteger(1);
|
||||
|
||||
public Thread newThread(Runnable r) {
|
||||
final Thread thread = new Thread(r, "UserTask #" + mCount.getAndIncrement());
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
||||
return thread;
|
||||
}
|
||||
};
|
||||
|
||||
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
|
||||
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
|
||||
|
||||
private static final int MESSAGE_POST_RESULT = 0x1;
|
||||
private static final int MESSAGE_POST_PROGRESS = 0x2;
|
||||
|
||||
protected static InternalHandler sHandler;
|
||||
|
||||
private final WorkerRunnable<Params, Result> mWorker;
|
||||
private final FutureTask<Result> mFuture;
|
||||
|
||||
private volatile Status mStatus = Status.PENDING;
|
||||
|
||||
/**
|
||||
* Indicates the current status of the task. Each status will be set only once
|
||||
* during the lifetime of a task.
|
||||
*/
|
||||
public enum Status {
|
||||
/**
|
||||
* Indicates that the task has not been executed yet.
|
||||
*/
|
||||
PENDING,
|
||||
/**
|
||||
* Indicates that the task is running.
|
||||
*/
|
||||
RUNNING,
|
||||
/**
|
||||
* Indicates that {@link UserTask#end(Object)} has finished.
|
||||
*/
|
||||
FINISHED,
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user task. This constructor must be invoked on the UI thread.
|
||||
*/
|
||||
public UserTask() {
|
||||
synchronized(UserTask.class) {
|
||||
if (sHandler == null) {
|
||||
sHandler = new InternalHandler();
|
||||
}
|
||||
}
|
||||
|
||||
mWorker = new WorkerRunnable<Params, Result>() {
|
||||
public Result call() throws Exception {
|
||||
return doInBackground(mParams);
|
||||
}
|
||||
};
|
||||
|
||||
mFuture = new FutureTask<Result>(mWorker) {
|
||||
@Override
|
||||
protected void done() {
|
||||
Result result = null;
|
||||
try {
|
||||
result = mFuture.get();
|
||||
} catch (InterruptedException e) {
|
||||
android.util.Log.w(LOG_TAG, e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("An error occured while executing doInBackground()",
|
||||
e.getCause());
|
||||
} catch (CancellationException e) {
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException("An error occured while executing "
|
||||
+ "doInBackground()", t);
|
||||
}
|
||||
|
||||
final Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
|
||||
new UserTaskResult<Result>(UserTask.this, result));
|
||||
message.sendToTarget();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current status of this task.
|
||||
*
|
||||
* @return The current status.
|
||||
*/
|
||||
public final Status getStatus() {
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to perform a computation on a background thread. The
|
||||
* specified parameters are the parameters passed to {@link #execute(Object[])}
|
||||
* by the caller of this task.
|
||||
*
|
||||
* This method can call {@link #publishProgress(Object[])} to publish updates
|
||||
* on the UI thread.
|
||||
*
|
||||
* @params params The parameters of the task.
|
||||
*
|
||||
* @return A result, defined by the subclass of this task.
|
||||
*
|
||||
* @see #begin()
|
||||
* @see #end(Object)
|
||||
* @see #publishProgress(Object[])
|
||||
*/
|
||||
public Result doInBackground(@SuppressWarnings("unused") Params... params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on the UI thread before {@link #doInBackground(Object[])}.
|
||||
*
|
||||
* @see #end(Object)
|
||||
* @see #doInBackground(Object[])
|
||||
*/
|
||||
public void begin() {
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on the UI thread after {@link #doInBackground(Object[])}. The
|
||||
* specified result is the value returned by {@link #doInBackground(Object[])}
|
||||
* or null if the task was cancelled or an exception occured.
|
||||
*
|
||||
* @see #begin()
|
||||
* @see #doInBackground(Object[])
|
||||
*/
|
||||
public void end(@SuppressWarnings("unused") Result result) {
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on the UI thread after {@link #publishProgress(Object[])} is invoked.
|
||||
* The specified values are the values passed to {@link #publishProgress(Object[])}.
|
||||
*
|
||||
* @see #publishProgress(Object[])
|
||||
* @see #doInBackground(Object[])
|
||||
*/
|
||||
public void processProgress(@SuppressWarnings("unused") Progress... values) {
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <tt>true</tt> if this task was cancelled before it completed
|
||||
* normally.
|
||||
*
|
||||
* @return <tt>true</tt> if task was cancelled before it completed
|
||||
*
|
||||
* @see #cancel(boolean)
|
||||
*/
|
||||
public final boolean isCancelled() {
|
||||
return mFuture.isCancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to cancel execution of this task. This attempt will
|
||||
* fail if the task has already completed, already been cancelled,
|
||||
* or could not be cancelled for some other reason. If successful,
|
||||
* and this task has not started when <tt>cancel</tt> is called,
|
||||
* this task should never run. If the task has already started,
|
||||
* then the <tt>mayInterruptIfRunning</tt> parameter determines
|
||||
* whether the thread executing this task should be interrupted in
|
||||
* an attempt to stop the task.
|
||||
*
|
||||
* @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
|
||||
* task should be interrupted; otherwise, in-progress tasks are allowed
|
||||
* to complete.
|
||||
*
|
||||
* @return <tt>false</tt> if the task could not be cancelled,
|
||||
* typically because it has already completed normally;
|
||||
* <tt>true</tt> otherwise
|
||||
*
|
||||
* @see #isCancelled()
|
||||
*/
|
||||
public final boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return mFuture.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits if necessary for the computation to complete, and then
|
||||
* retrieves its result.
|
||||
*
|
||||
* @return The computed result.
|
||||
*
|
||||
* @throws CancellationException If the computation was cancelled.
|
||||
* @throws ExecutionException If the computation threw an exception.
|
||||
* @throws InterruptedException If the current thread was interrupted
|
||||
* while waiting.
|
||||
*/
|
||||
public final Result get() throws InterruptedException, ExecutionException {
|
||||
return mFuture.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits if necessary for at most the given time for the computation
|
||||
* to complete, and then retrieves its result.
|
||||
*
|
||||
* @return The computed result.
|
||||
*
|
||||
* @throws CancellationException If the computation was cancelled.
|
||||
* @throws ExecutionException If the computation threw an exception.
|
||||
* @throws InterruptedException If the current thread was interrupted
|
||||
* while waiting.
|
||||
* @throws TimeoutException If the wait timed out.
|
||||
*/
|
||||
public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
|
||||
ExecutionException, TimeoutException {
|
||||
return mFuture.get(timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the task with the specified parameters. The task returns
|
||||
* itself (this) so that the caller can keep a reference to it.
|
||||
*
|
||||
* This method must be invoked on the UI thread.
|
||||
*
|
||||
* @params params The parameters of the task.
|
||||
*
|
||||
* @return This instance of UserTask.
|
||||
*
|
||||
* @throws IllegalStateException If {@link #getStatus()} returns either
|
||||
* {@link com.google.android.photostream.UserTask.Status#RUNNING} or
|
||||
* {@link com.google.android.photostream.UserTask.Status#FINISHED}.
|
||||
*/
|
||||
public final UserTask<Params, Progress, Result> execute(Params... params) {
|
||||
if (mStatus != Status.PENDING) {
|
||||
switch (mStatus) {
|
||||
case RUNNING:
|
||||
throw new IllegalStateException("Cannot execute task:"
|
||||
+ " the task is already running.");
|
||||
case FINISHED:
|
||||
throw new IllegalStateException("Cannot execute task:"
|
||||
+ " the task has already been executed "
|
||||
+ "(a task can be executed only once)");
|
||||
}
|
||||
}
|
||||
|
||||
mStatus = Status.RUNNING;
|
||||
|
||||
begin();
|
||||
|
||||
mWorker.mParams = params;
|
||||
|
||||
try {
|
||||
sExecutor.execute(mFuture);
|
||||
} catch (RejectedExecutionException e) {
|
||||
// cannot schedule because of some other error. just die quietly
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be invoked from {@link #doInBackground(Object[])} to
|
||||
* publish updates on the UI thread while the background computation is
|
||||
* still running. Each call to this method will trigger the execution of
|
||||
* {@link #processProgress(Object[])} on the UI thread.
|
||||
*
|
||||
* @params values The progress values to update the UI with.
|
||||
*
|
||||
* @see #processProgress(Object[])
|
||||
* @see #doInBackground(Object[])
|
||||
*/
|
||||
protected final void publishProgress(Progress... values) {
|
||||
sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
|
||||
new UserTaskResult<Progress>(this, values)).sendToTarget();
|
||||
}
|
||||
|
||||
protected void finish(Result result) {
|
||||
end(result);
|
||||
mStatus = Status.FINISHED;
|
||||
}
|
||||
|
||||
protected static class InternalHandler extends Handler {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
UserTaskResult result = (UserTaskResult) msg.obj;
|
||||
switch (msg.what) {
|
||||
case MESSAGE_POST_RESULT:
|
||||
// There is only one result
|
||||
result.mTask.finish(result.mData[0]);
|
||||
break;
|
||||
case MESSAGE_POST_PROGRESS:
|
||||
result.mTask.processProgress(result.mData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
|
||||
Params[] mParams;
|
||||
}
|
||||
|
||||
protected static class UserTaskResult<Data> {
|
||||
final UserTask<?, ?, ?> mTask;
|
||||
final Data[] mData;
|
||||
|
||||
UserTaskResult(UserTask<?, ?, ?> task, Data... data) {
|
||||
mTask = task;
|
||||
mData = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,331 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.common;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.data.Property.LongProperty;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.NotificationManager;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
import com.todoroo.astrid.utility.Constants;
|
||||
|
||||
/**
|
||||
* A helper class for writing synchronization services for Astrid. This class
|
||||
* contains logic for merging incoming changes and writing outgoing changes.
|
||||
* <p>
|
||||
* Use {@link initiate} as the entry point for your synchronization service,
|
||||
* which should handle authentication and then call {@link synchronizeTasks} to
|
||||
* initiate synchronization.
|
||||
*
|
||||
* @author timsu
|
||||
*
|
||||
*/
|
||||
public abstract class SyncProvider<TYPE extends TaskContainer> {
|
||||
|
||||
// --- abstract methods - your services should implement these
|
||||
|
||||
/**
|
||||
* Perform log in (launching activity if necessary) and sync. This is
|
||||
* invoked when users manually request synchronization
|
||||
*
|
||||
* @param activity
|
||||
* context
|
||||
*/
|
||||
abstract protected void initiateManual(Activity activity);
|
||||
|
||||
/**
|
||||
* Perform synchronize. Since this can be called from background services,
|
||||
* you should not open up new activities. Instead, if the user is not signed
|
||||
* in, your service should do nothing.
|
||||
*
|
||||
* @param service
|
||||
* context
|
||||
*/
|
||||
abstract protected void initiateBackground(Service service);
|
||||
|
||||
/**
|
||||
* Updates the text of a notification and the intent to open when tapped
|
||||
* @param context
|
||||
* @param notification
|
||||
*/
|
||||
abstract protected void updateNotification(Context context, Notification n);
|
||||
|
||||
/**
|
||||
* Deal with an exception that occurs during synchronization
|
||||
*
|
||||
* @param tag
|
||||
* short string description of where error occurred
|
||||
* @param e
|
||||
* exception
|
||||
* @param displayError
|
||||
* whether to display error to the user
|
||||
*/
|
||||
abstract protected void handleException(String tag, Exception e, boolean displayError);
|
||||
|
||||
/**
|
||||
* Create a task on the remote server.
|
||||
*
|
||||
* @param task
|
||||
* task to create
|
||||
* @return task created on remote server
|
||||
*/
|
||||
abstract protected TYPE create(TYPE task) throws IOException;
|
||||
|
||||
/**
|
||||
* Push variables from given task to the remote server.
|
||||
*
|
||||
* @param task
|
||||
* task proxy to push
|
||||
* @param remoteTask
|
||||
* remote task that we merged with. may be null
|
||||
*/
|
||||
abstract protected void push(TYPE task, TYPE remote) throws IOException;
|
||||
|
||||
/**
|
||||
* Fetch remote task. Used to re-read merged tasks
|
||||
*
|
||||
* @param task
|
||||
* task with id's to re-read
|
||||
* @return new Task
|
||||
*/
|
||||
abstract protected TYPE pull(TYPE task) throws IOException;
|
||||
|
||||
/**
|
||||
* Reads a task container from a task in the database
|
||||
*
|
||||
* @param task
|
||||
*/
|
||||
abstract protected TYPE read(TodorooCursor<Task> task) throws IOException;
|
||||
|
||||
/**
|
||||
* Save task. Used to save local tasks that have been updated and remote
|
||||
* tasks that need to be created locally
|
||||
*
|
||||
* @param task
|
||||
*/
|
||||
abstract protected void write(TYPE task) throws IOException;
|
||||
|
||||
/**
|
||||
* Finds a task in the list with the same remote identifier(s) as
|
||||
* the task passed in
|
||||
*
|
||||
* @return task from list if matches, null otherwise
|
||||
*/
|
||||
abstract protected int matchTask(ArrayList<TYPE> tasks, TYPE target);
|
||||
|
||||
/**
|
||||
* Transfer remote identifier(s) from one task to another
|
||||
*/
|
||||
abstract protected void transferIdentifiers(TYPE source,
|
||||
TYPE destination);
|
||||
|
||||
// --- implementation
|
||||
|
||||
private final Notification notification;
|
||||
|
||||
public SyncProvider() {
|
||||
// initialize notification
|
||||
int icon = android.R.drawable.stat_notify_sync;
|
||||
long when = System.currentTimeMillis();
|
||||
notification = new Notification(icon, null, when);
|
||||
notification.flags |= Notification.FLAG_ONGOING_EVENT;
|
||||
}
|
||||
|
||||
public void synchronize(final Context context) {
|
||||
// display toast
|
||||
if(context instanceof Activity) {
|
||||
((Activity) context).runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(context, R.string.SyP_progress_toast,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
initiateManual((Activity)context);
|
||||
} else if(context instanceof Service) {
|
||||
// display notification
|
||||
updateNotification(context, notification);
|
||||
final NotificationManager nm = new NotificationManager.AndroidNotificationManager(context);
|
||||
nm.notify(Constants.NOTIFICATION_SYNC, notification);
|
||||
|
||||
// start next step in background thread
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
initiateBackground((Service)context);
|
||||
} finally {
|
||||
nm.cancel(Constants.NOTIFICATION_SYNC);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
// --- synchronization logic
|
||||
|
||||
/**
|
||||
* Helper to synchronize remote tasks with our local database.
|
||||
*
|
||||
* This initiates the following process: 1. local changes are read 2. remote
|
||||
* changes are read 3. local tasks are merged with remote changes and pushed
|
||||
* across 4. remote changes are then read in
|
||||
*
|
||||
* @param data synchronization data structure
|
||||
*/
|
||||
protected void synchronizeTasks(SyncData<TYPE> data) throws IOException {
|
||||
int length;
|
||||
|
||||
// create internal data structures
|
||||
HashMap<String, Integer> remoteNewTaskNameMap = new HashMap<String, Integer>();
|
||||
length = data.remoteUpdated.size();
|
||||
for(int i = 0; i < length; i++) {
|
||||
TYPE remote = data.remoteUpdated.get(i);
|
||||
if(remote.task.getId() != Task.NO_ID)
|
||||
continue;
|
||||
remoteNewTaskNameMap.put(remote.task.getValue(Task.TITLE), i);
|
||||
}
|
||||
|
||||
// 1. CREATE: grab newly created tasks and create them remotely
|
||||
length = data.localCreated.getCount();
|
||||
for(int i = 0; i < length; i++) {
|
||||
data.localCreated.moveToNext();
|
||||
TYPE local = read(data.localCreated);
|
||||
try {
|
||||
|
||||
String taskTitle = local.task.getValue(Task.TITLE);
|
||||
|
||||
/* If there exists an incoming remote task with the same name and no
|
||||
* mapping, we don't want to create this on the remote server,
|
||||
* because user could have synchronized like this before. Instead,
|
||||
* we create a mapping and do an update.
|
||||
*/
|
||||
if (remoteNewTaskNameMap.containsKey(taskTitle)) {
|
||||
int remoteIndex = remoteNewTaskNameMap.remove(taskTitle);
|
||||
TYPE remote = data.remoteUpdated.get(remoteIndex);
|
||||
|
||||
transferIdentifiers(remote, local);
|
||||
push(local, remote);
|
||||
|
||||
// re-read remote task after merge, update remote task list
|
||||
remote = pull(remote);
|
||||
remote.task.setId(local.task.getId());
|
||||
data.remoteUpdated.set(remoteIndex, remote);
|
||||
|
||||
} else {
|
||||
create(local);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleException("sync-local-created", e, false); //$NON-NLS-1$
|
||||
}
|
||||
write(local);
|
||||
}
|
||||
|
||||
// 2. UPDATE: for each updated local task
|
||||
length = data.localUpdated.getCount();
|
||||
for(int i = 0; i < length; i++) {
|
||||
data.localUpdated.moveToNext();
|
||||
TYPE local = read(data.localUpdated);
|
||||
try {
|
||||
if(local.task == null)
|
||||
continue;
|
||||
|
||||
// if there is a conflict, merge
|
||||
int remoteIndex = matchTask((ArrayList<TYPE>)data.remoteUpdated, local);
|
||||
if(remoteIndex != -1) {
|
||||
TYPE remote = data.remoteUpdated.get(remoteIndex);
|
||||
push(local, remote);
|
||||
|
||||
// re-read remote task after merge
|
||||
remote = pull(remote);
|
||||
remote.task.setId(local.task.getId());
|
||||
data.remoteUpdated.set(remoteIndex, remote);
|
||||
} else {
|
||||
push(local, null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleException("sync-local-updated", e, false); //$NON-NLS-1$
|
||||
}
|
||||
write(local);
|
||||
}
|
||||
|
||||
// 3. REMOTE: load remote information
|
||||
|
||||
// Rearrange remoteTasks so completed tasks get synchronized first.
|
||||
// This prevents bugs where a repeated task has two copies come down
|
||||
// the wire, the new version and the completed old version. The new
|
||||
// version would get merged, then completed, if done in the wrong order.
|
||||
|
||||
Collections.sort(data.remoteUpdated, new Comparator<TYPE>() {
|
||||
private static final int SENTINEL = -2;
|
||||
private final int check(TYPE o1, TYPE o2, LongProperty property) {
|
||||
long o1Property = o1.task.getValue(property);
|
||||
long o2Property = o2.task.getValue(property);
|
||||
if(o1Property != 0 && o2Property != 0)
|
||||
return 0;
|
||||
else if(o1Property != 0)
|
||||
return -1;
|
||||
else if(o2Property != 0)
|
||||
return 1;
|
||||
return SENTINEL;
|
||||
}
|
||||
public int compare(TYPE o1, TYPE o2) {
|
||||
int comparison = check(o1, o2, Task.DELETION_DATE);
|
||||
if(comparison != SENTINEL)
|
||||
return comparison;
|
||||
comparison = check(o1, o2, Task.COMPLETION_DATE);
|
||||
if(comparison != SENTINEL)
|
||||
return comparison;
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
length = data.remoteUpdated.size();
|
||||
for(int i = 0; i < length; i++) {
|
||||
TYPE remote = data.remoteUpdated.get(i);
|
||||
|
||||
// don't synchronize new & deleted / completed tasks
|
||||
if(!remote.task.isSaved() && (remote.task.isDeleted() ||
|
||||
remote.task.isCompleted()))
|
||||
continue;
|
||||
|
||||
try {
|
||||
write(remote);
|
||||
} catch (Exception e) {
|
||||
handleException("sync-remote-updated", e, false); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- helper classes
|
||||
|
||||
/** data structure builder */
|
||||
protected static class SyncData<TYPE extends TaskContainer> {
|
||||
public final ArrayList<TYPE> remoteUpdated;
|
||||
|
||||
public final TodorooCursor<Task> localCreated;
|
||||
public final TodorooCursor<Task> localUpdated;
|
||||
|
||||
public SyncData(ArrayList<TYPE> remoteUpdated,
|
||||
TodorooCursor<Task> localCreated,
|
||||
TodorooCursor<Task> localUpdated) {
|
||||
super();
|
||||
this.remoteUpdated = remoteUpdated;
|
||||
this.localCreated = localCreated;
|
||||
this.localUpdated = localUpdated;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,206 +0,0 @@
|
||||
package com.todoroo.astrid.common;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.Preference.OnPreferenceClickListener;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.OnHierarchyChangeListener;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.andlib.utility.DialogUtilities;
|
||||
import com.todoroo.andlib.widget.TodorooPreferences;
|
||||
|
||||
/**
|
||||
* Utility class for common synchronization action: displaying synchronization
|
||||
* preferences and an action panel so users can initiate actions from the menu.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com
|
||||
*
|
||||
*/
|
||||
abstract public class SyncProviderPreferences extends TodorooPreferences {
|
||||
|
||||
// --- interface
|
||||
|
||||
/**
|
||||
* @return your preference resource
|
||||
*/
|
||||
@Override
|
||||
abstract public int getPreferenceResource();
|
||||
|
||||
/**
|
||||
* kick off synchronization
|
||||
*/
|
||||
abstract public void startSync();
|
||||
|
||||
/**
|
||||
* log user out
|
||||
*/
|
||||
abstract public void logOut();
|
||||
|
||||
/**
|
||||
* @return get preference utilities
|
||||
*/
|
||||
abstract public SyncProviderUtilities getUtilities();
|
||||
|
||||
|
||||
// --- implementation
|
||||
|
||||
@Autowired
|
||||
private DialogUtilities dialogUtilities;
|
||||
|
||||
private int statusColor = Color.BLACK;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getListView().setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onChildViewRemoved(View parent, View child) {
|
||||
//
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildViewAdded(View parent, View child) {
|
||||
View view = findViewById(R.id.status);
|
||||
if(view != null)
|
||||
view.setBackgroundColor(statusColor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param resource
|
||||
* if null, updates all resources
|
||||
*/
|
||||
@Override
|
||||
public void updatePreferences(Preference preference, Object value) {
|
||||
final Resources r = getResources();
|
||||
|
||||
// interval
|
||||
if (r.getString(getUtilities().getSyncIntervalKey()).equals(
|
||||
preference.getKey())) {
|
||||
int index = AndroidUtilities.indexOf(
|
||||
r.getStringArray(R.array.sync_SPr_interval_values),
|
||||
(String) value);
|
||||
if (index <= 0)
|
||||
preference.setSummary(R.string.sync_SPr_interval_desc_disabled);
|
||||
else
|
||||
preference.setSummary(r.getString(
|
||||
R.string.sync_SPr_interval_desc,
|
||||
r.getStringArray(R.array.sync_SPr_interval_entries)[index]));
|
||||
}
|
||||
|
||||
// status
|
||||
else if (r.getString(R.string.sync_SPr_status_key).equals(preference.getKey())) {
|
||||
boolean loggedIn = getUtilities().isLoggedIn();
|
||||
String status;
|
||||
String subtitle = ""; //$NON-NLS-1$
|
||||
|
||||
// ! logged in - display message, click -> sync
|
||||
if(!loggedIn) {
|
||||
status = r.getString(R.string.sync_status_loggedout);
|
||||
statusColor = Color.RED;
|
||||
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference p) {
|
||||
startSync();
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
// sync is occurring
|
||||
else if(getUtilities().isOngoing()) {
|
||||
status = r.getString(R.string.sync_status_ongoing);
|
||||
statusColor = Color.rgb(0, 0, 100);
|
||||
}
|
||||
// last sync was error
|
||||
else if(getUtilities().getLastAttemptedSyncDate() != 0) {
|
||||
status = r.getString(R.string.sync_status_failed,
|
||||
DateUtilities.getDateStringWithTime(SyncProviderPreferences.this,
|
||||
new Date(getUtilities().getLastAttemptedSyncDate())));
|
||||
if(getUtilities().getLastSyncDate() > 0) {
|
||||
subtitle = r.getString(R.string.sync_status_failed_subtitle,
|
||||
DateUtilities.getDateStringWithTime(SyncProviderPreferences.this,
|
||||
new Date(getUtilities().getLastSyncDate())));
|
||||
}
|
||||
statusColor = Color.rgb(100, 0, 0);
|
||||
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference p) {
|
||||
String error = getUtilities().getLastError();
|
||||
if(error != null)
|
||||
dialogUtilities.okDialog(SyncProviderPreferences.this, error, null);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} else if(getUtilities().getLastSyncDate() > 0) {
|
||||
status = r.getString(R.string.sync_status_success,
|
||||
DateUtilities.getDateStringWithTime(SyncProviderPreferences.this,
|
||||
new Date(getUtilities().getLastSyncDate())));
|
||||
statusColor = Color.rgb(0, 100, 0);
|
||||
} else {
|
||||
status = r.getString(R.string.sync_status_never);
|
||||
statusColor = Color.rgb(0, 0, 100);
|
||||
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference p) {
|
||||
startSync();
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
preference.setTitle(status);
|
||||
preference.setSummary(subtitle);
|
||||
|
||||
View view = findViewById(R.id.status);
|
||||
if(view != null)
|
||||
view.setBackgroundColor(statusColor);
|
||||
}
|
||||
|
||||
// sync button
|
||||
else if (r.getString(R.string.sync_SPr_sync_key).equals(preference.getKey())) {
|
||||
boolean loggedIn = getUtilities().isLoggedIn();
|
||||
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference p) {
|
||||
startSync();
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if(!loggedIn)
|
||||
preference.setTitle(R.string.sync_SPr_sync_log_in);
|
||||
}
|
||||
|
||||
// log out button
|
||||
else if (r.getString(R.string.sync_SPr_forget_key).equals(preference.getKey())) {
|
||||
boolean loggedIn = getUtilities().isLoggedIn();
|
||||
preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference p) {
|
||||
dialogUtilities.okCancelDialog(SyncProviderPreferences.this,
|
||||
r.getString(R.string.sync_forget_confirm), new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
logOut();
|
||||
initializePreference(getPreferenceScreen());
|
||||
}
|
||||
}, null);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if(!loggedIn)
|
||||
preference.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,137 +0,0 @@
|
||||
package com.todoroo.astrid.common;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
|
||||
/**
|
||||
* Sync Provider Utility class for accessing preferences
|
||||
*/
|
||||
abstract public class SyncProviderUtilities {
|
||||
|
||||
/**
|
||||
* @return your plugin identifier
|
||||
*/
|
||||
abstract public String getIdentifier();
|
||||
|
||||
/**
|
||||
* @return key for sync interval
|
||||
*/
|
||||
abstract public int getSyncIntervalKey();
|
||||
|
||||
// --- implementation
|
||||
|
||||
private static final String PREF_TOKEN = "_token"; //$NON-NLS-1$
|
||||
|
||||
private static final String PREF_LAST_SYNC = "_last_sync"; //$NON-NLS-1$
|
||||
|
||||
private static final String PREF_LAST_ATTEMPTED_SYNC = "_last_attempted"; //$NON-NLS-1$
|
||||
|
||||
private static final String PREF_LAST_ERROR = "_last_error"; //$NON-NLS-1$
|
||||
|
||||
private static final String PREF_ONGOING = "_ongoing"; //$NON-NLS-1$
|
||||
|
||||
/** Get preferences object from the context */
|
||||
protected static SharedPreferences getPrefs() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(ContextManager.getContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if we have a token for this user, false otherwise
|
||||
*/
|
||||
public boolean isLoggedIn() {
|
||||
return getPrefs().getString(getIdentifier() + PREF_TOKEN, null) != null;
|
||||
}
|
||||
|
||||
/** authentication token, or null if doesn't exist */
|
||||
public String getToken() {
|
||||
return getPrefs().getString(getIdentifier() + PREF_TOKEN, null);
|
||||
}
|
||||
|
||||
/** Sets the authentication token. Set to null to clear. */
|
||||
public void setToken(String setting) {
|
||||
Editor editor = getPrefs().edit();
|
||||
editor.putString(getIdentifier() + PREF_TOKEN, setting);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/** @return Last Successful Sync Date, or 0 */
|
||||
public long getLastSyncDate() {
|
||||
return getPrefs().getLong(getIdentifier() + PREF_LAST_SYNC, 0);
|
||||
}
|
||||
|
||||
/** @return Last Attempted Sync Date, or 0 if it was successful */
|
||||
public long getLastAttemptedSyncDate() {
|
||||
return getPrefs().getLong(getIdentifier() + PREF_LAST_ATTEMPTED_SYNC, 0);
|
||||
}
|
||||
|
||||
/** @return Last Error, or null if no last error */
|
||||
public String getLastError() {
|
||||
return getPrefs().getString(PREF_LAST_ERROR, null);
|
||||
}
|
||||
|
||||
/** @return Last Error, or null if no last error */
|
||||
public boolean isOngoing() {
|
||||
return getPrefs().getBoolean(getIdentifier() + PREF_ONGOING, false);
|
||||
}
|
||||
|
||||
/** Deletes Last Successful Sync Date */
|
||||
public void clearLastSyncDate() {
|
||||
Editor editor = getPrefs().edit();
|
||||
editor.remove(getIdentifier() + PREF_LAST_SYNC);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/** Set Last Successful Sync Date */
|
||||
public void setLastError(String error) {
|
||||
Editor editor = getPrefs().edit();
|
||||
editor.putString(getIdentifier() + PREF_LAST_ERROR, error);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/** Set Ongoing */
|
||||
public void stopOngoing() {
|
||||
Editor editor = getPrefs().edit();
|
||||
editor.putBoolean(getIdentifier() + PREF_ONGOING, false);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/** Set Last Successful Sync Date */
|
||||
public void recordSuccessfulSync() {
|
||||
Editor editor = getPrefs().edit();
|
||||
editor.putLong(getIdentifier() + PREF_LAST_SYNC, DateUtilities.now());
|
||||
editor.putLong(getIdentifier() + PREF_LAST_ATTEMPTED_SYNC, 0);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/** Set Last Attempted Sync Date */
|
||||
public void recordSyncStart() {
|
||||
Editor editor = getPrefs().edit();
|
||||
editor.putLong(getIdentifier() + PREF_LAST_ATTEMPTED_SYNC,
|
||||
DateUtilities.now());
|
||||
editor.putString(getIdentifier() + PREF_LAST_ERROR, null);
|
||||
editor.putBoolean(getIdentifier() + PREF_ONGOING, true);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the frequency, in seconds, auto-sync should occur.
|
||||
*
|
||||
* @return seconds duration, or 0 if not desired
|
||||
*/
|
||||
public int getSyncAutoSyncFrequency() {
|
||||
String value = getPrefs().getString(
|
||||
ContextManager.getContext().getString(
|
||||
getSyncIntervalKey()), null);
|
||||
if (value == null)
|
||||
return 0;
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
package com.todoroo.astrid.common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
||||
import com.todoroo.astrid.model.Metadata;
|
||||
import com.todoroo.astrid.model.Task;
|
||||
|
||||
/**
|
||||
* Container class for transmitting tasks and including local and remote
|
||||
* metadata. Synchronization Providers can subclass this class if desired.
|
||||
*
|
||||
* @see {@link SyncProvider}
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class TaskContainer {
|
||||
public Task task;
|
||||
public ArrayList<Metadata> metadata;
|
||||
|
||||
/**
|
||||
* Check if the metadata contains anything with the given key
|
||||
* @param key
|
||||
* @return first match. or null
|
||||
*/
|
||||
public Metadata findMetadata(String key) {
|
||||
for(Metadata item : metadata) {
|
||||
if(AndroidUtilities.equals(key, item.getValue(Metadata.KEY)))
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue