source: trunk/gsdl/src/java/org/nzdl/gsdl/LuceneWrap/GS2LuceneQuery.java@ 12656

Last change on this file since 12656 was 12656, checked in by mdewsnip, 18 years ago

Put old range filter stuff back, and added "-startresults" and "-endresults" support to return just the desired results, thus speeding up the query result parsing. Many thanks to John Thompson and DL Consulting Ltd.

  • Property svn:keywords set to Author Date Id Revision
File size: 13.0 KB
Line 
1/**
2 *
3 * @author [email protected]
4 * @author [email protected]
5 * @author [email protected]
6 * @author [email protected]
7 * @version
8 */
9
10package org.nzdl.gsdl.LuceneWrap;
11
12
13import java.io.*;
14import java.util.*;
15import java.util.regex.*;
16
17import org.apache.lucene.analysis.Analyzer;
18import org.apache.lucene.analysis.standard.StandardAnalyzer;
19import org.apache.lucene.document.Document;
20import org.apache.lucene.index.IndexReader;
21import org.apache.lucene.index.Term;
22import org.apache.lucene.index.TermFreqVector;
23import org.apache.lucene.queryParser.ParseException;
24import org.apache.lucene.queryParser.QueryParser;
25import org.apache.lucene.search.BooleanQuery.TooManyClauses;
26import org.apache.lucene.search.Filter;
27import org.apache.lucene.search.Hit;
28import org.apache.lucene.search.Hits;
29import org.apache.lucene.search.IndexSearcher;
30import org.apache.lucene.search.Query;
31import org.apache.lucene.search.QueryFilter;
32import org.apache.lucene.search.RangeFilter;
33import org.apache.lucene.search.Searcher;
34import org.apache.lucene.search.Sort;
35
36
37public class GS2LuceneQuery
38{
39 // Use the standard set of English stop words by default
40 static private String[] stop_words = StandardAnalyzer.STOP_WORDS;
41
42
43 static public void main (String args[])
44 {
45 if (args.length == 0) {
46 System.out.println("Usage: GS2LuceneQuery <index directory> [-fuzzy] [-filter filter_string] [-sort sort_field] [-dco AND|OR] [-startresults number -endresults number]");
47 return;
48 }
49
50 try {
51 Searcher searcher = new IndexSearcher(args[0]);
52 IndexReader reader = ((IndexSearcher) searcher).getIndexReader();
53
54 // Create one query parser with the standard set of stop words, and one with none
55 QueryParser query_parser = new QueryParser("TX", new StandardAnalyzer(stop_words));
56 QueryParser query_parser_no_stop_words = new QueryParser("TX", new StandardAnalyzer(new String[] { }));
57
58 Sort sorter = new Sort();
59 Filter filter = null;
60 boolean fuzzy = false;
61
62 // Paging
63 int start_results = 1;
64 int end_results = -1;
65
66 // New code to allow the default conjunction operator to be
67 // definable
68 String default_conjuction_operator = "OR";
69 for (int i = 1; i < args.length; i++)
70 {
71 if (args[i].equals("-sort"))
72 {
73 i++;
74 sorter = new Sort(args[i]);
75 }
76 if (args[i].equals("-filter"))
77 {
78 i++;
79
80 // Parse up filter
81 filter = parseFilterString(args[i]);
82 }
83 if (args[i].equals("-dco"))
84 {
85 i++;
86 default_conjuction_operator = args[i];
87 }
88 if (args[i].equals("-fuzzy"))
89 {
90 fuzzy = true;
91 }
92 if (args[i].equals("-startresults"))
93 {
94 i++;
95 if (args[i].matches("\\d+"))
96 {
97 start_results = Integer.parseInt(args[i]);
98 }
99 }
100 if (args[i].equals("-endresults"))
101 {
102 i++;
103 if (args[i].matches("\\d+"))
104 {
105 end_results = Integer.parseInt(args[i]);
106 }
107 }
108 }
109
110 // Lucene does "OR" queries by default; do an "AND" query if specified
111 if (default_conjuction_operator.equals("AND"))
112 {
113 query_parser.setDefaultOperator(query_parser.AND_OPERATOR);
114 query_parser_no_stop_words.setDefaultOperator(query_parser.AND_OPERATOR);
115 }
116
117 BufferedReader in = new BufferedReader(new InputStreamReader(System.in, "UTF-8"));
118 while (true) {
119 // Read the query from STDIN
120 String query_string = in.readLine();
121 if (query_string == null || query_string.length() == -1) {
122 break;
123 }
124 System.out.println("<ResultSet>");
125 System.out.println(" <QueryString>" + query_string + "</QueryString>");
126 if (filter != null)
127 {
128 System.out.println(" <FilterString>" + filter.toString() + "</FilterString>");
129 }
130
131 try {
132 Query query_including_stop_words = query_parser_no_stop_words.parse(query_string);
133 query_including_stop_words = query_including_stop_words.rewrite(reader);
134
135 Query query = parseQuery(reader, query_parser, query_string, fuzzy);
136 query = query.rewrite(reader);
137
138 // Perform the query
139 Hits hits;
140 if (filter != null) {
141 hits = searcher.search(query, filter, sorter);
142 }
143 else {
144 hits = searcher.search(query, sorter);
145 }
146
147 // Return the list of expanded query terms and their frequencies
148 HashMap term_counts = new HashMap();
149 HashMap term_fields = new HashMap();
150 HashSet terms = new HashSet();
151 query.extractTerms(terms);
152 Iterator iter = terms.iterator();
153 while (iter.hasNext())
154 {
155 Term term = (Term) iter.next();
156 // If you wanted to limit this to just TX terms add
157 // something like this:
158 //if (term.field().equals("TX"))
159 term_counts.put(term.text(), new Integer(0));
160 term_fields.put(term.text(), term.field());
161 }
162
163 // Do we need to use a hit iterator to get sorted results?
164 System.out.println(" <MatchingDocsInfo num=\"" + hits.length() + "\"/>");
165 System.out.println(" <StartResults num=\"" + start_results + "\" />");
166 System.out.println(" <EndsResults num=\"" + end_results + "\" />");
167
168 int counter = 1;
169 Iterator hit_iter = hits.iterator();
170 while (hit_iter.hasNext())
171 {
172 Hit hit = (Hit) hit_iter.next();
173 Document doc = hit.getDocument();
174 String node_id = doc.get("nodeID");
175
176 // May not be paging results
177 if (start_results == 1 && end_results == -1)
178 {
179 System.out.println(" <Match id=\"" + node_id + "\" />");
180 }
181 // Otherwise skip up until page offset
182 else if (start_results <= counter && counter <= end_results)
183 {
184 System.out.println(" <Match id=\"" + node_id + "\" />");
185 }
186 // And skip all the rest
187
188 // From the document, extract the Term Vector for the
189 // TX field
190 TermFreqVector term_freq_vector = reader.getTermFreqVector(hit.getId(), "TX");
191 if (term_freq_vector != null && term_freq_vector.size() > 0)
192 {
193 int[] term_frequencies = term_freq_vector.getTermFrequencies();
194 // Now for each query term, determine the
195 // frequency - which may of course be 0.
196 Set term_counts_set = term_counts.keySet();
197 Iterator terms_iter = term_counts_set.iterator();
198 while (terms_iter.hasNext())
199 {
200 String term = (String) terms_iter.next();
201 Integer count_integer = (Integer) term_counts.get(term);
202 int count = count_integer.intValue();
203 int index = term_freq_vector.indexOf(term);
204 // If the term has a count, then add to
205 // the total count for this term
206 if (index != -1)
207 {
208 count += term_frequencies[index];
209
210 }
211 // Store the result
212 term_counts.put(term, new Integer(count));
213 count_integer = null;
214 term = null;
215 }
216 terms_iter = null;
217 term_counts_set = null;
218 }
219 else
220 {
221 ///ystem.err.println("Error! Missing term vector for document " + hit.getId());
222 }
223 ++counter;
224 }
225
226 // Retrieve all the useful terms
227 Set term_counts_set = term_counts.keySet();
228 System.out.println(" <QueryTermsInfo num=\"" + term_counts_set.size() + "\"/>");
229 // Iterate over them
230 Iterator terms_iter = term_counts_set.iterator();
231 while (terms_iter.hasNext())
232 {
233 String term = (String) terms_iter.next();
234 Integer count = (Integer) term_counts.get(term);
235 String field = (String) term_fields.get(term);
236 System.out.println(" <Term value=\"" + term + "\" field=\"" + field + "\" freq=\"" + count.intValue() + "\" />");
237 count = null;
238 term = null;
239 }
240 // Cleanup
241 terms_iter = null;
242 term_counts_set = null;
243
244 // Return the list of stop words removed from the query
245 HashSet terms_including_stop_words = new HashSet();
246 query_including_stop_words.extractTerms(terms_including_stop_words);
247 Iterator terms_including_stop_words_iter = terms_including_stop_words.iterator();
248 while (terms_including_stop_words_iter.hasNext()) {
249 Term term = (Term) terms_including_stop_words_iter.next();
250 if (!terms.contains(term)) {
251 System.out.println(" <StopWord value=\"" + term.text() + "\"/>");
252 }
253 }
254 }
255 catch (ParseException parse_exception) {
256 System.out.println(" <Error type=\"PARSE_EXCEPTION\"/>");
257 }
258 catch (TooManyClauses too_many_clauses_exception) {
259 System.out.println(" <Error type=\"TOO_MANY_CLAUSES\"/>");
260 }
261
262 System.out.println("</ResultSet>");
263 }
264
265 searcher.close();
266 }
267 catch (IOException exception) {
268 exception.printStackTrace();
269 }
270 }
271
272
273 private static Query parseQuery(IndexReader reader, QueryParser query_parser, String query_string, boolean fuzzy)
274 throws java.io.IOException, org.apache.lucene.queryParser.ParseException
275 {
276 // Split query string into the search terms and the filter terms
277 // * The first +(...) term contains the search terms so count
278 // up '(' and stop when we finish matching ')'
279 int offset = 0;
280 int paren_count = 0;
281 boolean seen_paren = false;
282 while (offset < query_string.length() && (!seen_paren || paren_count > 0))
283 {
284 if (query_string.charAt(offset) == '(')
285 {
286 paren_count++;
287 seen_paren = true;
288 }
289 if (query_string.charAt(offset) == ')')
290 {
291 paren_count--;
292 }
293 offset++;
294 }
295 String query_prefix = query_string.substring(0, offset);
296 String query_suffix = query_string.substring(offset);
297
298 ///ystem.err.println("Prefix: " + query_prefix);
299 ///ystem.err.println("Suffix: " + query_suffix);
300
301 Query query = query_parser.parse(query_prefix);
302 query = query.rewrite(reader);
303
304 // If this is a fuzzy search, then we need to add the fuzzy
305 // flag to each of the query terms
306 if (fuzzy && query.toString().length() > 0)
307 {
308 // Revert the query to a string
309 System.err.println("Rewritten query: " + query.toString());
310 // Search through the string for TX:<term> query terms
311 // and append the ~ operator. Not that this search will
312 // not change phrase searches (TX:"<term> <term>") as
313 // fuzzy searching is not possible for these entries.
314 // Yahoo! Time for a state machine!
315 StringBuffer mutable_query_string = new StringBuffer(query.toString());
316 int o = 0; // Offset
317 // 0 = BASE, 1 = SEEN_T, 2 = SEEN_TX, 3 = SEEN_TX:
318 int s = 0; // State
319 while(o < mutable_query_string.length())
320 {
321 char c = mutable_query_string.charAt(o);
322 if (s == 0 && c == 'T')
323 {
324 ///ystem.err.println("Found T!");
325 s = 1;
326 }
327 else if (s == 1)
328 {
329 if (c == 'X')
330 {
331 ///ystem.err.println("Found X!");
332 s = 2;
333 }
334 else
335 {
336 s = 0; // Reset
337 }
338 }
339 else if (s == 2)
340 {
341 if (c == ':')
342 {
343 ///ystem.err.println("Found TX:!");
344 s = 3;
345 }
346 else
347 {
348 s = 0; // Reset
349 }
350 }
351 else if (s == 3)
352 {
353 // Don't process phrases
354 if (c == '"')
355 {
356 ///ystem.err.println("Stupid phrase...");
357 s = 0; // Reset
358 }
359 // Found the end of the term... add the
360 // fuzzy search indicator
361 // Nor outside the scope of parentheses
362 else if (Character.isWhitespace(c) || c == ')')
363 {
364 ///ystem.err.println("Yahoo! Found fuzzy term.");
365 mutable_query_string.insert(o, '~');
366 o++;
367 s = 0; // Reset
368 }
369 }
370 o++;
371 }
372 // If we were in the state of looking for the end of a
373 // term - then we just found it!
374 if (s == 3)
375 {
376 mutable_query_string.append('~');
377 }
378 // Reparse the query
379 ///ystem.err.println("Fuzzy query: " + mutable_query_string.toString() + query_suffix);
380 query = query_parser.parse(mutable_query_string.toString() + query_suffix);
381 }
382 else
383 {
384 query = query_parser.parse(query_prefix + query_suffix);
385 }
386
387 return query;
388 }
389
390
391 /**
392 * @todo Michael to comment
393 */
394 private static Filter parseFilterString(String filter_string)
395 {
396 Filter result = null;
397 Pattern pattern = Pattern.compile("\\s*\\+(\\w+)\\:([\\{\\[])(\\d+)\\s+TO\\s+(\\d+)([\\}\\]])\\s*");
398 Matcher matcher = pattern.matcher(filter_string);
399 if (matcher.matches())
400 {
401 String field_name = matcher.group(1);
402 boolean include_lower = matcher.group(2).equals("[");
403 String lower_term = matcher.group(3);
404 String upper_term = matcher.group(4);
405 boolean include_upper = matcher.group(5).equals("]");
406 result = new RangeFilter(field_name, lower_term, upper_term, include_lower, include_upper);
407 }
408 else
409 {
410 System.err.println("Error: Could not understand filter string \"" + filter_string + "\"");
411 }
412 return result;
413 }
414 /** parseFilterString() **/
415}
Note: See TracBrowser for help on using the repository browser.