mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-27 19:52:17 -05:00
Adding the 3 core classes for the search framework. ConditionsTreeNode, LocalSearch and SearchSpecification.
This commit is contained in:
parent
611bae3fb4
commit
5c6552cbf3
365
src/com/fsck/k9/search/ConditionsTreeNode.java
Normal file
365
src/com/fsck/k9/search/ConditionsTreeNode.java
Normal file
@ -0,0 +1,365 @@
|
||||
package com.fsck.k9.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.fsck.k9.search.SearchSpecification.ATTRIBUTE;
|
||||
import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
|
||||
import com.fsck.k9.search.SearchSpecification.SearchCondition;
|
||||
|
||||
/**
|
||||
* This class stores search conditions. It's basically a boolean expression binary tree.
|
||||
* The output will be SQL queries ( obtained by traversing inorder ).
|
||||
*
|
||||
* TODO removing conditions from the tree
|
||||
* TODO implement NOT as a node again
|
||||
*
|
||||
* @author dzan
|
||||
*/
|
||||
public class ConditionsTreeNode implements Parcelable{
|
||||
|
||||
public enum OPERATOR {
|
||||
AND, OR, CONDITION;
|
||||
}
|
||||
|
||||
public ConditionsTreeNode mLeft;
|
||||
public ConditionsTreeNode mRight;
|
||||
public ConditionsTreeNode mParent;
|
||||
|
||||
/*
|
||||
* If mValue isn't CONDITION then mCondition contains a real
|
||||
* condition, otherwise it's null.
|
||||
*/
|
||||
public OPERATOR mValue;
|
||||
public SearchCondition mCondition;
|
||||
|
||||
/*
|
||||
* Used for storing and retrieving the tree to/from the database.
|
||||
* The algorithm is called "modified preorder tree traversal".
|
||||
*/
|
||||
public int mLeftMPTTMarker;
|
||||
public int mRightMPTTMarker;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Static Helpers to restore a tree from a database cursor
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Builds a condition tree starting from a database cursor. The cursor
|
||||
* should point to rows representing the nodes of the tree.
|
||||
*
|
||||
* @param cursor Cursor pointing to the first of a bunch or rows. Each rows
|
||||
* should contains 1 tree node.
|
||||
* @return A condition tree.
|
||||
*/
|
||||
public static ConditionsTreeNode buildTreeFromDB(Cursor cursor) {
|
||||
Stack<ConditionsTreeNode> stack = new Stack<ConditionsTreeNode>();
|
||||
ConditionsTreeNode tmp = null;
|
||||
|
||||
// root node
|
||||
if (cursor.moveToFirst()) {
|
||||
tmp = buildNodeFromRow(cursor);
|
||||
stack.push(tmp);
|
||||
}
|
||||
|
||||
// other nodes
|
||||
while (cursor.moveToNext()) {
|
||||
tmp = buildNodeFromRow(cursor);
|
||||
if (tmp.mRightMPTTMarker < stack.peek().mRightMPTTMarker ){
|
||||
stack.peek().mLeft = tmp;
|
||||
stack.push(tmp);
|
||||
} else {
|
||||
while (stack.peek().mRightMPTTMarker < tmp.mRightMPTTMarker) {
|
||||
stack.pop();
|
||||
}
|
||||
stack.peek().mRight = tmp;
|
||||
}
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single database row to a single condition node.
|
||||
*
|
||||
* @param cursor Cursor pointing to the row we want to convert.
|
||||
* @return A single ConditionsTreeNode
|
||||
*/
|
||||
private static ConditionsTreeNode buildNodeFromRow(Cursor cursor) {
|
||||
ConditionsTreeNode result = null;
|
||||
SearchCondition condition = null;
|
||||
|
||||
OPERATOR tmpValue = ConditionsTreeNode.OPERATOR.valueOf(cursor.getString(5));
|
||||
|
||||
if (tmpValue == OPERATOR.CONDITION) {
|
||||
condition = new SearchCondition(SEARCHFIELD.valueOf(cursor.getString(0)),
|
||||
ATTRIBUTE.valueOf(cursor.getString(2)), cursor.getString(1));
|
||||
}
|
||||
|
||||
result = new ConditionsTreeNode(condition);
|
||||
result.mValue = tmpValue;
|
||||
result.mLeftMPTTMarker = cursor.getInt(3);
|
||||
result.mRightMPTTMarker = cursor.getInt(4);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Constructors
|
||||
///////////////////////////////////////////////////////////////
|
||||
public ConditionsTreeNode(SearchCondition condition) {
|
||||
mParent = null;
|
||||
mCondition = condition;
|
||||
mValue = OPERATOR.CONDITION;
|
||||
}
|
||||
|
||||
public ConditionsTreeNode(ConditionsTreeNode parent, OPERATOR op) {
|
||||
mParent = parent;
|
||||
mValue = op;
|
||||
mCondition = null;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Public modifiers
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Adds the expression as the second argument of an AND
|
||||
* clause to this node.
|
||||
*
|
||||
* @param expr Expression to 'AND' with.
|
||||
* @return New top AND node.
|
||||
* @throws Exception
|
||||
*/
|
||||
public ConditionsTreeNode and(ConditionsTreeNode expr) throws Exception {
|
||||
return add(expr, OPERATOR.AND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the expression as the second argument of an OR
|
||||
* clause to this node.
|
||||
*
|
||||
* @param expr Expression to 'OR' with.
|
||||
* @return New top OR node.
|
||||
* @throws Exception
|
||||
*/
|
||||
public ConditionsTreeNode or(ConditionsTreeNode expr) throws Exception {
|
||||
return add(expr, OPERATOR.OR);
|
||||
}
|
||||
|
||||
/**
|
||||
* This applies the MPTT labeling to the subtree of which this node
|
||||
* is the root node.
|
||||
*
|
||||
* For a description on MPTT see:
|
||||
* http://www.sitepoint.com/hierarchical-data-database-2/
|
||||
*/
|
||||
public void applyMPTTLabel() {
|
||||
applyMPTTLabel(1);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Public accessors
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Returns the condition stored in this node.
|
||||
* @return Condition stored in the node.
|
||||
*/
|
||||
public SearchCondition getCondition() {
|
||||
return mCondition;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will traverse the tree inorder and call toString recursively resulting
|
||||
* in a valid SQL where clause.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return (mLeft == null ? "" : "(" + mLeft + ")")
|
||||
+ " " + ( mCondition == null ? mValue.name() : mCondition ) + " "
|
||||
+ (mRight == null ? "" : "(" + mRight + ")") ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a set of all the leaves in the tree.
|
||||
* @return Set of all the leaves.
|
||||
*/
|
||||
public HashSet<ConditionsTreeNode> getLeafSet() {
|
||||
HashSet<ConditionsTreeNode> leafSet = new HashSet<ConditionsTreeNode>();
|
||||
return getLeafSet(leafSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the nodes in the subtree of which this node
|
||||
* is the root. The list contains the nodes in a pre traversal order.
|
||||
*
|
||||
* @return List of all nodes in subtree in preorder.
|
||||
*/
|
||||
public List<ConditionsTreeNode> preorder() {
|
||||
ArrayList<ConditionsTreeNode> result = new ArrayList<ConditionsTreeNode>();
|
||||
Stack<ConditionsTreeNode> stack = new Stack<ConditionsTreeNode>();
|
||||
stack.push(this);
|
||||
|
||||
while(!stack.isEmpty()) {
|
||||
ConditionsTreeNode current = stack.pop( );
|
||||
if( current.mLeft != null ) stack.push( current.mLeft );
|
||||
if( current.mRight != null ) stack.push( current.mRight );
|
||||
result.add(current);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Private class logic
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Adds two new ConditionTreeNodes, one for the operator and one for the
|
||||
* new condition. The current node will end up on the same level as the
|
||||
* one provided in the arguments, they will be siblings. Their common
|
||||
* parent node will be one containing the operator provided in the arguments.
|
||||
* The method will update all the required references so the tree ends up in
|
||||
* a valid state.
|
||||
*
|
||||
* This method only supports node arguments with a null parent node.
|
||||
*
|
||||
* @param Node to add.
|
||||
* @param Operator that will connect the new node with this one.
|
||||
* @return New parent node, containing the operator.
|
||||
* @throws Exception Throws when the provided new node does not have a null parent.
|
||||
*/
|
||||
private ConditionsTreeNode add(ConditionsTreeNode node, OPERATOR op) throws Exception{
|
||||
if (node.mParent != null) {
|
||||
throw new Exception("Can only add new expressions from root node down.");
|
||||
}
|
||||
|
||||
ConditionsTreeNode tmpNode = new ConditionsTreeNode(mParent, op);
|
||||
tmpNode.mLeft = this;
|
||||
tmpNode.mRight = node;
|
||||
|
||||
if (mParent != null) {
|
||||
mParent.updateChild(this, tmpNode);
|
||||
}
|
||||
this.mParent = tmpNode;
|
||||
|
||||
node.mParent = tmpNode;
|
||||
|
||||
return tmpNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that replaces a child of the current node with a new node.
|
||||
* If the provided old child node was the left one, left will be replaced with
|
||||
* the new one. Same goes for the right one.
|
||||
*
|
||||
* @param oldChild Old child node to be replaced.
|
||||
* @param newChild New child node.
|
||||
*/
|
||||
private void updateChild(ConditionsTreeNode oldChild, ConditionsTreeNode newChild) {
|
||||
// we can compare objects id's because this is the desired behaviour in this case
|
||||
if (mLeft == oldChild) {
|
||||
mLeft = newChild;
|
||||
} else if (mRight == oldChild) {
|
||||
mRight = newChild;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive function to gather all the leaves in the subtree of which
|
||||
* this node is the root.
|
||||
*
|
||||
* @param leafSet Leafset that's being built.
|
||||
* @return Set of leaves being completed.
|
||||
*/
|
||||
private HashSet<ConditionsTreeNode> getLeafSet(HashSet<ConditionsTreeNode> leafSet) {
|
||||
// if we ended up in a leaf, add ourself and return
|
||||
if (mLeft == null && mRight == null) {
|
||||
leafSet.add(this);
|
||||
return leafSet;
|
||||
// we didn't end up in a leaf
|
||||
} else {
|
||||
if (mLeft != null) {
|
||||
mLeft.getLeafSet(leafSet);
|
||||
}
|
||||
|
||||
if (mRight != null) {
|
||||
mRight.getLeafSet(leafSet);
|
||||
}
|
||||
return leafSet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This applies the MPTT labeling to the subtree of which this node
|
||||
* is the root node.
|
||||
*
|
||||
* For a description on MPTT see:
|
||||
* http://www.sitepoint.com/hierarchical-data-database-2/
|
||||
*/
|
||||
private int applyMPTTLabel(int label) {
|
||||
mLeftMPTTMarker = label;
|
||||
if (mLeft != null){
|
||||
label = mLeft.applyMPTTLabel(label += 1);
|
||||
}
|
||||
if (mRight != null){
|
||||
label = mRight.applyMPTTLabel(label += 1);
|
||||
}
|
||||
++label;
|
||||
mRightMPTTMarker = label;
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Parcelable
|
||||
//
|
||||
// This whole class has to be parcelable because it's passed
|
||||
// on through intents.
|
||||
///////////////////////////////////////////////////////////////
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mValue.ordinal());
|
||||
dest.writeParcelable(mCondition, flags);
|
||||
dest.writeParcelable(mLeft, flags);
|
||||
dest.writeParcelable(mRight, flags);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<ConditionsTreeNode> CREATOR
|
||||
= new Parcelable.Creator<ConditionsTreeNode>() {
|
||||
public ConditionsTreeNode createFromParcel(Parcel in) {
|
||||
return new ConditionsTreeNode(in);
|
||||
}
|
||||
|
||||
public ConditionsTreeNode[] newArray(int size) {
|
||||
return new ConditionsTreeNode[size];
|
||||
}
|
||||
};
|
||||
|
||||
private ConditionsTreeNode(Parcel in) {
|
||||
mValue = OPERATOR.values()[in.readInt()];
|
||||
mCondition = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
|
||||
mLeft = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
|
||||
mRight = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
|
||||
mParent = null;
|
||||
if (mLeft != null) {
|
||||
mLeft.mParent = this;
|
||||
}
|
||||
if (mRight != null) {
|
||||
mRight.mParent = this;
|
||||
}
|
||||
}
|
||||
}
|
374
src/com/fsck/k9/search/LocalSearch.java
Normal file
374
src/com/fsck/k9/search/LocalSearch.java
Normal file
@ -0,0 +1,374 @@
|
||||
package com.fsck.k9.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.fsck.k9.mail.Flag;
|
||||
|
||||
/**
|
||||
* This class represents a local search.
|
||||
|
||||
* Removing conditions could be done through matching there unique id in the leafset and then
|
||||
* removing them from the tree.
|
||||
*
|
||||
* @author dzan
|
||||
*
|
||||
* TODO implement a complete addAllowedFolder method
|
||||
* TODO conflicting conditions check on add
|
||||
* TODO duplicate condition checking?
|
||||
* TODO assign each node a unique id that's used to retrieve it from the leaveset and remove.
|
||||
*
|
||||
*/
|
||||
|
||||
public class LocalSearch implements SearchSpecification {
|
||||
|
||||
private String mName;
|
||||
private boolean mPredefined;
|
||||
|
||||
// since the uuid isn't in the message table it's not in the tree neither
|
||||
private HashSet<String> mAccountUuids = new HashSet<String>();
|
||||
private ConditionsTreeNode mConditions = null;
|
||||
private HashSet<ConditionsTreeNode> mLeafSet = new HashSet<ConditionsTreeNode>();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Constructors
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Use this only if the search won't be saved. Saved searches need
|
||||
* a name!
|
||||
*/
|
||||
public LocalSearch(){}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
public LocalSearch(String name) {
|
||||
this.mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this constructor when you know what you'r doing. Normally it's only used
|
||||
* when restoring these search objects from the database.
|
||||
*
|
||||
* @param name Name of the search
|
||||
* @param searchConditions SearchConditions, may contains flags and folders
|
||||
* @param accounts Relative Account's uuid's
|
||||
* @param predefined Is this a predefined search or a user created one?
|
||||
*/
|
||||
protected LocalSearch(String name, ConditionsTreeNode searchConditions,
|
||||
String accounts, boolean predefined) {
|
||||
this(name);
|
||||
mConditions = searchConditions;
|
||||
mPredefined = predefined;
|
||||
mLeafSet = new HashSet<ConditionsTreeNode>();
|
||||
if (mConditions != null) {
|
||||
mLeafSet.addAll(mConditions.getLeafSet());
|
||||
}
|
||||
|
||||
// initialize accounts
|
||||
if (accounts != null) {
|
||||
for (String account : accounts.split(",")) {
|
||||
mAccountUuids.add(account);
|
||||
}
|
||||
} else {
|
||||
// impossible but still not unrecoverable
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Public manipulation methods
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Sets the name of the saved search. If one existed it will
|
||||
* be overwritten.
|
||||
*
|
||||
* @param name Name to be set.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new account to the search. When no accounts are
|
||||
* added manually we search all accounts on the device.
|
||||
*
|
||||
* @param uuid Uuid of the account to be added.
|
||||
*/
|
||||
public void addAccountUuid(String uuid) {
|
||||
if (uuid.equals(ALL_ACCOUNTS)) {
|
||||
mAccountUuids.clear();
|
||||
}
|
||||
mAccountUuids.add(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all the account uuids in the provided array to
|
||||
* be matched by the seach.
|
||||
*
|
||||
* @param accountUuids
|
||||
*/
|
||||
public void addAccountUuids(String[] accountUuids) {
|
||||
for (String acc : accountUuids) {
|
||||
addAccountUuid(acc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an account UUID from the current search.
|
||||
*
|
||||
* @param uuid Account UUID to remove.
|
||||
* @return True if removed, false otherwise.
|
||||
*/
|
||||
public boolean removeAccountUuid(String uuid) {
|
||||
return mAccountUuids.remove(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided node as the second argument of an AND
|
||||
* clause to this node.
|
||||
*
|
||||
* @param field Message table field to match against.
|
||||
* @param string Value to look for.
|
||||
* @param contains Attribute to use when matching.
|
||||
*
|
||||
* @throws IllegalConditionException
|
||||
*/
|
||||
public void and(SEARCHFIELD field, String value, ATTRIBUTE attribute) {
|
||||
and(new SearchCondition(field, attribute, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided condition as the second argument of an AND
|
||||
* clause to this node.
|
||||
*
|
||||
* @param condition Condition to 'AND' with.
|
||||
* @return New top AND node, new root.
|
||||
*/
|
||||
public ConditionsTreeNode and(SearchCondition condition) {
|
||||
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||
return and(tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided node as the second argument of an AND
|
||||
* clause to this node.
|
||||
*
|
||||
* @param node Node to 'AND' with.
|
||||
* @return New top AND node, new root.
|
||||
*/
|
||||
public ConditionsTreeNode and(ConditionsTreeNode node) {
|
||||
try {
|
||||
mLeafSet.add(node);
|
||||
|
||||
if (mConditions == null) {
|
||||
mConditions = node;
|
||||
return node;
|
||||
}
|
||||
|
||||
mConditions = mConditions.and(node);
|
||||
return mConditions;
|
||||
} catch (Exception e) {
|
||||
// IMPOSSIBLE!
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided condition as the second argument of an OR
|
||||
* clause to this node.
|
||||
*
|
||||
* @param condition Condition to 'OR' with.
|
||||
* @return New top OR node, new root.
|
||||
*/
|
||||
public ConditionsTreeNode or(SearchCondition condition) {
|
||||
ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
|
||||
return or(tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided node as the second argument of an OR
|
||||
* clause to this node.
|
||||
*
|
||||
* @param node Node to 'OR' with.
|
||||
* @return New top OR node, new root.
|
||||
*/
|
||||
public ConditionsTreeNode or(ConditionsTreeNode node) {
|
||||
try {
|
||||
mLeafSet.add(node);
|
||||
|
||||
if (mConditions == null) {
|
||||
mConditions = node;
|
||||
return node;
|
||||
}
|
||||
|
||||
mConditions = mConditions.or(node);
|
||||
return mConditions;
|
||||
} catch (Exception e) {
|
||||
// IMPOSSIBLE!
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the flags to this node as required flags. The
|
||||
* provided flags will be combined using AND with the root.
|
||||
*
|
||||
* @param requiredFlags Array of required flags.
|
||||
*/
|
||||
public void allRequiredFlags(Flag[] requiredFlags) {
|
||||
if (requiredFlags != null) {
|
||||
for (Flag f : requiredFlags) {
|
||||
and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.CONTAINS, f.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the flags to this node as forbidden flags. The
|
||||
* provided flags will be combined using AND with the root.
|
||||
*
|
||||
* @param forbiddenFlags Array of forbidden flags.
|
||||
*/
|
||||
public void allForbiddenFlags(Flag[] forbiddenFlags) {
|
||||
if (forbiddenFlags != null) {
|
||||
for (Flag f : forbiddenFlags) {
|
||||
and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.NOT_CONTAINS, f.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* FOR NOW: And the folder with the root.
|
||||
*
|
||||
* Add the folder as another folder to search in. The folder
|
||||
* will be added AND to the root if no 'folder subtree' was found.
|
||||
* Otherwise the folder will be added OR to that tree.
|
||||
*
|
||||
* @param name Name of the folder to add.
|
||||
*/
|
||||
public void addAllowedFolder(String name) {
|
||||
/*
|
||||
* TODO find folder sub-tree
|
||||
* - do and on root of it & rest of search
|
||||
* - do or between folder nodes
|
||||
*/
|
||||
and(new SearchCondition(SEARCHFIELD.FOLDER, ATTRIBUTE.EQUALS, name));
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO make this more advanced!
|
||||
* This is a temporarely solution that does NOT WORK for
|
||||
* real searches because of possible extra conditions to a folder requirement.
|
||||
*/
|
||||
public List<String> getFolderNames() {
|
||||
ArrayList<String> results = new ArrayList<String>();
|
||||
for (ConditionsTreeNode node : mLeafSet) {
|
||||
if (node.mCondition.field == SEARCHFIELD.FOLDER
|
||||
&& node.mCondition.attribute == ATTRIBUTE.EQUALS) {
|
||||
results.add(node.mCondition.value);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the leafset of the related condition tree.
|
||||
*
|
||||
* @return All the leaf conditions as a set.
|
||||
*/
|
||||
public Set<ConditionsTreeNode> getLeafSet() {
|
||||
return mLeafSet;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Public accesor methods
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Returns the name of the saved search.
|
||||
*
|
||||
* @return Name of the search.
|
||||
*/
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this search was hard coded and shipped with K-9
|
||||
*
|
||||
* @return True is search was shipped with K-9
|
||||
*/
|
||||
public boolean isPredefined() {
|
||||
return mPredefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the account uuids that this search will try to
|
||||
* match against.
|
||||
*
|
||||
* @return Array of account uuids.
|
||||
*/
|
||||
@Override
|
||||
public String[] getAccountUuids() {
|
||||
if (mAccountUuids.size() == 0) {
|
||||
return new String[] {SearchSpecification.ALL_ACCOUNTS};
|
||||
}
|
||||
|
||||
String[] tmp = new String[mAccountUuids.size()];
|
||||
mAccountUuids.toArray(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the condition tree.
|
||||
*
|
||||
* @return The root node of the related conditions tree.
|
||||
*/
|
||||
@Override
|
||||
public ConditionsTreeNode getConditions() {
|
||||
return mConditions;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Parcelable
|
||||
///////////////////////////////////////////////////////////////
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mName);
|
||||
dest.writeByte((byte) (mPredefined ? 1 : 0));
|
||||
dest.writeStringList(new ArrayList<String>(mAccountUuids));
|
||||
dest.writeParcelable(mConditions, flags);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<LocalSearch> CREATOR
|
||||
= new Parcelable.Creator<LocalSearch>() {
|
||||
public LocalSearch createFromParcel(Parcel in) {
|
||||
return new LocalSearch(in);
|
||||
}
|
||||
|
||||
public LocalSearch[] newArray(int size) {
|
||||
return new LocalSearch[size];
|
||||
}
|
||||
};
|
||||
|
||||
public LocalSearch(Parcel in) {
|
||||
mName = in.readString();
|
||||
mPredefined = in.readByte() == 1;
|
||||
mAccountUuids.addAll(in.createStringArrayList());
|
||||
mConditions = in.readParcelable(LocalSearch.class.getClassLoader());
|
||||
mLeafSet = mConditions.getLeafSet();
|
||||
}
|
||||
}
|
@ -1,19 +1,184 @@
|
||||
|
||||
package com.fsck.k9.search;
|
||||
|
||||
import com.fsck.k9.mail.Flag;
|
||||
|
||||
public interface SearchSpecification {
|
||||
|
||||
public Flag[] getRequiredFlags();
|
||||
|
||||
public Flag[] getForbiddenFlags();
|
||||
|
||||
public boolean isIntegrate();
|
||||
|
||||
public String getQuery();
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public interface SearchSpecification extends Parcelable {
|
||||
|
||||
/**
|
||||
* Get all the uuids of accounts this search acts on.
|
||||
* @return Array of uuids.
|
||||
*/
|
||||
public String[] getAccountUuids();
|
||||
|
||||
/**
|
||||
* Returns the search's name if it was named.
|
||||
* @return Name of the search.
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* Returns the root node of the condition tree accompanying
|
||||
* the search.
|
||||
*
|
||||
* @return Root node of conditions tree.
|
||||
*/
|
||||
public ConditionsTreeNode getConditions();
|
||||
|
||||
/*
|
||||
* Some meta names for certain conditions.
|
||||
*/
|
||||
public static final String ALL_ACCOUNTS = "allAccounts";
|
||||
public static final String GENERIC_INBOX_NAME = "genericInboxName";
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// ATTRIBUTE enum
|
||||
///////////////////////////////////////////////////////////////
|
||||
public enum ATTRIBUTE {
|
||||
CONTAINS(false), EQUALS(false), STARTSWITH(false), ENDSWITH(false),
|
||||
NOT_CONTAINS(true), NOT_EQUALS(true), NOT_STARTSWITH(true), NOT_ENDSWITH(true);
|
||||
|
||||
private boolean mNegation;
|
||||
|
||||
private ATTRIBUTE(boolean negation) {
|
||||
this.mNegation = negation;
|
||||
}
|
||||
|
||||
public String formQuery(String value) {
|
||||
String queryPart = "";
|
||||
|
||||
switch (this) {
|
||||
case NOT_CONTAINS:
|
||||
case CONTAINS:
|
||||
queryPart = "'%"+value+"%'";
|
||||
break;
|
||||
case NOT_EQUALS:
|
||||
case EQUALS:
|
||||
queryPart = "'"+value+"'";
|
||||
break;
|
||||
case NOT_STARTSWITH:
|
||||
case STARTSWITH:
|
||||
queryPart = "'%"+value+"'";
|
||||
break;
|
||||
case NOT_ENDSWITH:
|
||||
case ENDSWITH:
|
||||
queryPart = "'"+value+"%'";
|
||||
break;
|
||||
default: queryPart = "'"+value+"'";
|
||||
}
|
||||
|
||||
return (mNegation ? " NOT LIKE " : " LIKE ") + queryPart;
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// SEARCHFIELD enum
|
||||
///////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* Using an enum in order to have more robust code. Users ( & coders )
|
||||
* are prevented from passing illegal fields. No database overhead
|
||||
* when invalid fields passed.
|
||||
*
|
||||
* By result, only the fields in here are searchable.
|
||||
*
|
||||
* Fields not in here at this moment ( and by effect not searchable ):
|
||||
* id, html_content, internal_date, message_id,
|
||||
* preview, mime_type
|
||||
*
|
||||
*/
|
||||
public enum SEARCHFIELD {
|
||||
SUBJECT("subject"), DATE("date"), UID("uid"), FLAG("flags"),
|
||||
SENDER("sender_list"), TO("to_list"), CC("cc_list"), FOLDER("folder_id"),
|
||||
BCC("bcc_list"), REPLY_TO("reply_to_list"), MESSAGE("text_content"),
|
||||
ATTACHMENT_COUNT("attachment_count"), DELETED("deleted");
|
||||
|
||||
public String[] getFolderNames();
|
||||
private String dbName;
|
||||
|
||||
private SEARCHFIELD(String dbName) {
|
||||
this.dbName = dbName;
|
||||
}
|
||||
|
||||
public String getDatabaseName() {
|
||||
return dbName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// SearchCondition class
|
||||
///////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* This class represents 1 value for a certain search field. One
|
||||
* value consists of three things:
|
||||
* an attribute: equals, starts with, contains,...
|
||||
* a searchfield: date, flags, sender, subject,...
|
||||
* a value: "apple", "jesse",..
|
||||
*
|
||||
* @author dzan
|
||||
*/
|
||||
public class SearchCondition implements Parcelable{
|
||||
public String value;
|
||||
public ATTRIBUTE attribute;
|
||||
public SEARCHFIELD field;
|
||||
|
||||
public SearchCondition(SEARCHFIELD field, ATTRIBUTE attribute, String value) {
|
||||
this.value = value;
|
||||
this.attribute = attribute;
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
private SearchCondition(Parcel in) {
|
||||
this.value = in.readString();
|
||||
this.attribute = ATTRIBUTE.values()[in.readInt()];
|
||||
this.field = SEARCHFIELD.values()[in.readInt()];
|
||||
}
|
||||
|
||||
public String toHumanString() {
|
||||
return field.toString() + attribute.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return field.getDatabaseName() + attribute.formQuery(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof SearchCondition) {
|
||||
SearchCondition tmp = (SearchCondition) o;
|
||||
if (tmp.attribute == attribute
|
||||
&& tmp.value.equals(value)
|
||||
&& tmp.field == field) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(value);
|
||||
dest.writeInt(attribute.ordinal());
|
||||
dest.writeInt(field.ordinal());
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SearchCondition> CREATOR
|
||||
= new Parcelable.Creator<SearchCondition>() {
|
||||
public SearchCondition createFromParcel(Parcel in) {
|
||||
return new SearchCondition(in);
|
||||
}
|
||||
|
||||
public SearchCondition[] newArray(int size) {
|
||||
return new SearchCondition[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user