pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / contrib / analyzers / common / src / test / org / apache / lucene / analysis / shingle / ShingleAnalyzerWrapperTest.java
1 package org.apache.lucene.analysis.shingle;
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.Reader;
21 import java.io.StringReader;
22
23 import org.apache.lucene.analysis.Analyzer;
24 import org.apache.lucene.analysis.BaseTokenStreamTestCase;
25 import org.apache.lucene.analysis.MockAnalyzer;
26 import org.apache.lucene.analysis.MockTokenizer;
27 import org.apache.lucene.analysis.TokenStream;
28 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
29 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
30 import org.apache.lucene.document.Document;
31 import org.apache.lucene.document.Field;
32 import org.apache.lucene.index.IndexWriter;
33 import org.apache.lucene.index.IndexWriterConfig;
34 import org.apache.lucene.index.Term;
35 import org.apache.lucene.queryParser.QueryParser;
36 import org.apache.lucene.search.BooleanClause;
37 import org.apache.lucene.search.BooleanQuery;
38 import org.apache.lucene.search.IndexSearcher;
39 import org.apache.lucene.search.PhraseQuery;
40 import org.apache.lucene.search.Query;
41 import org.apache.lucene.search.ScoreDoc;
42 import org.apache.lucene.search.TermQuery;
43 import org.apache.lucene.store.Directory;
44 import org.apache.lucene.store.RAMDirectory;
45
46 /**
47  * A test class for ShingleAnalyzerWrapper as regards queries and scoring.
48  */
49 public class ShingleAnalyzerWrapperTest extends BaseTokenStreamTestCase {
50
51   public IndexSearcher searcher;
52
53   /**
54    * Set up a new index in RAM with three test phrases and the supplied Analyzer.
55    *
56    * @param analyzer the analyzer to use
57    * @return an indexSearcher on the test index.
58    * @throws Exception if an error occurs with index writer or searcher
59    */
60   public IndexSearcher setUpSearcher(Analyzer analyzer) throws Exception {
61     Directory dir = new RAMDirectory();
62     IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, analyzer));
63
64     Document doc;
65     doc = new Document();
66     doc.add(new Field("content", "please divide this sentence into shingles",
67             Field.Store.YES,Field.Index.ANALYZED));
68     writer.addDocument(doc);
69
70     doc = new Document();
71     doc.add(new Field("content", "just another test sentence",
72                       Field.Store.YES,Field.Index.ANALYZED));
73     writer.addDocument(doc);
74
75     doc = new Document();
76     doc.add(new Field("content", "a sentence which contains no test",
77                       Field.Store.YES,Field.Index.ANALYZED));
78     writer.addDocument(doc);
79
80     writer.close();
81
82     return new IndexSearcher(dir, true);
83   }
84
85   protected ScoreDoc[] queryParsingTest(Analyzer analyzer, String qs) throws Exception {
86     searcher = setUpSearcher(analyzer);
87
88     QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, "content", analyzer);
89
90     Query q = qp.parse(qs);
91
92     return searcher.search(q, null, 1000).scoreDocs;
93   }
94
95   protected void compareRanks(ScoreDoc[] hits, int[] ranks) throws Exception {
96     assertEquals(ranks.length, hits.length);
97     for (int i = 0; i < ranks.length; i++) {
98       assertEquals(ranks[i], hits[i].doc);
99     }
100   }
101
102   /*
103    * Will not work on an index without unigrams, since QueryParser automatically
104    * tokenizes on whitespace.
105    */
106   public void testShingleAnalyzerWrapperQueryParsing() throws Exception {
107     ScoreDoc[] hits = queryParsingTest(new ShingleAnalyzerWrapper
108                                      (new MockAnalyzer(random, MockTokenizer.WHITESPACE, false), 2),
109                                  "test sentence");
110     int[] ranks = new int[] { 1, 2, 0 };
111     compareRanks(hits, ranks);
112   }
113
114   /*
115    * This one fails with an exception.
116    */
117   public void testShingleAnalyzerWrapperPhraseQueryParsingFails() throws Exception {
118     ScoreDoc[] hits = queryParsingTest(new ShingleAnalyzerWrapper
119                                      (new MockAnalyzer(random, MockTokenizer.WHITESPACE, false), 2),
120                                  "\"this sentence\"");
121     int[] ranks = new int[] { 0 };
122     compareRanks(hits, ranks);
123   }
124
125   /*
126    * This one works, actually.
127    */
128   public void testShingleAnalyzerWrapperPhraseQueryParsing() throws Exception {
129     ScoreDoc[] hits = queryParsingTest(new ShingleAnalyzerWrapper
130                                      (new MockAnalyzer(random, MockTokenizer.WHITESPACE, false), 2),
131                                  "\"test sentence\"");
132     int[] ranks = new int[] { 1 };
133     compareRanks(hits, ranks);
134   }
135
136   /*
137    * Same as above, is tokenized without using the analyzer.
138    */
139   public void testShingleAnalyzerWrapperRequiredQueryParsing() throws Exception {
140     ScoreDoc[] hits = queryParsingTest(new ShingleAnalyzerWrapper
141                                      (new MockAnalyzer(random, MockTokenizer.WHITESPACE, false), 2),
142                                  "+test +sentence");
143     int[] ranks = new int[] { 1, 2 };
144     compareRanks(hits, ranks);
145   }
146
147   /*
148    * This shows how to construct a phrase query containing shingles.
149    */
150   public void testShingleAnalyzerWrapperPhraseQuery() throws Exception {
151     Analyzer analyzer = new ShingleAnalyzerWrapper(new MockAnalyzer(random, MockTokenizer.WHITESPACE, false), 2);
152     searcher = setUpSearcher(analyzer);
153
154     PhraseQuery q = new PhraseQuery();
155
156     TokenStream ts = analyzer.tokenStream("content",
157                                           new StringReader("this sentence"));
158     int j = -1;
159     
160     PositionIncrementAttribute posIncrAtt = ts.addAttribute(PositionIncrementAttribute.class);
161     CharTermAttribute termAtt = ts.addAttribute(CharTermAttribute.class);
162     
163     ts.reset();
164     while (ts.incrementToken()) {
165       j += posIncrAtt.getPositionIncrement();
166       String termText = termAtt.toString();
167       q.add(new Term("content", termText), j);
168     }
169
170     ScoreDoc[] hits = searcher.search(q, null, 1000).scoreDocs;
171     int[] ranks = new int[] { 0 };
172     compareRanks(hits, ranks);
173   }
174
175   /*
176    * How to construct a boolean query with shingles. A query like this will
177    * implicitly score those documents higher that contain the words in the query
178    * in the right order and adjacent to each other.
179    */
180   public void testShingleAnalyzerWrapperBooleanQuery() throws Exception {
181     Analyzer analyzer = new ShingleAnalyzerWrapper(new MockAnalyzer(random, MockTokenizer.WHITESPACE, false), 2);
182     searcher = setUpSearcher(analyzer);
183
184     BooleanQuery q = new BooleanQuery();
185
186     TokenStream ts = analyzer.tokenStream("content",
187                                           new StringReader("test sentence"));
188     
189     CharTermAttribute termAtt = ts.addAttribute(CharTermAttribute.class);
190     
191     ts.reset();
192
193     while (ts.incrementToken()) {
194       String termText =  termAtt.toString();
195       q.add(new TermQuery(new Term("content", termText)),
196             BooleanClause.Occur.SHOULD);
197     }
198
199     ScoreDoc[] hits = searcher.search(q, null, 1000).scoreDocs;
200     int[] ranks = new int[] { 1, 2, 0 };
201     compareRanks(hits, ranks);
202   }
203   
204   public void testReusableTokenStream() throws Exception {
205     Analyzer a = new ShingleAnalyzerWrapper(new MockAnalyzer(random, MockTokenizer.WHITESPACE, false), 2);
206     assertAnalyzesToReuse(a, "please divide into shingles",
207         new String[] { "please", "please divide", "divide", "divide into", "into", "into shingles", "shingles" },
208         new int[] { 0, 0, 7, 7, 14, 14, 19 },
209         new int[] { 6, 13, 13, 18, 18, 27, 27 },
210         new int[] { 1, 0, 1, 0, 1, 0, 1 });
211     assertAnalyzesToReuse(a, "divide me up again",
212         new String[] { "divide", "divide me", "me", "me up", "up", "up again", "again" },
213         new int[] { 0, 0, 7, 7, 10, 10, 13 },
214         new int[] { 6, 9, 9, 12, 12, 18, 18 },
215         new int[] { 1, 0, 1, 0, 1, 0, 1 });
216   }
217   
218   /*
219    * analyzer that does not support reuse
220    * it is LetterTokenizer on odd invocations, WhitespaceTokenizer on even.
221    */
222   private class NonreusableAnalyzer extends Analyzer {
223     int invocationCount = 0;
224     @Override
225     public TokenStream tokenStream(String fieldName, Reader reader) {
226       if (++invocationCount % 2 == 0)
227         return new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
228       else
229         return new MockTokenizer(reader, MockTokenizer.SIMPLE, false);
230     }
231   }
232   
233   public void testWrappedAnalyzerDoesNotReuse() throws Exception {
234     Analyzer a = new ShingleAnalyzerWrapper(new NonreusableAnalyzer());
235     assertAnalyzesToReuse(a, "please divide into shingles.",
236         new String[] { "please", "please divide", "divide", "divide into", "into", "into shingles", "shingles" },
237         new int[] { 0, 0, 7, 7, 14, 14, 19 },
238         new int[] { 6, 13, 13, 18, 18, 27, 27 },
239         new int[] { 1, 0, 1, 0, 1, 0, 1 });
240     assertAnalyzesToReuse(a, "please divide into shingles.",
241         new String[] { "please", "please divide", "divide", "divide into", "into", "into shingles.", "shingles." },
242         new int[] { 0, 0, 7, 7, 14, 14, 19 },
243         new int[] { 6, 13, 13, 18, 18, 28, 28 },
244         new int[] { 1, 0, 1, 0, 1, 0, 1 });
245     assertAnalyzesToReuse(a, "please divide into shingles.",
246         new String[] { "please", "please divide", "divide", "divide into", "into", "into shingles", "shingles" },
247         new int[] { 0, 0, 7, 7, 14, 14, 19 },
248         new int[] { 6, 13, 13, 18, 18, 27, 27 },
249         new int[] { 1, 0, 1, 0, 1, 0, 1 });
250   }
251
252   public void testNonDefaultMinShingleSize() throws Exception {
253     ShingleAnalyzerWrapper analyzer 
254       = new ShingleAnalyzerWrapper(new MockAnalyzer(random, MockTokenizer.WHITESPACE, false), 3, 4);
255     assertAnalyzesToReuse(analyzer, "please divide this sentence into shingles",
256                           new String[] { "please",   "please divide this",   "please divide this sentence", 
257                                          "divide",   "divide this sentence", "divide this sentence into", 
258                                          "this",     "this sentence into",   "this sentence into shingles",
259                                          "sentence", "sentence into shingles",
260                                          "into",
261                                          "shingles" },
262                           new int[] { 0,  0,  0,  7,  7,  7, 14, 14, 14, 19, 19, 28, 33 },
263                           new int[] { 6, 18, 27, 13, 27, 32, 18, 32, 41, 27, 41, 32, 41 },
264                           new int[] { 1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  1,  1 });
265
266     analyzer = new ShingleAnalyzerWrapper(
267         new MockAnalyzer(random, MockTokenizer.WHITESPACE, false), 3, 4, ShingleFilter.TOKEN_SEPARATOR, false, false);
268     assertAnalyzesToReuse(analyzer, "please divide this sentence into shingles",
269                           new String[] { "please divide this",   "please divide this sentence", 
270                                          "divide this sentence", "divide this sentence into", 
271                                          "this sentence into",   "this sentence into shingles",
272                                          "sentence into shingles" },
273                           new int[] {  0,  0,  7,  7, 14, 14, 19 },
274                           new int[] { 18, 27, 27, 32, 32, 41, 41 },
275                           new int[] {  1,  0,  1,  0,  1,  0,  1 });
276   }
277   
278   public void testNonDefaultMinAndSameMaxShingleSize() throws Exception {
279     ShingleAnalyzerWrapper analyzer
280       = new ShingleAnalyzerWrapper(new MockAnalyzer(random, MockTokenizer.WHITESPACE, false), 3, 3);
281     assertAnalyzesToReuse(analyzer, "please divide this sentence into shingles",
282                           new String[] { "please",   "please divide this", 
283                                          "divide",   "divide this sentence", 
284                                          "this",     "this sentence into",
285                                          "sentence", "sentence into shingles",
286                                          "into",
287                                          "shingles" },
288                           new int[] { 0,  0,  7,  7, 14, 14, 19, 19, 28, 33 },
289                           new int[] { 6, 18, 13, 27, 18, 32, 27, 41, 32, 41 },
290                           new int[] { 1,  0,  1,  0,  1,  0,  1,  0,  1,  1 });
291
292     analyzer = new ShingleAnalyzerWrapper(
293         new MockAnalyzer(random, MockTokenizer.WHITESPACE, false), 3, 3, ShingleFilter.TOKEN_SEPARATOR, false, false);
294     assertAnalyzesToReuse(analyzer, "please divide this sentence into shingles",
295                           new String[] { "please divide this", 
296                                          "divide this sentence", 
297                                          "this sentence into",
298                                          "sentence into shingles" },
299                           new int[] {  0,  7, 14, 19 },
300                           new int[] { 18, 27, 32, 41 },
301                           new int[] {  1,  1,  1,  1 });
302   }
303
304   public void testNoTokenSeparator() throws Exception {
305     ShingleAnalyzerWrapper analyzer = new ShingleAnalyzerWrapper(
306         new MockAnalyzer(random, MockTokenizer.WHITESPACE, false),
307         ShingleFilter.DEFAULT_MIN_SHINGLE_SIZE,
308         ShingleFilter.DEFAULT_MAX_SHINGLE_SIZE,
309         "", true, false);
310     assertAnalyzesToReuse(analyzer, "please divide into shingles",
311                           new String[] { "please", "pleasedivide", 
312                                          "divide", "divideinto", 
313                                          "into", "intoshingles", 
314                                          "shingles" },
315                           new int[] { 0,  0,  7,  7, 14, 14, 19 },
316                           new int[] { 6, 13, 13, 18, 18, 27, 27 },
317                           new int[] { 1,  0,  1,  0,  1,  0,  1 });
318
319     analyzer = new ShingleAnalyzerWrapper(
320         new MockAnalyzer(random, MockTokenizer.WHITESPACE, false),
321         ShingleFilter.DEFAULT_MIN_SHINGLE_SIZE,
322         ShingleFilter.DEFAULT_MAX_SHINGLE_SIZE,
323         "", false, false);
324     assertAnalyzesToReuse(analyzer, "please divide into shingles",
325                           new String[] { "pleasedivide", 
326                                          "divideinto", 
327                                          "intoshingles" },
328                           new int[] {  0,  7, 14 },
329                           new int[] { 13, 18, 27 },
330                           new int[] {  1,  1,  1 });
331   }
332
333   public void testNullTokenSeparator() throws Exception {
334     ShingleAnalyzerWrapper analyzer = new ShingleAnalyzerWrapper(
335         new MockAnalyzer(random, MockTokenizer.WHITESPACE, false),
336         ShingleFilter.DEFAULT_MIN_SHINGLE_SIZE,
337         ShingleFilter.DEFAULT_MAX_SHINGLE_SIZE,
338         null, true, false);
339     assertAnalyzesToReuse(analyzer, "please divide into shingles",
340                           new String[] { "please", "pleasedivide", 
341                                          "divide", "divideinto", 
342                                          "into", "intoshingles", 
343                                          "shingles" },
344                           new int[] { 0,  0,  7,  7, 14, 14, 19 },
345                           new int[] { 6, 13, 13, 18, 18, 27, 27 },
346                           new int[] { 1,  0,  1,  0,  1,  0,  1 });
347
348     analyzer = new ShingleAnalyzerWrapper(
349         new MockAnalyzer(random, MockTokenizer.WHITESPACE, false),
350         ShingleFilter.DEFAULT_MIN_SHINGLE_SIZE,
351         ShingleFilter.DEFAULT_MAX_SHINGLE_SIZE,
352         "", false, false);
353     assertAnalyzesToReuse(analyzer, "please divide into shingles",
354                           new String[] { "pleasedivide", 
355                                          "divideinto", 
356                                          "intoshingles" },
357                           new int[] {  0,  7, 14 },
358                           new int[] { 13, 18, 27 },
359                           new int[] {  1,  1,  1 });
360   }
361   public void testAltTokenSeparator() throws Exception {
362     ShingleAnalyzerWrapper analyzer = new ShingleAnalyzerWrapper(
363         new MockAnalyzer(random, MockTokenizer.WHITESPACE, false),
364         ShingleFilter.DEFAULT_MIN_SHINGLE_SIZE,
365         ShingleFilter.DEFAULT_MAX_SHINGLE_SIZE,
366         "<SEP>", true, false);
367     assertAnalyzesToReuse(analyzer, "please divide into shingles",
368                           new String[] { "please", "please<SEP>divide", 
369                                          "divide", "divide<SEP>into", 
370                                          "into", "into<SEP>shingles", 
371                                          "shingles" },
372                           new int[] { 0,  0,  7,  7, 14, 14, 19 },
373                           new int[] { 6, 13, 13, 18, 18, 27, 27 },
374                           new int[] { 1,  0,  1,  0,  1,  0,  1 });
375
376     analyzer = new ShingleAnalyzerWrapper(
377         new MockAnalyzer(random, MockTokenizer.WHITESPACE, false),
378         ShingleFilter.DEFAULT_MIN_SHINGLE_SIZE,
379         ShingleFilter.DEFAULT_MAX_SHINGLE_SIZE,
380         "<SEP>", false, false);
381     assertAnalyzesToReuse(analyzer, "please divide into shingles",
382                           new String[] { "please<SEP>divide", 
383                                          "divide<SEP>into", 
384                                          "into<SEP>shingles" },
385                           new int[] {  0,  7, 14 },
386                           new int[] { 13, 18, 27 },
387                           new int[] {  1,  1,  1 });
388   }
389   
390   public void testOutputUnigramsIfNoShinglesSingleToken() throws Exception {
391     ShingleAnalyzerWrapper analyzer = new ShingleAnalyzerWrapper(
392         new MockAnalyzer(random, MockTokenizer.WHITESPACE, false),
393         ShingleFilter.DEFAULT_MIN_SHINGLE_SIZE,
394         ShingleFilter.DEFAULT_MAX_SHINGLE_SIZE,
395         "", false, true);
396     assertAnalyzesToReuse(analyzer, "please",
397                           new String[] { "please" },
398                           new int[] { 0 },
399                           new int[] { 6 },
400                           new int[] { 1 });
401   }
402 }