Index: lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighter.java =================================================================== --- lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighter.java (revision 1458944) +++ lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighter.java (working copy) @@ -457,7 +457,7 @@ iw.close(); IndexSearcher searcher = newSearcher(ir); - PostingsHighlighter highlighter = new PostingsHighlighter(10000, null, new PassageScorer(), new PassageFormatter()); + PostingsHighlighter highlighter = new PostingsHighlighter(10000, null); Query query = new TermQuery(new Term("body", "test")); TopDocs topDocs = searcher.search(query, null, 10, Sort.INDEXORDER); assertEquals(1, topDocs.totalHits); @@ -527,7 +527,7 @@ IndexSearcher searcher = newSearcher(ir); - PostingsHighlighter highlighter = new PostingsHighlighter(10000, null, new PassageScorer(), new PassageFormatter()) { + PostingsHighlighter highlighter = new PostingsHighlighter(10000, null) { @Override protected String[][] loadFieldValues(IndexSearcher searcher, String[] fields, int[] docids, int maxLength) throws IOException { assert fields.length == 1; @@ -636,7 +636,7 @@ iw.close(); IndexSearcher searcher = newSearcher(ir); - PostingsHighlighter highlighter = new PostingsHighlighter(10000, null, new PassageScorer(), new PassageFormatter()); + PostingsHighlighter highlighter = new PostingsHighlighter(10000, null); Query query = new TermQuery(new Term("body", "highlighting")); int[] docIDs = new int[] {0}; String snippets[] = highlighter.highlightFields(new String[] {"body"}, query, searcher, docIDs, 2).get("body"); Index: lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighterRanking.java =================================================================== --- lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighterRanking.java (revision 1458942) +++ lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighterRanking.java (working copy) @@ -112,16 +112,24 @@ private void checkQuery(IndexSearcher is, Query query, int doc, int maxTopN) throws IOException { for (int n = 1; n < maxTopN; n++) { - FakePassageFormatter f1 = new FakePassageFormatter(); + final FakePassageFormatter f1 = new FakePassageFormatter(); PostingsHighlighter p1 = new PostingsHighlighter(Integer.MAX_VALUE-1, - BreakIterator.getSentenceInstance(Locale.ROOT), - new PassageScorer(), - f1); - FakePassageFormatter f2 = new FakePassageFormatter(); + BreakIterator.getSentenceInstance(Locale.ROOT)) { + @Override + protected PassageFormatter getFormatter(String field) { + return f1; + } + }; + + final FakePassageFormatter f2 = new FakePassageFormatter(); PostingsHighlighter p2 = new PostingsHighlighter(Integer.MAX_VALUE-1, - BreakIterator.getSentenceInstance(Locale.ROOT), - new PassageScorer(), - f2); + BreakIterator.getSentenceInstance(Locale.ROOT)) { + @Override + protected PassageFormatter getFormatter(String field) { + return f2; + } + }; + BooleanQuery bq = new BooleanQuery(false); bq.add(query, BooleanClause.Occur.MUST); bq.add(new TermQuery(new Term("id", Integer.toString(doc))), BooleanClause.Occur.MUST); @@ -262,9 +270,12 @@ IndexSearcher searcher = newSearcher(ir); PostingsHighlighter highlighter = new PostingsHighlighter(10000, - BreakIterator.getSentenceInstance(Locale.ROOT), - new PassageScorer(1.2f, 0, 87), - new PassageFormatter()); + BreakIterator.getSentenceInstance(Locale.ROOT)) { + @Override + protected PassageScorer getScorer(String field) { + return new PassageScorer(1.2f, 0, 87); + } + }; Query query = new TermQuery(new Term("body", "test")); TopDocs topDocs = searcher.search(query, null, 10, Sort.INDEXORDER); assertEquals(1, topDocs.totalHits); @@ -299,9 +310,12 @@ IndexSearcher searcher = newSearcher(ir); PostingsHighlighter highlighter = new PostingsHighlighter(10000, - BreakIterator.getSentenceInstance(Locale.ROOT), - new PassageScorer(0, 0.75f, 87), - new PassageFormatter()); + BreakIterator.getSentenceInstance(Locale.ROOT)) { + @Override + protected PassageScorer getScorer(String field) { + return new PassageScorer(0, 0.75f, 87); + } + }; BooleanQuery query = new BooleanQuery(); query.add(new TermQuery(new Term("body", "foo")), BooleanClause.Occur.SHOULD); query.add(new TermQuery(new Term("body", "bar")), BooleanClause.Occur.SHOULD); Index: lucene/highlighter/src/java/org/apache/lucene/search/postingshighlight/PostingsHighlighter.java =================================================================== --- lucene/highlighter/src/java/org/apache/lucene/search/postingshighlight/PostingsHighlighter.java (revision 1458950) +++ lucene/highlighter/src/java/org/apache/lucene/search/postingshighlight/PostingsHighlighter.java (working copy) @@ -97,8 +97,6 @@ private final int maxLength; private final BreakIterator breakIterator; - private final PassageScorer scorer; - private final PassageFormatter formatter; /** * Creates a new highlighter with default parameters. @@ -113,7 +111,7 @@ * @throws IllegalArgumentException if maxLength is negative or Integer.MAX_VALUE */ public PostingsHighlighter(int maxLength) { - this(maxLength, BreakIterator.getSentenceInstance(Locale.ROOT), new PassageScorer(), new PassageFormatter()); + this(maxLength, BreakIterator.getSentenceInstance(Locale.ROOT)); } /** @@ -122,11 +120,9 @@ * @param breakIterator used for finding passage * boundaries; pass null to highlight the entire * content as a single Passage. - * @param scorer used for ranking passages. - * @param formatter used for formatting passages into highlighted snippets. * @throws IllegalArgumentException if maxLength is negative or Integer.MAX_VALUE */ - public PostingsHighlighter(int maxLength, BreakIterator breakIterator, PassageScorer scorer, PassageFormatter formatter) { + public PostingsHighlighter(int maxLength, BreakIterator breakIterator) { if (maxLength < 0 || maxLength == Integer.MAX_VALUE) { // two reasons: no overflow problems in BreakIterator.preceding(offset+1), // our sentinel in the offsets queue uses this value to terminate. @@ -135,15 +131,28 @@ if (breakIterator == null) { breakIterator = new WholeBreakIterator(); } - if (scorer == null || formatter == null) { - throw new NullPointerException(); - } this.maxLength = maxLength; this.breakIterator = breakIterator; - this.scorer = scorer; - this.formatter = formatter; } + /** Returns the {@link PassageFormatter} to use for + * formatting passages into highlighted snippets. This + * returns a new {@code PassageFormatter} by default; + * subclasses can override to customize. */ + protected PassageFormatter getFormatter(String field) { + // nocommit can we do this just once & reuse across fields? + return new PassageFormatter(); + } + + /** Returns the {@link PassageScorer} to use for + * ranking passages. This + * returns a new {@code PassageScorer} by default; + * subclasses can override to customize. */ + protected PassageScorer getScorer(String field) { + // nocommit can we do this just once & reuse across fields? + return new PassageScorer(); + } + /** * Highlights the top passages from a single field. * @@ -341,6 +350,11 @@ TermsEnum termsEnum = null; int lastLeaf = -1; + PassageFormatter fieldFormatter = getFormatter(field); + if (fieldFormatter == null) { + throw new NullPointerException("PassageFormatter cannot be null"); + } + for (int i = 0; i < docids.length; i++) { String content = contents[i]; if (content.length() == 0) { @@ -366,7 +380,7 @@ if (passages.length > 0) { // otherwise a null snippet (eg if field is missing // entirely from the doc) - highlights.put(doc, formatter.format(passages, content)); + highlights.put(doc, fieldFormatter.format(passages, content)); } lastLeaf = leaf; } @@ -379,6 +393,7 @@ // score each sentence as norm(sentenceStartOffset) * sum(weight * tf(freq)) private Passage[] highlightDoc(String field, Term terms[], int contentLength, BreakIterator bi, int doc, TermsEnum termsEnum, DocsAndPositionsEnum[] postings, int n) throws IOException { + PassageScorer scorer = getScorer(field); PriorityQueue pq = new PriorityQueue(); float weights[] = new float[terms.length]; // initialize postings