pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / contrib / highlighter / src / test / org / apache / lucene / search / highlight / HighlighterTest.java
1 package org.apache.lucene.search.highlight;
2
3 /**
4  * Licensed to the Apache Software Foundation (ASF) under one or more
5  * contributor license agreements.  See the NOTICE file distributed with
6  * this work for additional information regarding copyright ownership.
7  * The ASF licenses this file to You under the Apache License, Version 2.0
8  * (the "License"); you may not use this file except in compliance with
9  * the License.  You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19
20 import java.io.ByteArrayInputStream;
21 import java.io.IOException;
22 import java.io.Reader;
23 import java.io.StringReader;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.StringTokenizer;
32
33 import javax.xml.parsers.DocumentBuilder;
34 import javax.xml.parsers.DocumentBuilderFactory;
35
36 import org.apache.lucene.analysis.Analyzer;
37 import org.apache.lucene.analysis.BaseTokenStreamTestCase;
38 import org.apache.lucene.analysis.CharArraySet;
39 import org.apache.lucene.analysis.LowerCaseTokenizer;
40 import org.apache.lucene.analysis.MockAnalyzer;
41 import org.apache.lucene.analysis.MockTokenizer;
42 import org.apache.lucene.analysis.StopAnalyzer;
43 import org.apache.lucene.analysis.Token;
44 import org.apache.lucene.analysis.TokenStream;
45 import org.apache.lucene.analysis.standard.StandardAnalyzer;
46 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
47 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
48 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
49 import org.apache.lucene.document.Document;
50 import org.apache.lucene.document.Field;
51 import org.apache.lucene.document.NumericField;
52 import org.apache.lucene.document.Field.Index;
53 import org.apache.lucene.document.Field.Store;
54 import org.apache.lucene.index.IndexReader;
55 import org.apache.lucene.index.IndexWriter;
56 import org.apache.lucene.index.IndexWriterConfig;
57 import org.apache.lucene.index.Term;
58 import org.apache.lucene.index.IndexWriterConfig.OpenMode;
59 import org.apache.lucene.queryParser.ParseException;
60 import org.apache.lucene.queryParser.QueryParser;
61 import org.apache.lucene.search.BooleanQuery;
62 import org.apache.lucene.search.FilteredQuery;
63 import org.apache.lucene.search.IndexSearcher;
64 import org.apache.lucene.search.MultiPhraseQuery;
65 import org.apache.lucene.search.MultiSearcher;
66 import org.apache.lucene.search.MultiTermQuery;
67 import org.apache.lucene.search.NumericRangeQuery;
68 import org.apache.lucene.search.PhraseQuery;
69 import org.apache.lucene.search.Query;
70 import org.apache.lucene.search.TermQuery;
71 import org.apache.lucene.search.TermRangeFilter;
72 import org.apache.lucene.search.TopDocs;
73 import org.apache.lucene.search.WildcardQuery;
74 import org.apache.lucene.search.BooleanClause.Occur;
75 import org.apache.lucene.search.highlight.SynonymTokenizer.TestHighlightRunner;
76 import org.apache.lucene.search.regex.RegexQuery;
77 import org.apache.lucene.search.regex.SpanRegexQuery;
78 import org.apache.lucene.search.spans.SpanNearQuery;
79 import org.apache.lucene.search.spans.SpanNotQuery;
80 import org.apache.lucene.search.spans.SpanOrQuery;
81 import org.apache.lucene.search.spans.SpanQuery;
82 import org.apache.lucene.search.spans.SpanTermQuery;
83 import org.apache.lucene.store.Directory;
84 import org.apache.lucene.util.LuceneTestCase;
85 import org.w3c.dom.Element;
86 import org.w3c.dom.NodeList;
87
88 /**
89  * JUnit Test for Highlighter class.
90  *
91  */
92 public class HighlighterTest extends BaseTokenStreamTestCase implements Formatter {
93
94   private IndexReader reader;
95   static final String FIELD_NAME = "contents";
96   private static final String NUMERIC_FIELD_NAME = "nfield";
97   private Query query;
98   Directory ramDir;
99   public IndexSearcher searcher = null;
100   int numHighlights = 0;
101   final Analyzer analyzer = new StandardAnalyzer(TEST_VERSION_CURRENT);
102   TopDocs hits;
103
104   String[] texts = {
105       "Hello this is a piece of text that is very long and contains too much preamble and the meat is really here which says kennedy has been shot",
106       "This piece of text refers to Kennedy at the beginning then has a longer piece of text that is very long in the middle and finally ends with another reference to Kennedy",
107       "JFK has been shot", "John Kennedy has been shot",
108       "This text has a typo in referring to Keneddy",
109       "wordx wordy wordz wordx wordy wordx worda wordb wordy wordc", "y z x y z a b", "lets is a the lets is a the lets is a the lets" };
110
111   public void testQueryScorerHits() throws Exception {
112     Analyzer analyzer = new MockAnalyzer(random, MockTokenizer.SIMPLE, true);
113     QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, FIELD_NAME, analyzer);
114     query = qp.parse("\"very long\"");
115     searcher = new IndexSearcher(reader);
116     TopDocs hits = searcher.search(query, 10);
117     
118     QueryScorer scorer = new QueryScorer(query, FIELD_NAME);
119     Highlighter highlighter = new Highlighter(scorer);
120
121
122     for (int i = 0; i < hits.scoreDocs.length; i++) {
123       Document doc = searcher.doc(hits.scoreDocs[i].doc);
124       String storedField = doc.get(FIELD_NAME);
125
126       TokenStream stream = TokenSources.getAnyTokenStream(searcher
127           .getIndexReader(), hits.scoreDocs[i].doc, FIELD_NAME, doc, analyzer);
128
129       Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
130
131       highlighter.setTextFragmenter(fragmenter);
132
133       String fragment = highlighter.getBestFragment(stream, storedField);
134
135       if (VERBOSE) System.out.println(fragment);
136     }
137     searcher.close();
138   }
139   
140   public void testHighlightingWithDefaultField() throws Exception {
141
142     String s1 = "I call our world Flatland, not because we call it so,";
143
144     QueryParser parser = new QueryParser(TEST_VERSION_CURRENT, FIELD_NAME, new StandardAnalyzer(TEST_VERSION_CURRENT));
145
146     // Verify that a query against the default field results in text being
147     // highlighted
148     // regardless of the field name.
149     Query q = parser.parse("\"world Flatland\"~3");
150     String expected = "I call our <B>world</B> <B>Flatland</B>, not because we call it so,";
151     String observed = highlightField(q, "SOME_FIELD_NAME", s1);
152     if (VERBOSE) System.out.println("Expected: \"" + expected + "\n" + "Observed: \"" + observed);
153     assertEquals("Query in the default field results in text for *ANY* field being highlighted",
154         expected, observed);
155
156     // Verify that a query against a named field does not result in any
157     // highlighting
158     // when the query field name differs from the name of the field being
159     // highlighted,
160     // which in this example happens to be the default field name.
161     q = parser.parse("text:\"world Flatland\"~3");
162     expected = s1;
163     observed = highlightField(q, FIELD_NAME, s1);
164     if (VERBOSE) System.out.println("Expected: \"" + expected + "\n" + "Observed: \"" + observed);
165     assertEquals(
166         "Query in a named field does not result in highlighting when that field isn't in the query",
167         s1, highlightField(q, FIELD_NAME, s1));
168   }
169
170   /**
171    * This method intended for use with <tt>testHighlightingWithDefaultField()</tt>
172  * @throws InvalidTokenOffsetsException 
173    */
174   private static String highlightField(Query query, String fieldName, String text)
175       throws IOException, InvalidTokenOffsetsException {
176     TokenStream tokenStream = new StandardAnalyzer(TEST_VERSION_CURRENT).tokenStream(fieldName, new StringReader(text));
177     // Assuming "<B>", "</B>" used to highlight
178     SimpleHTMLFormatter formatter = new SimpleHTMLFormatter();
179     QueryScorer scorer = new QueryScorer(query, fieldName, FIELD_NAME);
180     Highlighter highlighter = new Highlighter(formatter, scorer);
181     highlighter.setTextFragmenter(new SimpleFragmenter(Integer.MAX_VALUE));
182
183     String rv = highlighter.getBestFragments(tokenStream, text, 1, "(FIELD TEXT TRUNCATED)");
184     return rv.length() == 0 ? text : rv;
185   }
186
187   public void testSimpleSpanHighlighter() throws Exception {
188     doSearching("Kennedy");
189
190     int maxNumFragmentsRequired = 2;
191
192     QueryScorer scorer = new QueryScorer(query, FIELD_NAME);
193     Highlighter highlighter = new Highlighter(scorer);
194     
195     for (int i = 0; i < hits.totalHits; i++) {
196       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
197       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME,
198           new StringReader(text));
199       highlighter.setTextFragmenter(new SimpleFragmenter(40));
200
201       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
202           "...");
203       if (VERBOSE) System.out.println("\t" + result);
204     }
205
206     // Not sure we can assert anything here - just running to check we dont
207     // throw any exceptions
208   }
209
210   // LUCENE-1752
211   public void testRepeatingTermsInMultBooleans() throws Exception {
212     String content = "x y z a b c d e f g b c g";
213     String ph1 = "\"a b c d\"";
214     String ph2 = "\"b c g\"";
215     String f1 = "f1";
216     String f2 = "f2";
217     String f1c = f1 + ":";
218     String f2c = f2 + ":";
219     String q = "(" + f1c + ph1 + " OR " + f2c + ph1 + ") AND (" + f1c + ph2
220         + " OR " + f2c + ph2 + ")";
221     Analyzer analyzer = new MockAnalyzer(random, MockTokenizer.WHITESPACE, false);
222     QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, f1, analyzer);
223     Query query = qp.parse(q);
224
225     QueryScorer scorer = new QueryScorer(query, f1);
226     scorer.setExpandMultiTermQuery(false);
227
228     Highlighter h = new Highlighter(this, scorer);
229
230     h.getBestFragment(analyzer, f1, content);
231
232     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
233         numHighlights == 7);
234   }
235
236   public void testSimpleQueryScorerPhraseHighlighting() throws Exception {
237     doSearching("\"very long and contains\"");
238
239     int maxNumFragmentsRequired = 2;
240
241     QueryScorer scorer = new QueryScorer(query, FIELD_NAME);
242     Highlighter highlighter = new Highlighter(this, scorer);
243     
244     for (int i = 0; i < hits.totalHits; i++) {
245       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
246       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
247
248       highlighter.setTextFragmenter(new SimpleFragmenter(40));
249
250       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
251           "...");
252       if (VERBOSE) System.out.println("\t" + result);
253     }
254
255     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
256         numHighlights == 3);
257     
258     numHighlights = 0;
259     doSearching("\"This piece of text refers to Kennedy\"");
260
261     maxNumFragmentsRequired = 2;
262
263     scorer = new QueryScorer(query, FIELD_NAME);
264     highlighter = new Highlighter(this, scorer);
265     
266     for (int i = 0; i < hits.totalHits; i++) {
267       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
268       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
269
270       highlighter.setTextFragmenter(new SimpleFragmenter(40));
271
272       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
273           "...");
274       if (VERBOSE) System.out.println("\t" + result);
275     }
276
277     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
278         numHighlights == 4);
279     
280     numHighlights = 0;
281     doSearching("\"lets is a the lets is a the lets is a the lets\"");
282
283     maxNumFragmentsRequired = 2;
284
285     scorer = new QueryScorer(query, FIELD_NAME);
286     highlighter = new Highlighter(this, scorer);
287     
288     for (int i = 0; i < hits.totalHits; i++) {
289       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
290       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
291
292       highlighter.setTextFragmenter(new SimpleFragmenter(40));
293
294       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
295           "...");
296       if (VERBOSE) System.out.println("\t" + result);
297     }
298
299     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
300         numHighlights == 4);
301     
302   }
303   
304   public void testSpanRegexQuery() throws Exception {
305     query = new SpanOrQuery(new SpanQuery [] {
306         new SpanRegexQuery(new Term(FIELD_NAME, "ken.*")) });
307     searcher = new IndexSearcher(reader);
308     hits = searcher.search(query, 100);
309     int maxNumFragmentsRequired = 2;
310
311     QueryScorer scorer = new QueryScorer(query, FIELD_NAME);
312     Highlighter highlighter = new Highlighter(this, scorer);
313     
314     for (int i = 0; i < hits.totalHits; i++) {
315       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
316       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
317
318       highlighter.setTextFragmenter(new SimpleFragmenter(40));
319
320       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
321           "...");
322       if (VERBOSE) System.out.println("\t" + result);
323     }
324     
325     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
326         numHighlights == 5);
327   }
328   
329   public void testRegexQuery() throws Exception {
330     query = new RegexQuery(new Term(FIELD_NAME, "ken.*"));
331     searcher = new IndexSearcher(reader);
332     hits = searcher.search(query, 100);
333     int maxNumFragmentsRequired = 2;
334
335     QueryScorer scorer = new QueryScorer(query, FIELD_NAME);
336     Highlighter highlighter = new Highlighter(this, scorer);
337     
338     for (int i = 0; i < hits.totalHits; i++) {
339       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
340       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
341
342       highlighter.setTextFragmenter(new SimpleFragmenter(40));
343
344       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
345           "...");
346       if (VERBOSE) System.out.println("\t" + result);
347     }
348     
349     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
350         numHighlights == 5);
351   }
352   
353   public void testNumericRangeQuery() throws Exception {
354     // doesn't currently highlight, but make sure it doesn't cause exception either
355     query = NumericRangeQuery.newIntRange(NUMERIC_FIELD_NAME, 2, 6, true, true);
356     searcher = new IndexSearcher(reader);
357     hits = searcher.search(query, 100);
358     int maxNumFragmentsRequired = 2;
359
360     QueryScorer scorer = new QueryScorer(query, FIELD_NAME);
361     Highlighter highlighter = new Highlighter(this, scorer);
362     
363     for (int i = 0; i < hits.totalHits; i++) {
364       String text = searcher.doc(hits.scoreDocs[i].doc).get(NUMERIC_FIELD_NAME);
365       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
366
367       highlighter.setTextFragmenter(new SimpleFragmenter(40));
368
369 //      String result = 
370         highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,"...");
371       //if (VERBOSE) System.out.println("\t" + result);
372     }
373
374
375   }
376
377   public void testSimpleQueryScorerPhraseHighlighting2() throws Exception {
378     doSearching("\"text piece long\"~5");
379
380     int maxNumFragmentsRequired = 2;
381
382     QueryScorer scorer =  new QueryScorer(query, FIELD_NAME);
383     Highlighter highlighter = new Highlighter(this,scorer);
384     highlighter.setTextFragmenter(new SimpleFragmenter(40));
385     
386     for (int i = 0; i < hits.totalHits; i++) {
387       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
388       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
389
390       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
391           "...");
392       if (VERBOSE) System.out.println("\t" + result);
393     }
394
395     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
396         numHighlights == 6);
397   }
398
399   public void testSimpleQueryScorerPhraseHighlighting3() throws Exception {
400     doSearching("\"x y z\"");
401
402     int maxNumFragmentsRequired = 2;
403
404     for (int i = 0; i < hits.totalHits; i++) {
405       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
406       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
407       QueryScorer scorer = new QueryScorer(query, FIELD_NAME);
408       Highlighter highlighter = new Highlighter(this, scorer);
409
410       highlighter.setTextFragmenter(new SimpleFragmenter(40));
411
412       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
413           "...");
414       if (VERBOSE) System.out.println("\t" + result);
415
416       assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
417           numHighlights == 3);
418     }
419   }
420   
421   public void testSimpleSpanFragmenter() throws Exception {
422     doSearching("\"piece of text that is very long\"");
423
424     int maxNumFragmentsRequired = 2;
425
426     QueryScorer scorer = new QueryScorer(query, FIELD_NAME);
427     Highlighter highlighter = new Highlighter(this, scorer);
428   
429     for (int i = 0; i < hits.totalHits; i++) {
430       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
431       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
432
433       highlighter.setTextFragmenter(new SimpleSpanFragmenter(scorer, 5));
434
435       String result = highlighter.getBestFragments(tokenStream, text,
436           maxNumFragmentsRequired, "...");
437       if (VERBOSE) System.out.println("\t" + result);
438
439     }
440     
441     doSearching("\"been shot\"");
442
443     maxNumFragmentsRequired = 2;
444     
445     scorer = new QueryScorer(query, FIELD_NAME);
446     highlighter = new Highlighter(this, scorer);
447
448     for (int i = 0; i < hits.totalHits; i++) {
449       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
450       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
451
452       highlighter.setTextFragmenter(new SimpleSpanFragmenter(scorer, 20));
453
454       String result = highlighter.getBestFragments(tokenStream, text,
455           maxNumFragmentsRequired, "...");
456       if (VERBOSE) System.out.println("\t" + result);
457
458     }
459   }
460   
461   // position sensitive query added after position insensitive query
462   public void testPosTermStdTerm() throws Exception {
463     doSearching("y \"x y z\"");
464
465     int maxNumFragmentsRequired = 2;
466     
467     QueryScorer scorer = new QueryScorer(query, FIELD_NAME);
468     Highlighter highlighter = new Highlighter(this,scorer);
469     
470     for (int i = 0; i < hits.totalHits; i++) {
471       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
472       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME,new StringReader(text));
473
474       highlighter.setTextFragmenter(new SimpleFragmenter(40));
475
476       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
477           "...");
478       if (VERBOSE) System.out.println("\t" + result);
479
480       assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
481           numHighlights == 4);
482     }
483   }
484
485   public void testQueryScorerMultiPhraseQueryHighlighting() throws Exception {
486     MultiPhraseQuery mpq = new MultiPhraseQuery();
487
488     mpq.add(new Term[] { new Term(FIELD_NAME, "wordx"), new Term(FIELD_NAME, "wordb") });
489     mpq.add(new Term(FIELD_NAME, "wordy"));
490
491     doSearching(mpq);
492
493     final int maxNumFragmentsRequired = 2;
494     assertExpectedHighlightCount(maxNumFragmentsRequired, 6);
495   }
496
497   public void testQueryScorerMultiPhraseQueryHighlightingWithGap() throws Exception {
498     MultiPhraseQuery mpq = new MultiPhraseQuery();
499
500     /*
501      * The toString of MultiPhraseQuery doesn't work so well with these
502      * out-of-order additions, but the Query itself seems to match accurately.
503      */
504
505     mpq.add(new Term[] { new Term(FIELD_NAME, "wordz") }, 2);
506     mpq.add(new Term[] { new Term(FIELD_NAME, "wordx") }, 0);
507
508     doSearching(mpq);
509
510     final int maxNumFragmentsRequired = 1;
511     final int expectedHighlights = 2;
512
513     assertExpectedHighlightCount(maxNumFragmentsRequired, expectedHighlights);
514   }
515
516   public void testNearSpanSimpleQuery() throws Exception {
517     doSearching(new SpanNearQuery(new SpanQuery[] {
518         new SpanTermQuery(new Term(FIELD_NAME, "beginning")),
519         new SpanTermQuery(new Term(FIELD_NAME, "kennedy")) }, 3, false));
520
521     TestHighlightRunner helper = new TestHighlightRunner() {
522
523       @Override
524       public void run() throws Exception {
525         mode = QUERY;
526         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
527       }
528     };
529
530     helper.run();
531
532     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
533         numHighlights == 2);
534   }
535
536   public void testSimpleQueryTermScorerHighlighter() throws Exception {
537     doSearching("Kennedy");
538     Highlighter highlighter = new Highlighter(new QueryTermScorer(query));
539     highlighter.setTextFragmenter(new SimpleFragmenter(40));
540     int maxNumFragmentsRequired = 2;
541     for (int i = 0; i < hits.totalHits; i++) {
542       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
543       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
544
545       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
546           "...");
547       if (VERBOSE) System.out.println("\t" + result);
548     }
549     // Not sure we can assert anything here - just running to check we dont
550     // throw any exceptions
551   }
552
553   public void testSpanHighlighting() throws Exception {
554     Query query1 = new SpanNearQuery(new SpanQuery[] {
555         new SpanTermQuery(new Term(FIELD_NAME, "wordx")),
556         new SpanTermQuery(new Term(FIELD_NAME, "wordy")) }, 1, false);
557     Query query2 = new SpanNearQuery(new SpanQuery[] {
558         new SpanTermQuery(new Term(FIELD_NAME, "wordy")),
559         new SpanTermQuery(new Term(FIELD_NAME, "wordc")) }, 1, false);
560     BooleanQuery bquery = new BooleanQuery();
561     bquery.add(query1, Occur.SHOULD);
562     bquery.add(query2, Occur.SHOULD);
563     doSearching(bquery);
564     TestHighlightRunner helper = new TestHighlightRunner() {
565
566       @Override
567       public void run() throws Exception {
568         mode = QUERY;
569         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
570       }
571     };
572
573     helper.run();
574     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
575         numHighlights == 7);
576   }
577
578   public void testNotSpanSimpleQuery() throws Exception {
579     doSearching(new SpanNotQuery(new SpanNearQuery(new SpanQuery[] {
580         new SpanTermQuery(new Term(FIELD_NAME, "shot")),
581         new SpanTermQuery(new Term(FIELD_NAME, "kennedy")) }, 3, false), new SpanTermQuery(
582         new Term(FIELD_NAME, "john"))));
583     TestHighlightRunner helper = new TestHighlightRunner() {
584
585       @Override
586       public void run() throws Exception {
587         mode = QUERY;
588         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
589       }
590     };
591
592     helper.run();
593     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
594         numHighlights == 4);
595   }
596
597   public void testGetBestFragmentsSimpleQuery() throws Exception {
598     TestHighlightRunner helper = new TestHighlightRunner() {
599
600       @Override
601       public void run() throws Exception {
602         numHighlights = 0;
603         doSearching("Kennedy");
604         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
605         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
606             numHighlights == 4);
607       }
608     };
609
610     helper.start();
611   }
612
613   public void testGetFuzzyFragments() throws Exception {
614     TestHighlightRunner helper = new TestHighlightRunner() {
615
616       @Override
617       public void run() throws Exception {
618         numHighlights = 0;
619         doSearching("Kinnedy~");
620         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this, true);
621         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
622             numHighlights == 5);
623       }
624     };
625
626     helper.start();
627   }
628
629   public void testGetWildCardFragments() throws Exception {
630     TestHighlightRunner helper = new TestHighlightRunner() {
631
632       @Override
633       public void run() throws Exception {
634         numHighlights = 0;
635         doSearching("K?nnedy");
636         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
637         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
638             numHighlights == 4);
639       }
640     };
641
642     helper.start();
643   }
644
645   public void testGetMidWildCardFragments() throws Exception {
646     TestHighlightRunner helper = new TestHighlightRunner() {
647
648       @Override
649       public void run() throws Exception {
650         numHighlights = 0;
651         doSearching("K*dy");
652         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
653         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
654             numHighlights == 5);
655       }
656     };
657
658     helper.start();
659   }
660
661   public void testGetRangeFragments() throws Exception {
662     TestHighlightRunner helper = new TestHighlightRunner() {
663
664       @Override
665       public void run() throws Exception {
666         numHighlights = 0;
667         String queryString = FIELD_NAME + ":[kannedy TO kznnedy]";
668
669         // Need to explicitly set the QueryParser property to use TermRangeQuery
670         // rather
671         // than RangeFilters
672         QueryParser parser = new QueryParser(TEST_VERSION_CURRENT, FIELD_NAME, analyzer);
673         parser.setMultiTermRewriteMethod(MultiTermQuery.SCORING_BOOLEAN_QUERY_REWRITE);
674         query = parser.parse(queryString);
675         doSearching(query);
676
677         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
678         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
679             numHighlights == 5);
680       }
681     };
682
683     helper.start();
684   }
685
686   public void testConstantScoreMultiTermQuery() throws Exception {
687
688     numHighlights = 0;
689
690     query = new WildcardQuery(new Term(FIELD_NAME, "ken*"));
691     ((WildcardQuery)query).setRewriteMethod(MultiTermQuery.CONSTANT_SCORE_FILTER_REWRITE);
692     searcher = new IndexSearcher(reader);
693     // can't rewrite ConstantScore if you want to highlight it -
694     // it rewrites to ConstantScoreQuery which cannot be highlighted
695     // query = unReWrittenQuery.rewrite(reader);
696     if (VERBOSE) System.out.println("Searching for: " + query.toString(FIELD_NAME));
697     hits = searcher.search(query, null, 1000);
698
699     for (int i = 0; i < hits.totalHits; i++) {
700       String text = searcher.doc(hits.scoreDocs[i].doc).get(HighlighterTest.FIELD_NAME);
701       int maxNumFragmentsRequired = 2;
702       String fragmentSeparator = "...";
703       QueryScorer scorer = null;
704       TokenStream tokenStream = null;
705
706       tokenStream = analyzer.tokenStream(HighlighterTest.FIELD_NAME, new StringReader(text));
707       
708       scorer = new QueryScorer(query, HighlighterTest.FIELD_NAME);
709
710       Highlighter highlighter = new Highlighter(this, scorer);
711
712       highlighter.setTextFragmenter(new SimpleFragmenter(20));
713
714       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
715           fragmentSeparator);
716       if (VERBOSE) System.out.println("\t" + result);
717     }
718     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
719         numHighlights == 5);
720     
721     // try null field
722     
723     hits = searcher.search(query, null, 1000);
724     
725     numHighlights = 0;
726
727     for (int i = 0; i < hits.totalHits; i++) {
728       String text = searcher.doc(hits.scoreDocs[i].doc).get(HighlighterTest.FIELD_NAME);
729       int maxNumFragmentsRequired = 2;
730       String fragmentSeparator = "...";
731       QueryScorer scorer = null;
732       TokenStream tokenStream = null;
733
734       tokenStream = analyzer.tokenStream(HighlighterTest.FIELD_NAME, new StringReader(text));
735       
736       scorer = new QueryScorer(query, null);
737
738       Highlighter highlighter = new Highlighter(this, scorer);
739
740       highlighter.setTextFragmenter(new SimpleFragmenter(20));
741
742       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
743           fragmentSeparator);
744       if (VERBOSE) System.out.println("\t" + result);
745     }
746     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
747         numHighlights == 5);
748     
749     // try default field
750     
751     hits = searcher.search(query, null, 1000);
752     
753     numHighlights = 0;
754
755     for (int i = 0; i < hits.totalHits; i++) {
756       String text = searcher.doc(hits.scoreDocs[i].doc).get(HighlighterTest.FIELD_NAME);
757       int maxNumFragmentsRequired = 2;
758       String fragmentSeparator = "...";
759       QueryScorer scorer = null;
760       TokenStream tokenStream = null;
761
762       tokenStream = analyzer.tokenStream(HighlighterTest.FIELD_NAME, new StringReader(text));
763       
764       scorer = new QueryScorer(query, "random_field", HighlighterTest.FIELD_NAME);
765
766       Highlighter highlighter = new Highlighter(this, scorer);
767
768       highlighter.setTextFragmenter(new SimpleFragmenter(20));
769
770       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
771           fragmentSeparator);
772       if (VERBOSE) System.out.println("\t" + result);
773     }
774     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
775         numHighlights == 5);
776   }
777
778   public void testGetBestFragmentsPhrase() throws Exception {
779     TestHighlightRunner helper = new TestHighlightRunner() {
780
781       @Override
782       public void run() throws Exception {
783         numHighlights = 0;
784         doSearching("\"John Kennedy\"");
785         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
786         // Currently highlights "John" and "Kennedy" separately
787         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
788             numHighlights == 2);
789       }
790     };
791
792     helper.start();
793   }
794
795   public void testGetBestFragmentsQueryScorer() throws Exception {
796     TestHighlightRunner helper = new TestHighlightRunner() {
797
798       @Override
799       public void run() throws Exception {
800         numHighlights = 0;
801         SpanQuery clauses[] = { new SpanTermQuery(new Term("contents", "john")),
802             new SpanTermQuery(new Term("contents", "kennedy")), };
803
804         SpanNearQuery snq = new SpanNearQuery(clauses, 1, true);
805         doSearching(snq);
806         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
807         // Currently highlights "John" and "Kennedy" separately
808         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
809             numHighlights == 2);
810       }
811     };
812
813     helper.start();
814   }
815
816   public void testOffByOne() throws Exception {
817     TestHighlightRunner helper = new TestHighlightRunner() {
818
819       @Override
820       public void run() throws Exception {
821         TermQuery query = new TermQuery(new Term("data", "help"));
822         Highlighter hg = new Highlighter(new SimpleHTMLFormatter(), new QueryTermScorer(query));
823         hg.setTextFragmenter(new NullFragmenter());
824
825         String match = null;
826         match = hg.getBestFragment(analyzer, "data", "help me [54-65]");
827         assertEquals("<B>help</B> me [54-65]", match);
828
829       }
830     };
831
832     helper.start();
833   }
834
835   public void testGetBestFragmentsFilteredQuery() throws Exception {
836     TestHighlightRunner helper = new TestHighlightRunner() {
837
838       @Override
839       public void run() throws Exception {
840         numHighlights = 0;
841         TermRangeFilter rf = new TermRangeFilter("contents", "john", "john", true, true);
842         SpanQuery clauses[] = { new SpanTermQuery(new Term("contents", "john")),
843             new SpanTermQuery(new Term("contents", "kennedy")), };
844         SpanNearQuery snq = new SpanNearQuery(clauses, 1, true);
845         FilteredQuery fq = new FilteredQuery(snq, rf);
846
847         doSearching(fq);
848         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
849         // Currently highlights "John" and "Kennedy" separately
850         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
851             numHighlights == 2);
852       }
853     };
854
855     helper.start();
856   }
857
858   public void testGetBestFragmentsFilteredPhraseQuery() throws Exception {
859     TestHighlightRunner helper = new TestHighlightRunner() {
860
861       @Override
862       public void run() throws Exception {
863         numHighlights = 0;
864         TermRangeFilter rf = new TermRangeFilter("contents", "john", "john", true, true);
865         PhraseQuery pq = new PhraseQuery();
866         pq.add(new Term("contents", "john"));
867         pq.add(new Term("contents", "kennedy"));
868         FilteredQuery fq = new FilteredQuery(pq, rf);
869
870         doSearching(fq);
871         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
872         // Currently highlights "John" and "Kennedy" separately
873         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
874             numHighlights == 2);
875       }
876     };
877
878     helper.start();
879   }
880
881   public void testGetBestFragmentsMultiTerm() throws Exception {
882     TestHighlightRunner helper = new TestHighlightRunner() {
883
884       @Override
885       public void run() throws Exception {
886         numHighlights = 0;
887         doSearching("John Kenn*");
888         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
889         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
890             numHighlights == 5);
891       }
892     };
893
894     helper.start();
895   }
896
897   public void testGetBestFragmentsWithOr() throws Exception {
898     TestHighlightRunner helper = new TestHighlightRunner() {
899
900       @Override
901       public void run() throws Exception {
902         numHighlights = 0;
903         doSearching("JFK OR Kennedy");
904         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
905         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
906             numHighlights == 5);
907       }
908     };
909     helper.start();
910   }
911
912   public void testGetBestSingleFragment() throws Exception {
913
914     TestHighlightRunner helper = new TestHighlightRunner() {
915
916       @Override
917       public void run() throws Exception {
918         doSearching("Kennedy");
919         numHighlights = 0;
920         for (int i = 0; i < hits.totalHits; i++) {
921           String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
922           TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
923
924           Highlighter highlighter = getHighlighter(query, FIELD_NAME, tokenStream,
925               HighlighterTest.this);
926           highlighter.setTextFragmenter(new SimpleFragmenter(40));
927           String result = highlighter.getBestFragment(tokenStream, text);
928           if (VERBOSE) System.out.println("\t" + result);
929         }
930         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
931             numHighlights == 4);
932
933         numHighlights = 0;
934         for (int i = 0; i < hits.totalHits; i++) {
935           String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
936           TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
937           Highlighter highlighter = getHighlighter(query, FIELD_NAME, tokenStream,
938               HighlighterTest.this);
939           highlighter.getBestFragment(analyzer, FIELD_NAME, text);
940         }
941         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
942             numHighlights == 4);
943
944         numHighlights = 0;
945         for (int i = 0; i < hits.totalHits; i++) {
946           String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
947
948           TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
949           Highlighter highlighter = getHighlighter(query, FIELD_NAME, tokenStream,
950               HighlighterTest.this);
951           highlighter.getBestFragments(analyzer, FIELD_NAME, text, 10);
952         }
953         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
954             numHighlights == 4);
955
956       }
957
958     };
959
960     helper.start();
961
962   }
963
964   public void testGetBestSingleFragmentWithWeights() throws Exception {
965
966     TestHighlightRunner helper = new TestHighlightRunner() {
967
968       @Override
969       public void run() throws Exception {
970         WeightedSpanTerm[] wTerms = new WeightedSpanTerm[2];
971         wTerms[0] = new WeightedSpanTerm(10f, "hello");
972
973         List<PositionSpan> positionSpans = new ArrayList<PositionSpan>();
974         positionSpans.add(new PositionSpan(0, 0));
975         wTerms[0].addPositionSpans(positionSpans);
976
977         wTerms[1] = new WeightedSpanTerm(1f, "kennedy");
978         positionSpans = new ArrayList<PositionSpan>();
979         positionSpans.add(new PositionSpan(14, 14));
980         wTerms[1].addPositionSpans(positionSpans);
981
982         Highlighter highlighter = getHighlighter(wTerms, HighlighterTest.this);// new
983         // Highlighter(new
984         // QueryTermScorer(wTerms));
985         TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(texts[0]));
986         highlighter.setTextFragmenter(new SimpleFragmenter(2));
987
988         String result = highlighter.getBestFragment(tokenStream, texts[0]).trim();
989         assertTrue("Failed to find best section using weighted terms. Found: [" + result + "]",
990             "<B>Hello</B>".equals(result));
991
992         // readjust weights
993         wTerms[1].setWeight(50f);
994         tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(texts[0]));
995         highlighter = getHighlighter(wTerms, HighlighterTest.this);
996         highlighter.setTextFragmenter(new SimpleFragmenter(2));
997
998         result = highlighter.getBestFragment(tokenStream, texts[0]).trim();
999         assertTrue("Failed to find best section using weighted terms. Found: " + result,
1000             "<B>kennedy</B>".equals(result));
1001       }
1002
1003     };
1004
1005     helper.start();
1006
1007   }
1008
1009   // tests a "complex" analyzer that produces multiple
1010   // overlapping tokens
1011   public void testOverlapAnalyzer() throws Exception {
1012     TestHighlightRunner helper = new TestHighlightRunner() {
1013
1014       @Override
1015       public void run() throws Exception {
1016         HashMap<String,String> synonyms = new HashMap<String,String>();
1017         synonyms.put("football", "soccer,footie");
1018         Analyzer analyzer = new SynonymAnalyzer(synonyms);
1019         String srchkey = "football";
1020
1021         String s = "football-soccer in the euro 2004 footie competition";
1022         QueryParser parser = new QueryParser(TEST_VERSION_CURRENT, "bookid", analyzer);
1023         Query query = parser.parse(srchkey);
1024
1025         TokenStream tokenStream = analyzer.tokenStream(null, new StringReader(s));
1026
1027         Highlighter highlighter = getHighlighter(query, null, tokenStream, HighlighterTest.this);
1028
1029         // Get 3 best fragments and seperate with a "..."
1030         tokenStream = analyzer.tokenStream(null, new StringReader(s));
1031
1032         String result = highlighter.getBestFragments(tokenStream, s, 3, "...");
1033         String expectedResult = "<B>football</B>-<B>soccer</B> in the euro 2004 <B>footie</B> competition";
1034         assertTrue("overlapping analyzer should handle highlights OK, expected:" + expectedResult
1035             + " actual:" + result, expectedResult.equals(result));
1036       }
1037
1038     };
1039
1040     helper.start();
1041
1042   }
1043
1044   public void testGetSimpleHighlight() throws Exception {
1045     TestHighlightRunner helper = new TestHighlightRunner() {
1046
1047       @Override
1048       public void run() throws Exception {
1049         numHighlights = 0;
1050         doSearching("Kennedy");
1051         // new Highlighter(HighlighterTest.this, new QueryTermScorer(query));
1052
1053         for (int i = 0; i < hits.totalHits; i++) {
1054           String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
1055           TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
1056           Highlighter highlighter = getHighlighter(query, FIELD_NAME, tokenStream,
1057               HighlighterTest.this);
1058           String result = highlighter.getBestFragment(tokenStream, text);
1059           if (VERBOSE) System.out.println("\t" + result);
1060         }
1061         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
1062             numHighlights == 4);
1063       }
1064     };
1065     helper.start();
1066   }
1067
1068   public void testGetTextFragments() throws Exception {
1069     TestHighlightRunner helper = new TestHighlightRunner() {
1070
1071       @Override
1072       public void run() throws Exception {
1073
1074         doSearching("Kennedy");
1075
1076         for (int i = 0; i < hits.totalHits; i++) {
1077           String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
1078           TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
1079
1080           Highlighter highlighter = getHighlighter(query, FIELD_NAME, tokenStream,
1081               HighlighterTest.this);// new Highlighter(this, new
1082           // QueryTermScorer(query));
1083           highlighter.setTextFragmenter(new SimpleFragmenter(20));
1084           String stringResults[] = highlighter.getBestFragments(tokenStream, text, 10);
1085
1086           tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
1087           TextFragment fragmentResults[] = highlighter.getBestTextFragments(tokenStream, text,
1088               true, 10);
1089
1090           assertTrue("Failed to find correct number of text Fragments: " + fragmentResults.length
1091               + " vs " + stringResults.length, fragmentResults.length == stringResults.length);
1092           for (int j = 0; j < stringResults.length; j++) {
1093             if (VERBOSE) System.out.println(fragmentResults[j]);
1094             assertTrue("Failed to find same text Fragments: " + fragmentResults[j] + " found",
1095                 fragmentResults[j].toString().equals(stringResults[j]));
1096
1097           }
1098
1099         }
1100       }
1101     };
1102     helper.start();
1103   }
1104
1105   public void testMaxSizeHighlight() throws Exception {
1106     final MockAnalyzer analyzer = new MockAnalyzer(random, MockTokenizer.SIMPLE, true, (CharArraySet) StopAnalyzer.ENGLISH_STOP_WORDS_SET, true);
1107     // we disable MockTokenizer checks because we will forcefully limit the 
1108     // tokenstream and call end() before incrementToken() returns false.
1109     analyzer.setEnableChecks(false);
1110     TestHighlightRunner helper = new TestHighlightRunner() {
1111
1112       @Override
1113       public void run() throws Exception {
1114         numHighlights = 0;
1115         doSearching("meat");
1116         TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(texts[0]));
1117         Highlighter highlighter = getHighlighter(query, FIELD_NAME, tokenStream,
1118             HighlighterTest.this);// new Highlighter(this, new
1119         // QueryTermScorer(query));
1120         highlighter.setMaxDocCharsToAnalyze(30);
1121
1122         highlighter.getBestFragment(tokenStream, texts[0]);
1123         assertTrue("Setting MaxDocBytesToAnalyze should have prevented "
1124             + "us from finding matches for this record: " + numHighlights + " found",
1125             numHighlights == 0);
1126       }
1127     };
1128
1129     helper.start();
1130   }
1131
1132   public void testMaxSizeHighlightTruncates() throws Exception {
1133     TestHighlightRunner helper = new TestHighlightRunner() {
1134
1135       @Override
1136       public void run() throws Exception {
1137         String goodWord = "goodtoken";
1138         Set<String> stopWords = new HashSet<String>(1);
1139         stopWords.add("stoppedtoken");
1140
1141         TermQuery query = new TermQuery(new Term("data", goodWord));
1142
1143         String match = null;
1144         StringBuilder sb = new StringBuilder();
1145         sb.append(goodWord);
1146         for (int i = 0; i < 10000; i++) {
1147           sb.append(" ");
1148           // only one stopword
1149           sb.append(stopWords.iterator().next());
1150         }
1151         SimpleHTMLFormatter fm = new SimpleHTMLFormatter();
1152         Highlighter hg = getHighlighter(query, "data", new StandardAnalyzer(TEST_VERSION_CURRENT, stopWords).tokenStream(
1153             "data", new StringReader(sb.toString())), fm);// new Highlighter(fm,
1154         // new
1155         // QueryTermScorer(query));
1156         hg.setTextFragmenter(new NullFragmenter());
1157         hg.setMaxDocCharsToAnalyze(100);
1158         match = hg.getBestFragment(new StandardAnalyzer(TEST_VERSION_CURRENT, stopWords), "data", sb.toString());
1159         assertTrue("Matched text should be no more than 100 chars in length ", match.length() < hg
1160             .getMaxDocCharsToAnalyze());
1161
1162         // add another tokenized word to the overrall length - but set way
1163         // beyond
1164         // the length of text under consideration (after a large slug of stop
1165         // words
1166         // + whitespace)
1167         sb.append(" ");
1168         sb.append(goodWord);
1169         match = hg.getBestFragment(new StandardAnalyzer(TEST_VERSION_CURRENT, stopWords), "data", sb.toString());
1170         assertTrue("Matched text should be no more than 100 chars in length ", match.length() < hg
1171             .getMaxDocCharsToAnalyze());
1172       }
1173     };
1174
1175     helper.start();
1176
1177   }
1178   
1179   public void testMaxSizeEndHighlight() throws Exception {
1180     TestHighlightRunner helper = new TestHighlightRunner() {
1181       @Override
1182       public void run() throws Exception {
1183         Set<String> stopWords = new HashSet<String>();
1184         stopWords.add("in");
1185         stopWords.add("it");
1186         TermQuery query = new TermQuery(new Term("text", "searchterm"));
1187
1188         String text = "this is a text with searchterm in it";
1189         SimpleHTMLFormatter fm = new SimpleHTMLFormatter();
1190         Highlighter hg = getHighlighter(query, "text", new StandardAnalyzer(TEST_VERSION_CURRENT, 
1191             stopWords).tokenStream("text", new StringReader(text)), fm);
1192         hg.setTextFragmenter(new NullFragmenter());
1193         hg.setMaxDocCharsToAnalyze(36);
1194         String match = hg.getBestFragment(new StandardAnalyzer(TEST_VERSION_CURRENT, stopWords), "text", text);
1195         assertTrue(
1196             "Matched text should contain remainder of text after highlighted query ",
1197             match.endsWith("in it"));
1198       }
1199     };
1200     helper.start();
1201   }
1202
1203   public void testUnRewrittenQuery() throws Exception {
1204     final TestHighlightRunner helper = new TestHighlightRunner() {
1205
1206       @Override
1207       public void run() throws Exception {
1208         numHighlights = 0;
1209         // test to show how rewritten query can still be used
1210         if (searcher != null) searcher.close();
1211         searcher = new IndexSearcher(reader);
1212         Analyzer analyzer = new StandardAnalyzer(TEST_VERSION_CURRENT);
1213
1214         QueryParser parser = new QueryParser(TEST_VERSION_CURRENT, FIELD_NAME, analyzer);
1215         Query query = parser.parse("JF? or Kenned*");
1216         if (VERBOSE) System.out.println("Searching with primitive query");
1217         // forget to set this and...
1218         // query=query.rewrite(reader);
1219         TopDocs hits = searcher.search(query, null, 1000);
1220
1221         // create an instance of the highlighter with the tags used to surround
1222         // highlighted text
1223         // QueryHighlightExtractor highlighter = new
1224         // QueryHighlightExtractor(this,
1225         // query, new StandardAnalyzer(TEST_VERSION));
1226
1227         int maxNumFragmentsRequired = 3;
1228
1229         for (int i = 0; i < hits.totalHits; i++) {
1230           String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
1231           TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
1232           Highlighter highlighter = getHighlighter(query, FIELD_NAME, tokenStream, HighlighterTest.this, false);
1233
1234           highlighter.setTextFragmenter(new SimpleFragmenter(40));
1235
1236           String highlightedText = highlighter.getBestFragments(tokenStream, text,
1237               maxNumFragmentsRequired, "...");
1238
1239           if (VERBOSE) System.out.println(highlightedText);
1240         }
1241         // We expect to have zero highlights if the query is multi-terms and is
1242         // not
1243         // rewritten!
1244         assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
1245             numHighlights == 0);
1246       }
1247     };
1248
1249     helper.start();
1250   }
1251
1252   public void testNoFragments() throws Exception {
1253     TestHighlightRunner helper = new TestHighlightRunner() {
1254
1255       @Override
1256       public void run() throws Exception {
1257         doSearching("AnInvalidQueryWhichShouldYieldNoResults");
1258
1259         for (int i = 0; i < texts.length; i++) {
1260           String text = texts[i];
1261           TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
1262           Highlighter highlighter = getHighlighter(query, FIELD_NAME, tokenStream,
1263               HighlighterTest.this);
1264           String result = highlighter.getBestFragment(tokenStream, text);
1265           assertNull("The highlight result should be null for text with no query terms", result);
1266         }
1267       }
1268     };
1269
1270     helper.start();
1271   }
1272
1273   /**
1274    * Demonstrates creation of an XHTML compliant doc using new encoding facilities.
1275    * 
1276    * @throws Exception
1277    */
1278   public void testEncoding() throws Exception {
1279
1280     String rawDocContent = "\"Smith & sons' prices < 3 and >4\" claims article";
1281     // run the highlighter on the raw content (scorer does not score any tokens
1282     // for
1283     // highlighting but scores a single fragment for selection
1284     Highlighter highlighter = new Highlighter(this, new SimpleHTMLEncoder(), new Scorer() {
1285       public void startFragment(TextFragment newFragment) {
1286       }
1287
1288       public float getTokenScore() {
1289         return 0;
1290       }
1291
1292       public float getFragmentScore() {
1293         return 1;
1294       }
1295
1296       public TokenStream init(TokenStream tokenStream) {
1297         return null;
1298       }
1299     });
1300     highlighter.setTextFragmenter(new SimpleFragmenter(2000));
1301     TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(rawDocContent));
1302
1303     String encodedSnippet = highlighter.getBestFragments(tokenStream, rawDocContent, 1, "");
1304     // An ugly bit of XML creation:
1305     String xhtml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1306         + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
1307         + "<head>\n" + "<title>My Test HTML Document</title>\n" + "</head>\n" + "<body>\n" + "<h2>"
1308         + encodedSnippet + "</h2>\n" + "</body>\n" + "</html>";
1309     // now an ugly built of XML parsing to test the snippet is encoded OK
1310     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
1311     DocumentBuilder db = dbf.newDocumentBuilder();
1312     org.w3c.dom.Document doc = db.parse(new ByteArrayInputStream(xhtml.getBytes()));
1313     Element root = doc.getDocumentElement();
1314     NodeList nodes = root.getElementsByTagName("body");
1315     Element body = (Element) nodes.item(0);
1316     nodes = body.getElementsByTagName("h2");
1317     Element h2 = (Element) nodes.item(0);
1318     String decodedSnippet = h2.getFirstChild().getNodeValue();
1319     assertEquals("XHTML Encoding should have worked:", rawDocContent, decodedSnippet);
1320   }
1321
1322   public void testMultiSearcher() throws Exception {
1323     // setup index 1
1324     Directory ramDir1 = newDirectory();
1325     IndexWriter writer1 = new IndexWriter(ramDir1, newIndexWriterConfig(
1326         TEST_VERSION_CURRENT, new StandardAnalyzer(TEST_VERSION_CURRENT)));
1327     Document d = new Document();
1328     Field f = new Field(FIELD_NAME, "multiOne", Field.Store.YES, Field.Index.ANALYZED);
1329     d.add(f);
1330     writer1.addDocument(d);
1331     writer1.optimize();
1332     writer1.close();
1333     IndexReader reader1 = IndexReader.open(ramDir1, true);
1334
1335     // setup index 2
1336     Directory ramDir2 = newDirectory();
1337     IndexWriter writer2 = new IndexWriter(ramDir2, newIndexWriterConfig(
1338         TEST_VERSION_CURRENT, new StandardAnalyzer(TEST_VERSION_CURRENT)));
1339     d = new Document();
1340     f = new Field(FIELD_NAME, "multiTwo", Field.Store.YES, Field.Index.ANALYZED);
1341     d.add(f);
1342     writer2.addDocument(d);
1343     writer2.optimize();
1344     writer2.close();
1345     IndexReader reader2 = IndexReader.open(ramDir2, true);
1346
1347     IndexSearcher searchers[] = new IndexSearcher[2];
1348     searchers[0] = new IndexSearcher(ramDir1, true);
1349     searchers[1] = new IndexSearcher(ramDir2, true);
1350     MultiSearcher multiSearcher = new MultiSearcher(searchers);
1351     QueryParser parser = new QueryParser(TEST_VERSION_CURRENT, FIELD_NAME, new StandardAnalyzer(TEST_VERSION_CURRENT));
1352     parser.setMultiTermRewriteMethod(MultiTermQuery.SCORING_BOOLEAN_QUERY_REWRITE);
1353     query = parser.parse("multi*");
1354     if (VERBOSE) System.out.println("Searching for: " + query.toString(FIELD_NAME));
1355     // at this point the multisearcher calls combine(query[])
1356     hits = multiSearcher.search(query, null, 1000);
1357
1358     // query = QueryParser.parse("multi*", FIELD_NAME, new StandardAnalyzer(TEST_VERSION));
1359     Query expandedQueries[] = new Query[2];
1360     expandedQueries[0] = query.rewrite(reader1);
1361     expandedQueries[1] = query.rewrite(reader2);
1362     query = query.combine(expandedQueries);
1363
1364     // create an instance of the highlighter with the tags used to surround
1365     // highlighted text
1366     Highlighter highlighter = new Highlighter(this, new QueryTermScorer(query));
1367
1368     for (int i = 0; i < hits.totalHits; i++) {
1369       String text = multiSearcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
1370       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
1371       String highlightedText = highlighter.getBestFragment(tokenStream, text);
1372       if (VERBOSE) System.out.println(highlightedText);
1373     }
1374     assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
1375         numHighlights == 2);
1376     reader1.close();
1377     reader2.close();
1378     searchers[0].close();
1379     searchers[1].close();
1380     ramDir1.close();
1381     ramDir2.close();
1382   }
1383
1384   public void testFieldSpecificHighlighting() throws Exception {
1385     TestHighlightRunner helper = new TestHighlightRunner() {
1386
1387       @Override
1388       public void run() throws Exception {
1389         String docMainText = "fred is one of the people";
1390         QueryParser parser = new QueryParser(TEST_VERSION_CURRENT, FIELD_NAME, analyzer);
1391         Query query = parser.parse("fred category:people");
1392
1393         // highlighting respects fieldnames used in query
1394
1395         Scorer fieldSpecificScorer = null;
1396         if (mode == TestHighlightRunner.QUERY) {
1397           fieldSpecificScorer = new QueryScorer(query, FIELD_NAME);
1398         } else if (mode == TestHighlightRunner.QUERY_TERM) {
1399           fieldSpecificScorer = new QueryTermScorer(query, "contents");
1400         }
1401         Highlighter fieldSpecificHighlighter = new Highlighter(new SimpleHTMLFormatter(),
1402             fieldSpecificScorer);
1403         fieldSpecificHighlighter.setTextFragmenter(new NullFragmenter());
1404         String result = fieldSpecificHighlighter.getBestFragment(analyzer, FIELD_NAME, docMainText);
1405         assertEquals("Should match", result, "<B>fred</B> is one of the people");
1406
1407         // highlighting does not respect fieldnames used in query
1408         Scorer fieldInSpecificScorer = null;
1409         if (mode == TestHighlightRunner.QUERY) {
1410           fieldInSpecificScorer = new QueryScorer(query, null);
1411         } else if (mode == TestHighlightRunner.QUERY_TERM) {
1412           fieldInSpecificScorer = new QueryTermScorer(query);
1413         }
1414
1415         Highlighter fieldInSpecificHighlighter = new Highlighter(new SimpleHTMLFormatter(),
1416             fieldInSpecificScorer);
1417         fieldInSpecificHighlighter.setTextFragmenter(new NullFragmenter());
1418         result = fieldInSpecificHighlighter.getBestFragment(analyzer, FIELD_NAME, docMainText);
1419         assertEquals("Should match", result, "<B>fred</B> is one of the <B>people</B>");
1420
1421         reader.close();
1422       }
1423     };
1424
1425     helper.start();
1426
1427   }
1428
1429   protected TokenStream getTS2() {
1430     // String s = "Hi-Speed10 foo";
1431     return new TokenStream() {
1432       Iterator<Token> iter;
1433       List<Token> lst;
1434       private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
1435       private final PositionIncrementAttribute posIncrAtt = addAttribute(PositionIncrementAttribute.class);
1436       private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);
1437       {
1438         lst = new ArrayList<Token>();
1439         Token t;
1440         t = createToken("hi", 0, 2);
1441         t.setPositionIncrement(1);
1442         lst.add(t);
1443         t = createToken("hispeed", 0, 8);
1444         t.setPositionIncrement(1);
1445         lst.add(t);
1446         t = createToken("speed", 3, 8);
1447         t.setPositionIncrement(0);
1448         lst.add(t);
1449         t = createToken("10", 8, 10);
1450         t.setPositionIncrement(1);
1451         lst.add(t);
1452         t = createToken("foo", 11, 14);
1453         t.setPositionIncrement(1);
1454         lst.add(t);
1455         iter = lst.iterator();
1456       }
1457
1458       @Override
1459       public boolean incrementToken() throws IOException {
1460         if(iter.hasNext()) {
1461           Token token =  iter.next();
1462           clearAttributes();
1463           termAtt.setEmpty().append(token);
1464           posIncrAtt.setPositionIncrement(token.getPositionIncrement());
1465           offsetAtt.setOffset(token.startOffset(), token.endOffset());
1466           return true;
1467         }
1468         return false;
1469       }
1470      
1471     };
1472   }
1473
1474   // same token-stream as above, but the bigger token comes first this time
1475   protected TokenStream getTS2a() {
1476     // String s = "Hi-Speed10 foo";
1477     return new TokenStream() {
1478       Iterator<Token> iter;
1479       List<Token> lst;
1480       private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
1481       private final PositionIncrementAttribute posIncrAtt = addAttribute(PositionIncrementAttribute.class);
1482       private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);
1483       {
1484         lst = new ArrayList<Token>();
1485         Token t;
1486         t = createToken("hispeed", 0, 8);
1487         t.setPositionIncrement(1);
1488         lst.add(t);
1489         t = createToken("hi", 0, 2);
1490         t.setPositionIncrement(0);
1491         lst.add(t);
1492         t = createToken("speed", 3, 8);
1493         t.setPositionIncrement(1);
1494         lst.add(t);
1495         t = createToken("10", 8, 10);
1496         t.setPositionIncrement(1);
1497         lst.add(t);
1498         t = createToken("foo", 11, 14);
1499         t.setPositionIncrement(1);
1500         lst.add(t);
1501         iter = lst.iterator();
1502       }
1503
1504       @Override
1505       public boolean incrementToken() throws IOException {
1506         if(iter.hasNext()) {
1507           Token token = iter.next();
1508           clearAttributes();
1509           termAtt.setEmpty().append(token);
1510           posIncrAtt.setPositionIncrement(token.getPositionIncrement());
1511           offsetAtt.setOffset(token.startOffset(), token.endOffset());
1512           return true;
1513         }
1514         return false;
1515       }
1516     };
1517   }
1518
1519   public void testOverlapAnalyzer2() throws Exception {
1520     TestHighlightRunner helper = new TestHighlightRunner() {
1521
1522       @Override
1523       public void run() throws Exception {
1524         String s = "Hi-Speed10 foo";
1525
1526         Query query;
1527         Highlighter highlighter;
1528         String result;
1529
1530         query = new QueryParser(TEST_VERSION_CURRENT, "text", new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).parse("foo");
1531         highlighter = getHighlighter(query, "text", getTS2(), HighlighterTest.this);
1532         result = highlighter.getBestFragments(getTS2(), s, 3, "...");
1533         assertEquals("Hi-Speed10 <B>foo</B>", result);
1534
1535         query = new QueryParser(TEST_VERSION_CURRENT, "text", new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).parse("10");
1536         highlighter = getHighlighter(query, "text", getTS2(), HighlighterTest.this);
1537         result = highlighter.getBestFragments(getTS2(), s, 3, "...");
1538         assertEquals("Hi-Speed<B>10</B> foo", result);
1539
1540         query = new QueryParser(TEST_VERSION_CURRENT, "text", new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).parse("hi");
1541         highlighter = getHighlighter(query, "text", getTS2(), HighlighterTest.this);
1542         result = highlighter.getBestFragments(getTS2(), s, 3, "...");
1543         assertEquals("<B>Hi</B>-Speed10 foo", result);
1544
1545         query = new QueryParser(TEST_VERSION_CURRENT, "text", new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).parse("speed");
1546         highlighter = getHighlighter(query, "text", getTS2(), HighlighterTest.this);
1547         result = highlighter.getBestFragments(getTS2(), s, 3, "...");
1548         assertEquals("Hi-<B>Speed</B>10 foo", result);
1549
1550         query = new QueryParser(TEST_VERSION_CURRENT, "text", new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).parse("hispeed");
1551         highlighter = getHighlighter(query, "text", getTS2(), HighlighterTest.this);
1552         result = highlighter.getBestFragments(getTS2(), s, 3, "...");
1553         assertEquals("<B>Hi-Speed</B>10 foo", result);
1554
1555         query = new QueryParser(TEST_VERSION_CURRENT, "text", new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).parse("hi speed");
1556         highlighter = getHighlighter(query, "text", getTS2(), HighlighterTest.this);
1557         result = highlighter.getBestFragments(getTS2(), s, 3, "...");
1558         assertEquals("<B>Hi-Speed</B>10 foo", result);
1559
1560         // ///////////////// same tests, just put the bigger overlapping token
1561         // first
1562         query = new QueryParser(TEST_VERSION_CURRENT, "text", new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).parse("foo");
1563         highlighter = getHighlighter(query, "text", getTS2a(), HighlighterTest.this);
1564         result = highlighter.getBestFragments(getTS2a(), s, 3, "...");
1565         assertEquals("Hi-Speed10 <B>foo</B>", result);
1566
1567         query = new QueryParser(TEST_VERSION_CURRENT, "text", new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).parse("10");
1568         highlighter = getHighlighter(query, "text", getTS2a(), HighlighterTest.this);
1569         result = highlighter.getBestFragments(getTS2a(), s, 3, "...");
1570         assertEquals("Hi-Speed<B>10</B> foo", result);
1571
1572         query = new QueryParser(TEST_VERSION_CURRENT, "text", new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).parse("hi");
1573         highlighter = getHighlighter(query, "text", getTS2a(), HighlighterTest.this);
1574         result = highlighter.getBestFragments(getTS2a(), s, 3, "...");
1575         assertEquals("<B>Hi</B>-Speed10 foo", result);
1576
1577         query = new QueryParser(TEST_VERSION_CURRENT, "text", new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).parse("speed");
1578         highlighter = getHighlighter(query, "text", getTS2a(), HighlighterTest.this);
1579         result = highlighter.getBestFragments(getTS2a(), s, 3, "...");
1580         assertEquals("Hi-<B>Speed</B>10 foo", result);
1581
1582         query = new QueryParser(TEST_VERSION_CURRENT, "text", new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).parse("hispeed");
1583         highlighter = getHighlighter(query, "text", getTS2a(), HighlighterTest.this);
1584         result = highlighter.getBestFragments(getTS2a(), s, 3, "...");
1585         assertEquals("<B>Hi-Speed</B>10 foo", result);
1586
1587         query = new QueryParser(TEST_VERSION_CURRENT, "text", new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).parse("hi speed");
1588         highlighter = getHighlighter(query, "text", getTS2a(), HighlighterTest.this);
1589         result = highlighter.getBestFragments(getTS2a(), s, 3, "...");
1590         assertEquals("<B>Hi-Speed</B>10 foo", result);
1591       }
1592     };
1593
1594     helper.start();
1595   }
1596   
1597   private Directory dir;
1598   private Analyzer a = new MockAnalyzer(random, MockTokenizer.WHITESPACE, false);
1599   
1600   public void testWeightedTermsWithDeletes() throws IOException, ParseException, InvalidTokenOffsetsException {
1601     makeIndex();
1602     deleteDocument();
1603     searchIndex();
1604   }
1605   
1606   private Document doc( String f, String v ){
1607     Document doc = new Document();
1608     doc.add( new Field( f, v, Store.YES, Index.ANALYZED ) );
1609     return doc;
1610   }
1611   
1612   private void makeIndex() throws IOException {
1613     IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)));
1614     writer.addDocument( doc( "t_text1", "random words for highlighting tests del" ) );
1615     writer.addDocument( doc( "t_text1", "more random words for second field del" ) );
1616     writer.addDocument( doc( "t_text1", "random words for highlighting tests del" ) );
1617     writer.addDocument( doc( "t_text1", "more random words for second field" ) );
1618     writer.forceMerge(1);
1619     writer.close();
1620   }
1621   
1622   private void deleteDocument() throws IOException {
1623     IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setOpenMode(OpenMode.APPEND));
1624     writer.deleteDocuments( new Term( "t_text1", "del" ) );
1625     // To see negative idf, keep comment the following line
1626     //writer.forceMerge(1);
1627     writer.close();
1628   }
1629   
1630   private void searchIndex() throws IOException, ParseException, InvalidTokenOffsetsException {
1631     String q = "t_text1:random";
1632     QueryParser parser = new QueryParser(TEST_VERSION_CURRENT,  "t_text1", a );
1633     Query query = parser.parse( q );
1634     IndexReader reader = IndexReader.open(dir);
1635     IndexSearcher searcher = new IndexSearcher(reader);
1636     // This scorer can return negative idf -> null fragment
1637     Scorer scorer = new QueryTermScorer( query, searcher.getIndexReader(), "t_text1" );
1638     // This scorer doesn't use idf (patch version)
1639     //Scorer scorer = new QueryTermScorer( query, "t_text1" );
1640     Highlighter h = new Highlighter( scorer );
1641
1642     TopDocs hits = searcher.search(query, null, 10);
1643     for( int i = 0; i < hits.totalHits; i++ ){
1644       Document doc = searcher.doc( hits.scoreDocs[i].doc );
1645       String result = h.getBestFragment( a, "t_text1", doc.get( "t_text1" ));
1646       if (VERBOSE) System.out.println("result:" +  result);
1647       assertEquals("more <B>random</B> words for second field", result);
1648     }
1649     searcher.close();
1650     reader.close();
1651   }
1652
1653   /*
1654    * 
1655    * public void testBigramAnalyzer() throws IOException, ParseException {
1656    * //test to ensure analyzers with none-consecutive start/end offsets //dont
1657    * double-highlight text //setup index 1 RAMDirectory ramDir = new
1658    * RAMDirectory(); Analyzer bigramAnalyzer=new CJKAnalyzer(); IndexWriter
1659    * writer = new IndexWriter(ramDir,bigramAnalyzer , true); Document d = new
1660    * Document(); Field f = new Field(FIELD_NAME, "java abc def", true, true,
1661    * true); d.add(f); writer.addDocument(d); writer.close(); IndexReader reader =
1662    * IndexReader.open(ramDir, true);
1663    * 
1664    * IndexSearcher searcher=new IndexSearcher(reader); query =
1665    * QueryParser.parse("abc", FIELD_NAME, bigramAnalyzer);
1666    * System.out.println("Searching for: " + query.toString(FIELD_NAME)); hits =
1667    * searcher.search(query);
1668    * 
1669    * Highlighter highlighter = new Highlighter(this,new
1670    * QueryFragmentScorer(query));
1671    * 
1672    * for (int i = 0; i < hits.totalHits; i++) { String text =
1673    * searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME); TokenStream
1674    * tokenStream=bigramAnalyzer.tokenStream(FIELD_NAME,new StringReader(text));
1675    * String highlightedText = highlighter.getBestFragment(tokenStream,text);
1676    * System.out.println(highlightedText); } }
1677    */
1678
1679   public String highlightTerm(String originalText, TokenGroup group) {
1680     if (group.getTotalScore() <= 0) {
1681       return originalText;
1682     }
1683     numHighlights++; // update stats used in assertions
1684     return "<B>" + originalText + "</B>";
1685   }
1686
1687   public void doSearching(String queryString) throws Exception {
1688     QueryParser parser = new QueryParser(TEST_VERSION_CURRENT, FIELD_NAME, analyzer);
1689     parser.setEnablePositionIncrements(true);
1690     parser.setMultiTermRewriteMethod(MultiTermQuery.SCORING_BOOLEAN_QUERY_REWRITE);
1691     query = parser.parse(queryString);
1692     doSearching(query);
1693   }
1694
1695   public void doSearching(Query unReWrittenQuery) throws Exception {
1696     if (searcher != null) searcher.close();
1697     searcher = new IndexSearcher(reader);
1698     // for any multi-term queries to work (prefix, wildcard, range,fuzzy etc)
1699     // you must use a rewritten query!
1700     query = unReWrittenQuery.rewrite(reader);
1701     if (VERBOSE) System.out.println("Searching for: " + query.toString(FIELD_NAME));
1702     hits = searcher.search(query, null, 1000);
1703   }
1704
1705   public void assertExpectedHighlightCount(final int maxNumFragmentsRequired,
1706       final int expectedHighlights) throws Exception {
1707     for (int i = 0; i < hits.totalHits; i++) {
1708       String text = searcher.doc(hits.scoreDocs[i].doc).get(FIELD_NAME);
1709       TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, new StringReader(text));
1710       QueryScorer scorer = new QueryScorer(query, FIELD_NAME);
1711       Highlighter highlighter = new Highlighter(this, scorer);
1712
1713       highlighter.setTextFragmenter(new SimpleFragmenter(40));
1714
1715       String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
1716           "...");
1717       if (VERBOSE) System.out.println("\t" + result);
1718
1719       assertTrue("Failed to find correct number of highlights " + numHighlights + " found",
1720           numHighlights == expectedHighlights);
1721     }
1722   }
1723
1724   @Override
1725   public void setUp() throws Exception {
1726     super.setUp();
1727     dir = newDirectory();
1728     ramDir = newDirectory();
1729     IndexWriter writer = new IndexWriter(ramDir, newIndexWriterConfig(
1730         TEST_VERSION_CURRENT, new StandardAnalyzer(TEST_VERSION_CURRENT)));
1731     for (int i = 0; i < texts.length; i++) {
1732       addDoc(writer, texts[i]);
1733     }
1734     Document doc = new Document();
1735     NumericField nfield = new NumericField(NUMERIC_FIELD_NAME, Store.YES, true);
1736     nfield.setIntValue(1);
1737     doc.add(nfield);
1738     writer.addDocument(doc, analyzer);
1739     nfield = new NumericField(NUMERIC_FIELD_NAME, Store.YES, true);
1740     nfield.setIntValue(3);
1741     doc = new Document();
1742     doc.add(nfield);
1743     writer.addDocument(doc, analyzer);
1744     nfield = new NumericField(NUMERIC_FIELD_NAME, Store.YES, true);
1745     nfield.setIntValue(5);
1746     doc = new Document();
1747     doc.add(nfield);
1748     writer.addDocument(doc, analyzer);
1749     nfield = new NumericField(NUMERIC_FIELD_NAME, Store.YES, true);
1750     nfield.setIntValue(7);
1751     doc = new Document();
1752     doc.add(nfield);
1753     writer.addDocument(doc, analyzer);
1754     writer.forceMerge(1);
1755     writer.close();
1756     reader = IndexReader.open(ramDir, true);
1757     numHighlights = 0;
1758   }
1759
1760   @Override
1761   public void tearDown() throws Exception {
1762     if (searcher != null) searcher.close();
1763     reader.close();
1764     dir.close();
1765     ramDir.close();
1766     super.tearDown();
1767   }
1768   private void addDoc(IndexWriter writer, String text) throws IOException {
1769     Document d = new Document();
1770     Field f = new Field(FIELD_NAME, text, Field.Store.YES, Field.Index.ANALYZED);
1771     d.add(f);
1772     writer.addDocument(d);
1773
1774   }
1775
1776   private static Token createToken(String term, int start, int offset)
1777   {
1778     return new Token(term, start, offset);
1779   }
1780
1781 }
1782
1783 // ===================================================================
1784 // ========== BEGIN TEST SUPPORTING CLASSES
1785 // ========== THESE LOOK LIKE, WITH SOME MORE EFFORT THESE COULD BE
1786 // ========== MADE MORE GENERALLY USEFUL.
1787 // TODO - make synonyms all interchangeable with each other and produce
1788 // a version that does hyponyms - the "is a specialised type of ...."
1789 // so that car = audi, bmw and volkswagen but bmw != audi so different
1790 // behaviour to synonyms
1791 // ===================================================================
1792
1793 final class SynonymAnalyzer extends Analyzer {
1794   private Map<String,String> synonyms;
1795
1796   public SynonymAnalyzer(Map<String,String> synonyms) {
1797     this.synonyms = synonyms;
1798   }
1799
1800   /*
1801    * (non-Javadoc)
1802    * 
1803    * @see org.apache.lucene.analysis.Analyzer#tokenStream(java.lang.String,
1804    *      java.io.Reader)
1805    */
1806   @Override
1807   public TokenStream tokenStream(String arg0, Reader arg1) {
1808     LowerCaseTokenizer stream = new LowerCaseTokenizer(LuceneTestCase.TEST_VERSION_CURRENT, arg1);
1809     stream.addAttribute(CharTermAttribute.class);
1810     stream.addAttribute(PositionIncrementAttribute.class);
1811     stream.addAttribute(OffsetAttribute.class);
1812     try {
1813       stream.reset();
1814     } catch (IOException e) {
1815       throw new RuntimeException(e);
1816     }
1817     return new SynonymTokenizer(stream, synonyms);
1818   }
1819 }
1820
1821 /**
1822  * Expands a token stream with synonyms (TODO - make the synonyms analyzed by choice of analyzer)
1823  *
1824  */
1825 final class SynonymTokenizer extends TokenStream {
1826   private final TokenStream realStream;
1827   private Token currentRealToken = null;
1828   private final Map<String, String> synonyms;
1829   private StringTokenizer st = null;
1830   private final CharTermAttribute realTermAtt;
1831   private final PositionIncrementAttribute realPosIncrAtt;
1832   private final OffsetAttribute realOffsetAtt;
1833   private final CharTermAttribute termAtt;
1834   private final PositionIncrementAttribute posIncrAtt;
1835   private final OffsetAttribute offsetAtt;
1836
1837   public SynonymTokenizer(TokenStream realStream, Map<String, String> synonyms) {
1838     this.realStream = realStream;
1839     this.synonyms = synonyms;
1840     realTermAtt = realStream.addAttribute(CharTermAttribute.class);
1841     realPosIncrAtt = realStream.addAttribute(PositionIncrementAttribute.class);
1842     realOffsetAtt = realStream.addAttribute(OffsetAttribute.class);
1843
1844     termAtt = addAttribute(CharTermAttribute.class);
1845     posIncrAtt = addAttribute(PositionIncrementAttribute.class);
1846     offsetAtt = addAttribute(OffsetAttribute.class);
1847   }
1848
1849   @Override
1850   public boolean incrementToken() throws IOException {
1851
1852     if (currentRealToken == null) {
1853       boolean next = realStream.incrementToken();
1854       if (!next) {
1855         return false;
1856       }
1857       //Token nextRealToken = new Token(, offsetAtt.startOffset(), offsetAtt.endOffset());
1858       clearAttributes();
1859       termAtt.copyBuffer(realTermAtt.buffer(), 0, realTermAtt.length());
1860       offsetAtt.setOffset(realOffsetAtt.startOffset(), realOffsetAtt.endOffset());
1861       posIncrAtt.setPositionIncrement(realPosIncrAtt.getPositionIncrement());
1862
1863       String expansions =  synonyms.get(realTermAtt.toString());
1864       if (expansions == null) {
1865         return true;
1866       }
1867       st = new StringTokenizer(expansions, ",");
1868       if (st.hasMoreTokens()) {
1869         currentRealToken = new Token(realOffsetAtt.startOffset(), realOffsetAtt.endOffset());
1870         currentRealToken.copyBuffer(realTermAtt.buffer(), 0, realTermAtt.length());
1871       }
1872       
1873       return true;
1874     } else {
1875       String tok = st.nextToken();
1876       clearAttributes();
1877       termAtt.setEmpty().append(tok);
1878       offsetAtt.setOffset(currentRealToken.startOffset(), currentRealToken.endOffset());
1879       posIncrAtt.setPositionIncrement(0);
1880       if (!st.hasMoreTokens()) {
1881         currentRealToken = null;
1882         st = null;
1883       }
1884       return true;
1885     }
1886     
1887   }
1888
1889   @Override
1890   public void reset() throws IOException {
1891     super.reset();
1892     this.currentRealToken = null;
1893     this.st = null;
1894   }
1895
1896   static abstract class TestHighlightRunner {
1897     static final int QUERY = 0;
1898     static final int QUERY_TERM = 1;
1899
1900     int mode = QUERY;
1901     Fragmenter frag = new SimpleFragmenter(20);
1902     
1903     public Highlighter getHighlighter(Query query, String fieldName, TokenStream stream, Formatter formatter) {
1904       return getHighlighter(query, fieldName, stream, formatter, true);
1905     }
1906     
1907     public Highlighter getHighlighter(Query query, String fieldName, TokenStream stream, Formatter formatter, boolean expanMultiTerm) {
1908       Scorer scorer = null;
1909       if (mode == QUERY) {
1910         scorer = new QueryScorer(query, fieldName);
1911         if(!expanMultiTerm) {
1912           ((QueryScorer)scorer).setExpandMultiTermQuery(false);
1913         }
1914       } else if (mode == QUERY_TERM) {
1915         scorer = new QueryTermScorer(query);
1916       } else {
1917         throw new RuntimeException("Unknown highlight mode");
1918       }
1919       
1920       return new Highlighter(formatter, scorer);
1921     }
1922
1923     Highlighter getHighlighter(WeightedTerm[] weightedTerms, Formatter formatter) {
1924       if (mode == QUERY) {
1925         return  new Highlighter(formatter, new QueryScorer((WeightedSpanTerm[]) weightedTerms));
1926       } else if (mode == QUERY_TERM) {
1927         return new Highlighter(formatter, new QueryTermScorer(weightedTerms));
1928
1929       } else {
1930         throw new RuntimeException("Unknown highlight mode");
1931       }
1932     }
1933
1934     void doStandardHighlights(Analyzer analyzer, IndexSearcher searcher, TopDocs hits, Query query, Formatter formatter)
1935     throws Exception {
1936       doStandardHighlights(analyzer, searcher, hits, query, formatter, false);
1937     }
1938     
1939     void doStandardHighlights(Analyzer analyzer, IndexSearcher searcher, TopDocs hits, Query query, Formatter formatter, boolean expandMT)
1940         throws Exception {
1941
1942       for (int i = 0; i < hits.totalHits; i++) {
1943         String text = searcher.doc(hits.scoreDocs[i].doc).get(HighlighterTest.FIELD_NAME);
1944         int maxNumFragmentsRequired = 2;
1945         String fragmentSeparator = "...";
1946         Scorer scorer = null;
1947         TokenStream tokenStream = analyzer.tokenStream(HighlighterTest.FIELD_NAME, new StringReader(text));
1948         if (mode == QUERY) {
1949           scorer = new QueryScorer(query);
1950         } else if (mode == QUERY_TERM) {
1951           scorer = new QueryTermScorer(query);
1952         }
1953         Highlighter highlighter = new Highlighter(formatter, scorer);
1954         highlighter.setTextFragmenter(frag);
1955
1956         String result = highlighter.getBestFragments(tokenStream, text, maxNumFragmentsRequired,
1957             fragmentSeparator);
1958         if (HighlighterTest.VERBOSE) System.out.println("\t" + result);
1959       }
1960     }
1961
1962     abstract void run() throws Exception;
1963
1964     void start() throws Exception {
1965       if (HighlighterTest.VERBOSE) System.out.println("Run QueryScorer");
1966       run();
1967       if (HighlighterTest.VERBOSE) System.out.println("Run QueryTermScorer");
1968       mode = QUERY_TERM;
1969       run();
1970     }
1971   }
1972 }