001: package prefuse.data.search;
002:
003: import java.util.Iterator;
004: import java.util.StringTokenizer;
005:
006: import prefuse.data.Tuple;
007:
008: /**
009: * <p>
010: * SearchTupleSet implementation supporting word prefix searches over indexed
011: * Tuple data fields. This class uses a {@link Trie Trie} data structure
012: * to find search results quickly; however, only prefix matches will be
013: * identified as valid search matches. Multi-term search queries will result
014: * in the union of the results for the individual query terms. That is, Tuples
015: * that match any one of the terms will be included in the results.
016: * </p>
017: *
018: * <p>
019: * For more advanced search capabilities, see
020: * {@link KeywordSearchTupleSet} or {@link RegexSearchTupleSet}.
021: * </p>
022: *
023: * @version 1.0
024: * @author <a href="http://jheer.org">jeffrey heer</a>
025: * @see prefuse.data.query.SearchQueryBinding
026: */
027: public class PrefixSearchTupleSet extends SearchTupleSet {
028:
029: private Trie m_trie;
030: private Trie.TrieNode m_curNode;
031: private String m_delim = " \t\n\r";
032: private String m_query = "";
033:
034: /**
035: * Creates a new KeywordSearchFocusSet that is not case sensitive.
036: */
037: public PrefixSearchTupleSet() {
038: this (false);
039: }
040:
041: /**
042: * Creates a new KeywordSearchFocusSet with the indicated case sensitivity.
043: * @param caseSensitive true if the search routines should be case
044: * sensitive, false otherwise.
045: */
046: public PrefixSearchTupleSet(boolean caseSensitive) {
047: m_trie = new Trie(caseSensitive);
048: }
049:
050: /**
051: * Returns the delimiter string used to divide data values and
052: * queries into separate words. By default, the value consists
053: * of just whitespace characters.
054: * @return the delimiter string used. This is passed as an argument to a
055: * {@link java.util.StringTokenizer} instance that will tokenize the text.
056: * @see java.util.StringTokenizer
057: */
058: public String getDelimiterString() {
059: return m_delim;
060: }
061:
062: /**
063: * Sets the delimiter string used to divide data values and
064: * queries into separate words. By default, the delimiter consists
065: * of just whitespace characters.
066: * @param delim the delimiter string to use. This is passed as an argument
067: * to a {@link java.util.StringTokenizer} instance that will tokenize the
068: * text.
069: * @see java.util.StringTokenizer
070: */
071: public void setDelimiterString(String delim) {
072: m_delim = delim;
073: }
074:
075: /**
076: * @see prefuse.data.search.SearchTupleSet#getQuery()
077: */
078: public String getQuery() {
079: return m_query;
080: }
081:
082: /**
083: * Searches the indexed Tuple fields for matching string prefixes,
084: * adding the Tuple instances for each search match to this TupleSet.
085: * The query string is first broken up into separate terms, as determined
086: * by the current delimiter string. A search for each term is conducted,
087: * and all matching Tuples are included in the results.
088: * @param query the query string to search for.
089: * @see #setDelimiterString(String)
090: */
091: public void search(String query) {
092: if (query == null)
093: query = "";
094:
095: if (query.equals(m_query))
096: return;
097:
098: Tuple[] rem = clearInternal();
099: m_query = query;
100: StringTokenizer st = new StringTokenizer(m_query, m_delim);
101: if (!st.hasMoreTokens())
102: m_query = "";
103: while (st.hasMoreTokens())
104: prefixSearch(st.nextToken());
105: Tuple[] add = getTupleCount() > 0 ? toArray() : null;
106: fireTupleEvent(add, rem);
107: }
108:
109: /**
110: * Issues a prefix search and collects the results
111: */
112: private void prefixSearch(String query) {
113: m_curNode = m_trie.find(query);
114: if (m_curNode != null) {
115: Iterator iter = trieIterator();
116: while (iter.hasNext())
117: addInternal((Tuple) iter.next());
118: }
119: }
120:
121: /**
122: * Indexes the given field of the provided Tuple instance.
123: * @see prefuse.data.search.SearchTupleSet#index(prefuse.data.Tuple, java.lang.String)
124: */
125: public void index(Tuple t, String field) {
126: String s;
127: if ((s = t.getString(field)) == null)
128: return;
129: StringTokenizer st = new StringTokenizer(s, m_delim);
130: while (st.hasMoreTokens()) {
131: String tok = st.nextToken();
132: addString(tok, t);
133: }
134: }
135:
136: private void addString(String s, Tuple t) {
137: m_trie.addString(s, t);
138: }
139:
140: /**
141: * Returns true, as unidexing is supported by this class.
142: * @see prefuse.data.search.SearchTupleSet#isUnindexSupported()
143: */
144: public boolean isUnindexSupported() {
145: return true;
146: }
147:
148: /**
149: * @see prefuse.data.search.SearchTupleSet#unindex(prefuse.data.Tuple, java.lang.String)
150: */
151: public void unindex(Tuple t, String field) {
152: String s;
153: if ((s = t.getString(field)) == null)
154: return;
155: StringTokenizer st = new StringTokenizer(s, m_delim);
156: while (st.hasMoreTokens()) {
157: String tok = st.nextToken();
158: removeString(tok, t);
159: }
160: }
161:
162: /**
163: * Removes all search hits and clears out the index.
164: * @see prefuse.data.tuple.TupleSet#clear()
165: */
166: public void clear() {
167: m_trie = new Trie(m_trie.isCaseSensitive());
168: super .clear();
169: }
170:
171: private void removeString(String s, Tuple t) {
172: m_trie.removeString(s, t);
173: }
174:
175: private Iterator trieIterator() {
176: return m_trie.new TrieIterator(m_curNode);
177: }
178:
179: } // end of class PrefixSearchTupleSet
|