~slub.team/goobi-indexserver/3.x

« back to all changes in this revision

Viewing changes to lucene/contrib/facet/src/test/org/apache/lucene/facet/taxonomy/TestTaxonomyCombined.java

  • Committer: Sebastian Meyer
  • Date: 2012-08-03 09:12:40 UTC
  • Revision ID: sebastian.meyer@slub-dresden.de-20120803091240-x6861b0vabq1xror
Remove Lucene and Solr source code and add patches instead
Fix Bug #985487: Auto-suggestion for the search interface

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
package org.apache.lucene.facet.taxonomy;
2
 
 
3
 
import java.io.IOException;
4
 
import java.io.PrintWriter;
5
 
import java.io.StringWriter;
6
 
import java.util.ArrayList;
7
 
import java.util.Arrays;
8
 
 
9
 
import org.apache.lucene.store.Directory;
10
 
import org.apache.lucene.store.LockObtainFailedException;
11
 
import org.apache.lucene.store.RAMDirectory;
12
 
import org.junit.Ignore;
13
 
import org.junit.Test;
14
 
 
15
 
import org.apache.lucene.util.LuceneTestCase;
16
 
import org.apache.lucene.facet.taxonomy.TaxonomyReader.ChildrenArrays;
17
 
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
18
 
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter;
19
 
import org.apache.lucene.util.SlowRAMDirectory;
20
 
 
21
 
/**
22
 
 * Licensed to the Apache Software Foundation (ASF) under one or more
23
 
 * contributor license agreements.  See the NOTICE file distributed with
24
 
 * this work for additional information regarding copyright ownership.
25
 
 * The ASF licenses this file to You under the Apache License, Version 2.0
26
 
 * (the "License"); you may not use this file except in compliance with
27
 
 * the License.  You may obtain a copy of the License at
28
 
 *
29
 
 *     http://www.apache.org/licenses/LICENSE-2.0
30
 
 *
31
 
 * Unless required by applicable law or agreed to in writing, software
32
 
 * distributed under the License is distributed on an "AS IS" BASIS,
33
 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
34
 
 * See the License for the specific language governing permissions and
35
 
 * limitations under the License.
36
 
 */
37
 
 
38
 
public class TestTaxonomyCombined extends LuceneTestCase {
39
 
 
40
 
  /**  The following categories will be added to the taxonomy by
41
 
    fillTaxonomy(), and tested by all tests below:
42
 
  */
43
 
  private final static String[][] categories = {
44
 
    { "Author", "Tom Clancy" },
45
 
    { "Author", "Richard Dawkins" },
46
 
    { "Author", "Richard Adams" },
47
 
    { "Price", "10", "11" },
48
 
    { "Price", "10", "12" },
49
 
    { "Price", "20", "27" },
50
 
    { "Date", "2006", "05" },
51
 
    { "Date", "2005" },
52
 
    { "Date", "2006" },
53
 
    { "Subject", "Nonfiction", "Children", "Animals" },
54
 
    { "Author", "Stephen Jay Gould" },
55
 
    { "Author", "\u05e0\u05d3\u05d1\u3042\u0628" },
56
 
  };
57
 
  
58
 
  /**  When adding the above categories with TaxonomyWriter.addCategory(), 
59
 
    the following paths are expected to be returned:
60
 
    (note that currently the full path is not returned, and therefore
61
 
    not tested - rather, just the last component, the ordinal, is returned
62
 
    and tested.
63
 
  */
64
 
  private final static int[][] expectedPaths = {
65
 
    { 1, 2 },
66
 
    { 1, 3 },
67
 
    { 1, 4 },
68
 
    { 5, 6, 7 },
69
 
    { 5, 6, 8 },
70
 
    { 5, 9, 10 },
71
 
    { 11, 12, 13 },
72
 
    { 11, 14 },
73
 
    { 11, 12 },
74
 
    { 15, 16, 17, 18 },
75
 
    { 1, 19 },
76
 
    { 1, 20 }
77
 
  };
78
 
 
79
 
  /**  The taxonomy index is expected to then contain the following
80
 
    generated categories, with increasing ordinals (note how parent
81
 
    categories are be added automatically when subcategories are added).
82
 
   */  
83
 
  private final static String[][] expectedCategories = {
84
 
    { }, // the root category
85
 
    { "Author" },
86
 
    { "Author", "Tom Clancy" },
87
 
    { "Author", "Richard Dawkins" },
88
 
    { "Author", "Richard Adams" },
89
 
    { "Price" },
90
 
    { "Price", "10" },
91
 
    { "Price", "10", "11" },
92
 
    { "Price", "10", "12" },
93
 
    { "Price", "20" },
94
 
    { "Price", "20", "27" },
95
 
    { "Date" },
96
 
    { "Date", "2006" },
97
 
    { "Date", "2006", "05" },
98
 
    { "Date", "2005" },
99
 
    { "Subject" },
100
 
    { "Subject", "Nonfiction" },
101
 
    { "Subject", "Nonfiction", "Children" },
102
 
    { "Subject", "Nonfiction", "Children", "Animals" },
103
 
    { "Author", "Stephen Jay Gould" },
104
 
    { "Author", "\u05e0\u05d3\u05d1\u3042\u0628" },
105
 
  };
106
 
 
107
 
  /**  fillTaxonomy adds the categories in the categories[] array, and asserts
108
 
    that the additions return exactly the ordinals (in the past - paths)
109
 
    specified in expectedPaths[].
110
 
    Note that this assumes that fillTaxonomy() is called on an empty taxonomy
111
 
    index. Calling it after something else was already added to the taxonomy
112
 
    index will surely have this method fail.
113
 
   */
114
 
  public static void fillTaxonomy(TaxonomyWriter tw) throws IOException {
115
 
    for (int i = 0; i < categories.length; i++) {
116
 
      int ordinal = tw.addCategory(new CategoryPath(categories[i]));
117
 
      int expectedOrdinal = expectedPaths[i][expectedPaths[i].length-1];
118
 
      if (ordinal!=expectedOrdinal) {
119
 
        fail("For category "+showcat(categories[i])+" expected ordinal "+
120
 
            expectedOrdinal+", but got "+ordinal);
121
 
      }
122
 
    }
123
 
  }
124
 
 
125
 
  public static String showcat(String[] path) {
126
 
    if (path==null) {
127
 
      return "<null>";
128
 
    }
129
 
    if (path.length==0) {
130
 
      return "<empty>";
131
 
    }
132
 
    if (path.length==1 && path[0].length()==0) {
133
 
      return "<\"\">";
134
 
    }
135
 
    StringBuilder sb = new StringBuilder(path[0]);
136
 
    for (int i=1; i<path.length; i++) {
137
 
      sb.append('/');
138
 
      sb.append(path[i]);
139
 
    }
140
 
    return sb.toString();
141
 
  }
142
 
 
143
 
  private String showcat(CategoryPath path) {
144
 
    if (path==null) {
145
 
      return "<null>";
146
 
    }
147
 
    if (path.length()==0) {
148
 
      return "<empty>";
149
 
    }
150
 
    return "<"+path.toString('/')+">";
151
 
  }
152
 
 
153
 
  /**  Basic tests for TaxonomyWriter. Basically, we test that
154
 
    IndexWriter.addCategory works, i.e. returns the expected ordinals
155
 
    (this is tested by calling the fillTaxonomy() method above).
156
 
    We do not test here that after writing the index can be read -
157
 
    this will be done in more tests below.
158
 
   */
159
 
  @Test
160
 
  public void testWriter() throws Exception {
161
 
    Directory indexDir = newDirectory();
162
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
163
 
    fillTaxonomy(tw);
164
 
    // Also check TaxonomyWriter.getSize() - see that the taxonomy's size
165
 
    // is what we expect it to be.
166
 
    assertEquals(expectedCategories.length, tw.getSize());
167
 
    tw.close();
168
 
    indexDir.close();
169
 
  }
170
 
 
171
 
  /**  testWriterTwice is exactly like testWriter, except that after adding
172
 
    all the categories, we add them again, and see that we get the same
173
 
    old ids again - not new categories.
174
 
   */
175
 
  @Test
176
 
  public void testWriterTwice() throws Exception {
177
 
    Directory indexDir = newDirectory();
178
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
179
 
    fillTaxonomy(tw);
180
 
    // run fillTaxonomy again - this will try to add the same categories
181
 
    // again, and check that we see the same ordinal paths again, not
182
 
    // different ones. 
183
 
    fillTaxonomy(tw);
184
 
    // Let's check the number of categories again, to see that no
185
 
    // extraneous categories were created:
186
 
    assertEquals(expectedCategories.length, tw.getSize());    
187
 
    tw.close();
188
 
    indexDir.close();
189
 
  }
190
 
 
191
 
  /**  testWriterTwice2 is similar to testWriterTwice, except that the index
192
 
    is closed and reopened before attempting to write to it the same
193
 
    categories again. While testWriterTwice can get along with writing
194
 
    and reading correctly just to the cache, testWriterTwice2 checks also
195
 
    the actual disk read part of the writer:
196
 
   */
197
 
  @Test
198
 
  public void testWriterTwice2() throws Exception {
199
 
    Directory indexDir = newDirectory();
200
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
201
 
    fillTaxonomy(tw);
202
 
    tw.close();
203
 
    tw = new DirectoryTaxonomyWriter(indexDir);
204
 
    // run fillTaxonomy again - this will try to add the same categories
205
 
    // again, and check that we see the same ordinals again, not different
206
 
    // ones, and that the number of categories hasn't grown by the new
207
 
    // additions
208
 
    fillTaxonomy(tw);
209
 
    assertEquals(expectedCategories.length, tw.getSize());    
210
 
    tw.close();
211
 
    indexDir.close();
212
 
  }
213
 
  
214
 
  /**
215
 
   * testWriterTwice3 is yet another test which tests creating a taxonomy
216
 
   * in two separate writing sessions. This test used to fail because of
217
 
   * a bug involving commit(), explained below, and now should succeed.
218
 
   * 
219
 
   * @throws Exception
220
 
   */
221
 
  @Test
222
 
  public void testWriterTwice3() throws Exception {
223
 
    Directory indexDir = newDirectory();
224
 
    // First, create and fill the taxonomy
225
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
226
 
    fillTaxonomy(tw);
227
 
    tw.close();
228
 
    // Now, open the same taxonomy and add the same categories again.
229
 
    // After a few categories, the LuceneTaxonomyWriter implementation
230
 
    // will stop looking for each category on disk, and rather read them
231
 
    // all into memory and close it's reader. The bug was that it closed
232
 
    // the reader, but forgot that it did (because it didn't set the reader
233
 
    // reference to null).
234
 
    tw = new DirectoryTaxonomyWriter(indexDir);
235
 
    fillTaxonomy(tw);
236
 
    // Add one new category, just to make commit() do something:
237
 
    tw.addCategory(new CategoryPath("hi"));
238
 
    // Do a commit(). Here was a bug - if tw had a reader open, it should
239
 
    // be reopened after the commit. However, in our case the reader should
240
 
    // not be open (as explained above) but because it was not set to null,
241
 
    // we forgot that, tried to reopen it, and got an AlreadyClosedException.
242
 
    tw.commit();
243
 
    assertEquals(expectedCategories.length+1, tw.getSize());    
244
 
    tw.close();
245
 
    indexDir.close();
246
 
  }  
247
 
  
248
 
  /**  Another set of tests for the writer, which don't use an array and
249
 
   *  try to distill the different cases, and therefore may be more helpful
250
 
   *  for debugging a problem than testWriter() which is hard to know why
251
 
   *  or where it failed. 
252
 
   */
253
 
  @Test
254
 
  public void testWriterSimpler() throws Exception {
255
 
    Directory indexDir = newDirectory();
256
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
257
 
    assertEquals(1, tw.getSize()); // the root only
258
 
    // Test that adding a new top-level category works
259
 
    assertEquals(1, tw.addCategory(new CategoryPath("a")));
260
 
    assertEquals(2, tw.getSize());
261
 
    // Test that adding the same category again is noticed, and the
262
 
    // same ordinal (and not a new one) is returned.
263
 
    assertEquals(1, tw.addCategory(new CategoryPath("a")));
264
 
    assertEquals(2, tw.getSize());
265
 
    // Test that adding another top-level category returns a new ordinal,
266
 
    // not the same one
267
 
    assertEquals(2, tw.addCategory(new CategoryPath("b")));
268
 
    assertEquals(3, tw.getSize());
269
 
    // Test that adding a category inside one of the above adds just one
270
 
    // new ordinal:
271
 
    assertEquals(3, tw.addCategory(new CategoryPath("a","c")));
272
 
    assertEquals(4, tw.getSize());
273
 
    // Test that adding the same second-level category doesn't do anything:
274
 
    assertEquals(3, tw.addCategory(new CategoryPath("a","c")));
275
 
    assertEquals(4, tw.getSize());
276
 
    // Test that adding a second-level category with two new components
277
 
    // indeed adds two categories
278
 
    assertEquals(5, tw.addCategory(new CategoryPath("d","e")));
279
 
    assertEquals(6, tw.getSize());
280
 
    // Verify that the parents were added above in the order we expected
281
 
    assertEquals(4, tw.addCategory(new CategoryPath("d")));
282
 
    // Similar, but inside a category that already exists:
283
 
    assertEquals(7, tw.addCategory(new CategoryPath("b", "d","e")));
284
 
    assertEquals(8, tw.getSize());
285
 
    // And now inside two levels of categories that already exist:
286
 
    assertEquals(8, tw.addCategory(new CategoryPath("b", "d","f")));
287
 
    assertEquals(9, tw.getSize());
288
 
    
289
 
    tw.close();
290
 
    indexDir.close();
291
 
  }
292
 
  
293
 
  /**  Test writing an empty index, and seeing that a reader finds in it
294
 
    the root category, and only it. We check all the methods on that
295
 
    root category return the expected results.
296
 
   */
297
 
  @Test
298
 
  public void testRootOnly() throws Exception {
299
 
    Directory indexDir = newDirectory();
300
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
301
 
    // right after opening the index, it should already contain the
302
 
    // root, so have size 1:
303
 
    assertEquals(1, tw.getSize());
304
 
    tw.close();
305
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
306
 
    assertEquals(1, tr.getSize());
307
 
    assertEquals(0, tr.getPath(0).length());
308
 
    assertEquals(TaxonomyReader.INVALID_ORDINAL, tr.getParent(0));
309
 
    assertEquals(0, tr.getOrdinal(new CategoryPath()));
310
 
    tr.close();
311
 
    indexDir.close();
312
 
  }
313
 
 
314
 
  /**  The following test is exactly the same as testRootOnly, except we
315
 
   *  do not close the writer before opening the reader. We want to see
316
 
   *  that the root is visible to the reader not only after the writer is
317
 
   *  closed, but immediately after it is created.
318
 
   */
319
 
  @Test
320
 
  public void testRootOnly2() throws Exception {
321
 
    Directory indexDir = newDirectory();
322
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
323
 
    tw.commit();
324
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
325
 
    assertEquals(1, tr.getSize());
326
 
    assertEquals(0, tr.getPath(0).length());
327
 
    assertEquals(TaxonomyReader.INVALID_ORDINAL, tr.getParent(0));
328
 
    assertEquals(0, tr.getOrdinal(new CategoryPath()));
329
 
    tw.close();
330
 
    tr.close();
331
 
    indexDir.close();
332
 
  }
333
 
 
334
 
  /**  Basic tests for TaxonomyReader's category <=> ordinal transformations
335
 
    (getSize(), getCategory() and getOrdinal()).
336
 
    We test that after writing the index, it can be read and all the
337
 
    categories and ordinals are there just as we expected them to be.
338
 
   */
339
 
  @Test
340
 
  public void testReaderBasic() throws Exception {
341
 
    Directory indexDir = newDirectory();
342
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
343
 
    fillTaxonomy(tw);
344
 
    tw.close();
345
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
346
 
 
347
 
    // test TaxonomyReader.getSize():
348
 
    assertEquals(expectedCategories.length, tr.getSize());
349
 
 
350
 
    // test round trips of ordinal => category => ordinal
351
 
    for (int i=0; i<tr.getSize(); i++) {
352
 
      assertEquals(i, tr.getOrdinal(tr.getPath(i)));
353
 
    }
354
 
 
355
 
    // test TaxonomyReader.getCategory():
356
 
    for (int i=0; i<tr.getSize(); i++) {
357
 
      CategoryPath expectedCategory = new CategoryPath(expectedCategories[i]);
358
 
      CategoryPath category = tr.getPath(i);
359
 
      if (!expectedCategory.equals(category)) {
360
 
        fail("For ordinal "+i+" expected category "+
361
 
            showcat(expectedCategory)+", but got "+showcat(category));
362
 
      }
363
 
    }
364
 
    //  (also test invalid ordinals:)
365
 
    assertNull(tr.getPath(-1));
366
 
    assertNull(tr.getPath(tr.getSize()));
367
 
    assertNull(tr.getPath(TaxonomyReader.INVALID_ORDINAL));
368
 
 
369
 
    // test TaxonomyReader.getOrdinal():
370
 
    for (int i=0; i<expectedCategories.length; i++) {
371
 
      int expectedOrdinal = i;
372
 
      int ordinal = tr.getOrdinal(new CategoryPath(expectedCategories[i]));
373
 
      if (expectedOrdinal != ordinal) {
374
 
        fail("For category "+showcat(expectedCategories[i])+" expected ordinal "+
375
 
            expectedOrdinal+", but got "+ordinal);
376
 
      }
377
 
    }
378
 
    // (also test invalid categories:)
379
 
    assertEquals(TaxonomyReader.INVALID_ORDINAL, tr.getOrdinal(new CategoryPath("non-existant")));
380
 
    assertEquals(TaxonomyReader.INVALID_ORDINAL, tr.getOrdinal(new CategoryPath("Author", "Jules Verne")));
381
 
 
382
 
    tr.close();
383
 
    indexDir.close();
384
 
  }
385
 
 
386
 
  /**  Tests for TaxonomyReader's getParent() method.
387
 
    We check it by comparing its results to those we could have gotten by
388
 
    looking at the category string paths (where the parentage is obvious).
389
 
    Note that after testReaderBasic(), we already know we can trust the
390
 
    ordinal <=> category conversions.
391
 
    
392
 
    Note: At the moment, the parent methods in the reader are deprecated,
393
 
    but this does not mean they should not be tested! Until they are
394
 
    removed (*if* they are removed), these tests should remain to see
395
 
    that they still work correctly.
396
 
   */
397
 
 
398
 
  @Test
399
 
  public void testReaderParent() throws Exception {
400
 
    Directory indexDir = newDirectory();
401
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
402
 
    fillTaxonomy(tw);
403
 
    tw.close();
404
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
405
 
 
406
 
    // check that the parent of the root ordinal is the invalid ordinal:
407
 
    assertEquals(TaxonomyReader.INVALID_ORDINAL, tr.getParent(0));
408
 
 
409
 
    // check parent of non-root ordinals:
410
 
    for (int ordinal=1; ordinal<tr.getSize(); ordinal++) {
411
 
      CategoryPath me = tr.getPath(ordinal);
412
 
      int parentOrdinal = tr.getParent(ordinal);
413
 
      CategoryPath parent = tr.getPath(parentOrdinal);
414
 
      if (parent==null) {
415
 
        fail("Parent of "+ordinal+" is "+parentOrdinal+
416
 
        ", but this is not a valid category.");
417
 
      }
418
 
      // verify that the parent is indeed my parent, according to the strings
419
 
      if (!new CategoryPath(me, me.length()-1).equals(parent)) {
420
 
        fail("Got parent "+parentOrdinal+" for ordinal "+ordinal+
421
 
            " but categories are "+showcat(parent)+" and "+showcat(me)+
422
 
            " respectively.");
423
 
      }
424
 
    }
425
 
 
426
 
    // check parent of of invalid ordinals:
427
 
    try {
428
 
      tr.getParent(-1);
429
 
      fail("getParent for -1 should throw exception");
430
 
    } catch (ArrayIndexOutOfBoundsException e) {
431
 
      // ok
432
 
    }
433
 
    try {
434
 
      tr.getParent(TaxonomyReader.INVALID_ORDINAL);
435
 
      fail("getParent for INVALID_ORDINAL should throw exception");
436
 
    } catch (ArrayIndexOutOfBoundsException e) {
437
 
      // ok
438
 
    }
439
 
    try {
440
 
      int parent = tr.getParent(tr.getSize());
441
 
      fail("getParent for getSize() should throw exception, but returned "+parent);
442
 
    } catch (ArrayIndexOutOfBoundsException e) {
443
 
      // ok
444
 
    }
445
 
 
446
 
    tr.close();
447
 
    indexDir.close();
448
 
  }
449
 
  
450
 
  /**
451
 
   * Tests for TaxonomyWriter's getParent() method. We check it by comparing
452
 
   * its results to those we could have gotten by looking at the category
453
 
   * string paths using a TaxonomyReader (where the parentage is obvious).
454
 
   * Note that after testReaderBasic(), we already know we can trust the
455
 
   * ordinal <=> category conversions from TaxonomyReader.
456
 
   *
457
 
   * The difference between testWriterParent1 and testWriterParent2 is that
458
 
   * the former closes the taxonomy writer before reopening it, while the
459
 
   * latter does not.
460
 
   * 
461
 
   * This test code is virtually identical to that of testReaderParent().
462
 
   */
463
 
  @Test
464
 
  public void testWriterParent1() throws Exception {
465
 
    Directory indexDir = newDirectory();
466
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
467
 
    fillTaxonomy(tw);
468
 
    tw.close();
469
 
    tw = new DirectoryTaxonomyWriter(indexDir);
470
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
471
 
    
472
 
    checkWriterParent(tr, tw);
473
 
    
474
 
    tw.close();
475
 
    tr.close();
476
 
    indexDir.close();
477
 
  }
478
 
 
479
 
  @Test
480
 
  public void testWriterParent2() throws Exception {
481
 
    Directory indexDir = newDirectory();
482
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
483
 
    fillTaxonomy(tw);
484
 
    tw.commit();
485
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
486
 
    
487
 
    checkWriterParent(tr, tw);
488
 
    
489
 
    tw.close();
490
 
    tr.close();
491
 
    indexDir.close();
492
 
  }
493
 
  
494
 
  private void checkWriterParent(TaxonomyReader tr, TaxonomyWriter tw) throws Exception {
495
 
    // check that the parent of the root ordinal is the invalid ordinal:
496
 
    assertEquals(TaxonomyReader.INVALID_ORDINAL, tw.getParent(0));
497
 
 
498
 
    // check parent of non-root ordinals:
499
 
    for (int ordinal = 1; ordinal < tr.getSize(); ordinal++) {
500
 
      CategoryPath me = tr.getPath(ordinal);
501
 
      int parentOrdinal = tw.getParent(ordinal);
502
 
      CategoryPath parent = tr.getPath(parentOrdinal);
503
 
      if (parent == null) {
504
 
        fail("Parent of " + ordinal + " is " + parentOrdinal
505
 
            + ", but this is not a valid category.");
506
 
      }
507
 
      // verify that the parent is indeed my parent, according to the
508
 
      // strings
509
 
      if (!new CategoryPath(me, me.length() - 1).equals(parent)) {
510
 
        fail("Got parent " + parentOrdinal + " for ordinal " + ordinal
511
 
            + " but categories are " + showcat(parent) + " and "
512
 
            + showcat(me) + " respectively.");
513
 
      }
514
 
    }
515
 
 
516
 
    // check parent of of invalid ordinals:
517
 
    try {
518
 
      tw.getParent(-1);
519
 
      fail("getParent for -1 should throw exception");
520
 
    } catch (ArrayIndexOutOfBoundsException e) {
521
 
      // ok
522
 
    }
523
 
    try {
524
 
      tw.getParent(TaxonomyReader.INVALID_ORDINAL);
525
 
      fail("getParent for INVALID_ORDINAL should throw exception");
526
 
    } catch (ArrayIndexOutOfBoundsException e) {
527
 
      // ok
528
 
    }
529
 
    try {
530
 
      int parent = tw.getParent(tr.getSize());
531
 
      fail("getParent for getSize() should throw exception, but returned "
532
 
          + parent);
533
 
    } catch (ArrayIndexOutOfBoundsException e) {
534
 
      // ok
535
 
    }
536
 
  }
537
 
 
538
 
  /**  Tests TaxonomyReader's getParentArray() method. We do not test this
539
 
    method directly, but rather just compare its results to those from
540
 
    other methods (which we have already tested above).
541
 
   */
542
 
  @Test
543
 
  public void testReaderParentArray() throws Exception {
544
 
    Directory indexDir = newDirectory();
545
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
546
 
    fillTaxonomy(tw);
547
 
    tw.close();
548
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
549
 
    int[] parents = tr.getParentArray();
550
 
    assertEquals(tr.getSize(), parents.length);
551
 
    for (int i=0; i<tr.getSize(); i++) {
552
 
      assertEquals(tr.getParent(i), parents[i]);
553
 
    }
554
 
    tr.close();
555
 
    indexDir.close();
556
 
  }
557
 
  
558
 
  /**
559
 
   * Test TaxonomyReader's child browsing method, getChildrenArrays()
560
 
   * This only tests for correctness of the data on one example - we have
561
 
   * below further tests on data refresh etc.
562
 
   */
563
 
  @Test
564
 
  public void testChildrenArrays() throws Exception {
565
 
    Directory indexDir = newDirectory();
566
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
567
 
    fillTaxonomy(tw);
568
 
    tw.close();
569
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
570
 
    ChildrenArrays ca = tr.getChildrenArrays();
571
 
    int[] youngestChildArray = ca.getYoungestChildArray();
572
 
    assertEquals(tr.getSize(), youngestChildArray.length);
573
 
    int[] olderSiblingArray = ca.getOlderSiblingArray();
574
 
    assertEquals(tr.getSize(), olderSiblingArray.length);
575
 
    for (int i=0; i<expectedCategories.length; i++) {
576
 
      // find expected children by looking at all expectedCategories
577
 
      // for children
578
 
      ArrayList<Integer> expectedChildren = new ArrayList<Integer>();
579
 
      for (int j=expectedCategories.length-1; j>=0; j--) {
580
 
        if (expectedCategories[j].length != expectedCategories[i].length+1) {
581
 
          continue; // not longer by 1, so can't be a child
582
 
        }
583
 
        boolean ischild=true;
584
 
        for (int k=0; k<expectedCategories[i].length; k++) {
585
 
          if (!expectedCategories[j][k].equals(expectedCategories[i][k])) {
586
 
            ischild=false;
587
 
            break;
588
 
          }
589
 
        }
590
 
        if (ischild) {
591
 
          expectedChildren.add(j);
592
 
        }
593
 
      }
594
 
      // check that children and expectedChildren are the same, with the
595
 
      // correct reverse (youngest to oldest) order:
596
 
      if (expectedChildren.size()==0) {
597
 
        assertEquals(TaxonomyReader.INVALID_ORDINAL, youngestChildArray[i]);
598
 
      } else {
599
 
        int child = youngestChildArray[i];
600
 
        assertEquals(expectedChildren.get(0).intValue(),
601
 
            child);
602
 
        for (int j=1; j<expectedChildren.size(); j++) {
603
 
          child = olderSiblingArray[child];
604
 
          assertEquals(expectedChildren.get(j).intValue(),
605
 
              child);
606
 
          // if child is INVALID_ORDINAL we should stop, but
607
 
          // assertEquals would fail in this case anyway.
608
 
        }
609
 
        // When we're done comparing, olderSiblingArray should now point
610
 
        // to INVALID_ORDINAL, saying there are no more children. If it
611
 
        // doesn't, we found too many children...
612
 
        assertEquals(-1, olderSiblingArray[child]);
613
 
      }
614
 
    }
615
 
    tr.close();
616
 
    indexDir.close();
617
 
  }
618
 
 
619
 
  /**
620
 
   * Similar to testChildrenArrays, except rather than look at
621
 
   * expected results, we test for several "invariants" that the results
622
 
   * should uphold, e.g., that a child of a category indeed has this category
623
 
   * as its parent. This sort of test can more easily be extended to larger
624
 
   * example taxonomies, because we do not need to build the expected list
625
 
   * of categories like we did in the above test.
626
 
   */
627
 
  @Test
628
 
  public void testChildrenArraysInvariants() throws Exception {
629
 
    Directory indexDir = newDirectory();
630
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
631
 
    fillTaxonomy(tw);
632
 
    tw.close();
633
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
634
 
    ChildrenArrays ca = tr.getChildrenArrays();
635
 
    int[] youngestChildArray = ca.getYoungestChildArray();
636
 
    assertEquals(tr.getSize(), youngestChildArray.length);
637
 
    int[] olderSiblingArray = ca.getOlderSiblingArray();
638
 
    assertEquals(tr.getSize(), olderSiblingArray.length);
639
 
        
640
 
    // test that the "youngest child" of every category is indeed a child:
641
 
    for (int i=0; i<tr.getSize(); i++) {
642
 
      int youngestChild = youngestChildArray[i];
643
 
      if (youngestChild != TaxonomyReader.INVALID_ORDINAL) {
644
 
        assertEquals(i, tr.getParent(youngestChild));
645
 
      }
646
 
    }
647
 
        
648
 
    // test that the "older sibling" of every category is indeed older (lower)
649
 
    // (it can also be INVALID_ORDINAL, which is lower than any ordinal)
650
 
    for (int i=0; i<tr.getSize(); i++) {
651
 
      assertTrue("olderSiblingArray["+i+"] should be <"+i, olderSiblingArray[i] < i);
652
 
    }
653
 
    
654
 
    // test that the "older sibling" of every category is indeed a sibling
655
 
    // (they share the same parent)
656
 
    for (int i=0; i<tr.getSize(); i++) {
657
 
      int sibling = olderSiblingArray[i];
658
 
      if (sibling == TaxonomyReader.INVALID_ORDINAL) {
659
 
        continue;
660
 
      }
661
 
      assertEquals(tr.getParent(i), tr.getParent(sibling));
662
 
    }
663
 
    
664
 
    // And now for slightly more complex (and less "invariant-like"...)
665
 
    // tests:
666
 
    
667
 
    // test that the "youngest child" is indeed the youngest (so we don't
668
 
    // miss the first children in the chain)
669
 
    for (int i=0; i<tr.getSize(); i++) {
670
 
      // Find the really youngest child:
671
 
      int j;
672
 
      for (j=tr.getSize()-1; j>i; j--) {
673
 
        if (tr.getParent(j)==i) {
674
 
          break; // found youngest child
675
 
        }
676
 
      }
677
 
      if (j==i) { // no child found
678
 
        j=TaxonomyReader.INVALID_ORDINAL;
679
 
      }
680
 
      assertEquals(j, youngestChildArray[i]);
681
 
    }
682
 
 
683
 
    // test that the "older sibling" is indeed the least oldest one - and
684
 
    // not a too old one or -1 (so we didn't miss some children in the
685
 
    // middle or the end of the chain).
686
 
    for (int i=0; i<tr.getSize(); i++) {
687
 
      // Find the youngest older sibling:
688
 
      int j;
689
 
      for (j=i-1; j>=0; j--) {
690
 
        if (tr.getParent(j)==tr.getParent(i)) {
691
 
          break; // found youngest older sibling
692
 
        }
693
 
      }
694
 
      if (j<0) { // no sibling found
695
 
        j=TaxonomyReader.INVALID_ORDINAL;
696
 
      }
697
 
      assertEquals(j, olderSiblingArray[i]);
698
 
    }
699
 
  
700
 
    tr.close();
701
 
    indexDir.close();
702
 
  }
703
 
  
704
 
  /**
705
 
   * Test how getChildrenArrays() deals with the taxonomy's growth:
706
 
   */
707
 
  @Test
708
 
  public void testChildrenArraysGrowth() throws Exception {
709
 
    Directory indexDir = newDirectory();
710
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
711
 
    tw.addCategory(new CategoryPath("hi", "there"));
712
 
    tw.commit();
713
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
714
 
    ChildrenArrays ca = tr.getChildrenArrays();
715
 
    assertEquals(3, tr.getSize());
716
 
    assertEquals(3, ca.getOlderSiblingArray().length);
717
 
    assertEquals(3, ca.getYoungestChildArray().length);
718
 
    assertTrue(Arrays.equals(new int[] { 1, 2, -1 }, ca.getYoungestChildArray()));
719
 
    assertTrue(Arrays.equals(new int[] { -1, -1, -1 }, ca.getOlderSiblingArray()));
720
 
    tw.addCategory(new CategoryPath("hi", "ho"));
721
 
    tw.addCategory(new CategoryPath("hello"));
722
 
    tw.commit();
723
 
    // Before refresh, nothing changed..
724
 
    ChildrenArrays newca = tr.getChildrenArrays();
725
 
    assertSame(newca, ca); // we got exactly the same object
726
 
    assertEquals(3, tr.getSize());
727
 
    assertEquals(3, ca.getOlderSiblingArray().length);
728
 
    assertEquals(3, ca.getYoungestChildArray().length);
729
 
    // After the refresh, things change:
730
 
    tr.refresh();
731
 
    ca = tr.getChildrenArrays();
732
 
    assertEquals(5, tr.getSize());
733
 
    assertEquals(5, ca.getOlderSiblingArray().length);
734
 
    assertEquals(5, ca.getYoungestChildArray().length);
735
 
    assertTrue(Arrays.equals(new int[] { 4, 3, -1, -1, -1 }, ca.getYoungestChildArray()));
736
 
    assertTrue(Arrays.equals(new int[] { -1, -1, -1, 2, 1 }, ca.getOlderSiblingArray()));
737
 
    tw.close();
738
 
    tr.close();
739
 
    indexDir.close();
740
 
  }
741
 
  
742
 
  /**
743
 
   * Test that getParentArrays is valid when retrieved during refresh
744
 
   */
745
 
  @Test
746
 
  @Ignore
747
 
  public void testTaxonomyReaderRefreshRaces() throws Exception {
748
 
    // compute base child arrays - after first chunk, and after the other
749
 
    Directory indexDirBase =  newDirectory();
750
 
    TaxonomyWriter twBase = new DirectoryTaxonomyWriter(indexDirBase);
751
 
    twBase.addCategory(new CategoryPath("a", "0"));
752
 
    final CategoryPath abPath = new CategoryPath("a", "b");
753
 
    twBase.addCategory(abPath);
754
 
    twBase.commit();
755
 
    TaxonomyReader trBase = new DirectoryTaxonomyReader(indexDirBase);
756
 
 
757
 
    final ChildrenArrays ca1 = trBase.getChildrenArrays();
758
 
    
759
 
    final int abOrd = trBase.getOrdinal(abPath);
760
 
    final int abYoungChildBase1 = ca1.getYoungestChildArray()[abOrd]; 
761
 
    
762
 
    for (int i=0; i < 1<<10; i++) { //1024 facets
763
 
      twBase.addCategory(new CategoryPath("a", "b", Integer.toString(i)));
764
 
    }
765
 
    twBase.commit();
766
 
    
767
 
    trBase.refresh();
768
 
    
769
 
    final ChildrenArrays ca2 = trBase.getChildrenArrays();
770
 
    final int abYoungChildBase2 = ca2.getYoungestChildArray()[abOrd];
771
 
    
772
 
    for (int retry=0; retry<100; retry++) {
773
 
      assertConsistentYoungestChild(abPath, abOrd, abYoungChildBase1,  abYoungChildBase2, retry);
774
 
    }
775
 
    indexDirBase.close();
776
 
  }
777
 
 
778
 
  private void assertConsistentYoungestChild(final CategoryPath abPath,
779
 
      final int abOrd, final int abYoungChildBase1, final int abYoungChildBase2, final int retry)
780
 
      throws Exception {
781
 
    SlowRAMDirectory indexDir =  new SlowRAMDirectory(-1,null); // no slowness for intialization
782
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
783
 
    tw.addCategory(new CategoryPath("a", "0"));
784
 
    tw.addCategory(abPath);
785
 
    tw.commit();
786
 
    
787
 
    final TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
788
 
    for (int i=0; i < 1<<10; i++) { //1024 facets
789
 
      final CategoryPath cp = new CategoryPath("a", "b", Integer.toString(i));
790
 
      tw.addCategory(cp);
791
 
      assertEquals("Ordinal of "+cp+" must be invalid until Taxonomy Reader was refreshed", TaxonomyReader.INVALID_ORDINAL, tr.getOrdinal(cp));
792
 
    }
793
 
    tw.commit();
794
 
    
795
 
    final boolean[] stop = new boolean[] { false };
796
 
    final Throwable[] error = new Throwable[] { null };
797
 
    final int retrieval[] = { 0 }; 
798
 
    
799
 
    Thread thread = new Thread("Child Arrays Verifier") {
800
 
      @Override
801
 
      public void run() {
802
 
        setPriority(1+getPriority());
803
 
        try {
804
 
          while (!stop[0]) {
805
 
            int lastOrd = tr.getParentArray().length-1;
806
 
            assertNotNull("path of last-ord "+lastOrd+" is not found!",tr.getPath(lastOrd));
807
 
            assertChildrenArrays(tr.getChildrenArrays(),retry,retrieval[0]++);
808
 
          }
809
 
        } catch (Throwable e) {
810
 
          error[0] = e;
811
 
          stop[0] = true;
812
 
        }
813
 
      }
814
 
 
815
 
      private void assertChildrenArrays(ChildrenArrays ca, int retry, int retrieval) {
816
 
        final int abYoungChild = ca.getYoungestChildArray()[abOrd];
817
 
        assertTrue(
818
 
            "Retry "+retry+": retrieval: "+retrieval+": wrong youngest child for category "+abPath+" (ord="+abOrd+
819
 
            ") - must be either "+abYoungChildBase1+" or "+abYoungChildBase2+" but was: "+abYoungChild,
820
 
            abYoungChildBase1==abYoungChild ||
821
 
            abYoungChildBase2==ca.getYoungestChildArray()[abOrd]);
822
 
      }
823
 
    };
824
 
    thread.start();
825
 
    
826
 
    indexDir.setSleepMillis(1); // some delay for refresh
827
 
    tr.refresh();
828
 
    
829
 
    stop[0] = true;
830
 
    thread.join();
831
 
    assertNull("Unexpcted exception at retry "+retry+" retrieval "+retrieval[0]+": \n"+stackTraceStr(error[0]), error[0]);
832
 
    
833
 
    tw.close();
834
 
    tr.close();
835
 
  }
836
 
 
837
 
  /** Grab the stack trace into a string since the exception was thrown in a thread and we want the assert 
838
 
   * outside the thread to show the stack trace in case of failure.   */
839
 
  private String stackTraceStr(final Throwable error) {
840
 
    if (error == null) {
841
 
      return "";
842
 
    }
843
 
    StringWriter sw = new StringWriter();
844
 
    PrintWriter pw = new PrintWriter(sw);
845
 
    error.printStackTrace(pw);
846
 
    pw.close();
847
 
    return sw.toString();
848
 
  }
849
 
  
850
 
  /**  Test that if separate reader and writer objects are opened, new
851
 
    categories written into the writer are available to a reader only
852
 
    after a commit().
853
 
    Note that this test obviously doesn't cover all the different
854
 
    concurrency scenarios, all different methods, and so on. We may
855
 
    want to write more tests of this sort.
856
 
 
857
 
    This test simulates what would happen when there are two separate
858
 
    processes, one doing indexing, and the other searching, and each opens
859
 
    its own object (with obviously no connection between the objects) using
860
 
    the same disk files. Note, though, that this test does not test what
861
 
    happens when the two processes do their actual work at exactly the same
862
 
    time.
863
 
    It also doesn't test multi-threading.
864
 
   */
865
 
  @Test
866
 
  public void testSeparateReaderAndWriter() throws Exception {
867
 
    Directory indexDir = newDirectory();
868
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
869
 
    tw.commit();
870
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
871
 
 
872
 
    int author = 1;
873
 
 
874
 
    // getParent() and getSize() test:
875
 
    try {
876
 
      tr.getParent(author);
877
 
      fail("Initially, getParent for "+author+" should throw exception");
878
 
    } catch (ArrayIndexOutOfBoundsException e) {
879
 
      // ok
880
 
    }
881
 
    assertEquals(1, tr.getSize()); // the empty taxonomy has size 1 (the root)
882
 
    tw.addCategory(new CategoryPath("Author"));
883
 
    try {
884
 
      tr.getParent(author);
885
 
      fail("Before commit() and refresh(), getParent for "+author+" should still throw exception");
886
 
    } catch (ArrayIndexOutOfBoundsException e) {
887
 
      // ok
888
 
    }
889
 
    assertEquals(1, tr.getSize()); // still root only...
890
 
    tr.refresh(); // this is not enough, because tw.commit() hasn't been done yet
891
 
    try {
892
 
      tr.getParent(author);
893
 
      fail("Before commit() and refresh(), getParent for "+author+" should still throw exception");
894
 
    } catch (ArrayIndexOutOfBoundsException e) {
895
 
      // ok
896
 
    }
897
 
    assertEquals(1, tr.getSize()); // still root only...
898
 
    tw.commit();
899
 
    try {
900
 
      tr.getParent(author);
901
 
      fail("Before refresh(), getParent for "+author+" should still throw exception");
902
 
    } catch (ArrayIndexOutOfBoundsException e) {
903
 
      // ok
904
 
    }
905
 
    assertEquals(1, tr.getSize()); // still root only...
906
 
    tr.refresh();
907
 
    try {
908
 
      assertEquals(TaxonomyReader.ROOT_ORDINAL, tr.getParent(author));
909
 
      // ok
910
 
    } catch (ArrayIndexOutOfBoundsException e) {
911
 
      fail("After category addition, commit() and refresh(), getParent for "+author+" should NOT throw exception");
912
 
    }
913
 
    assertEquals(2, tr.getSize()); // finally, see there are two categories
914
 
 
915
 
    // now, add another category, and verify that after commit and refresh
916
 
    // the parent of this category is correct (this requires the reader
917
 
    // to correctly update its prefetched parent vector), and that the
918
 
    // old information also wasn't ruined:
919
 
    tw.addCategory(new CategoryPath("Author", "Richard Dawkins"));
920
 
    int dawkins = 2;
921
 
    tw.commit();
922
 
    tr.refresh();
923
 
    assertEquals(author, tr.getParent(dawkins));
924
 
    assertEquals(TaxonomyReader.ROOT_ORDINAL, tr.getParent(author));
925
 
    assertEquals(TaxonomyReader.INVALID_ORDINAL, tr.getParent(TaxonomyReader.ROOT_ORDINAL));
926
 
    assertEquals(3, tr.getSize()); 
927
 
    tw.close();
928
 
    tr.close();
929
 
    indexDir.close();
930
 
  }
931
 
  
932
 
  @Test
933
 
  public void testSeparateReaderAndWriter2() throws Exception {
934
 
    Directory indexDir = newDirectory();
935
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
936
 
    tw.commit();
937
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
938
 
 
939
 
    // Test getOrdinal():
940
 
    CategoryPath author = new CategoryPath("Author");
941
 
 
942
 
    assertEquals(1, tr.getSize()); // the empty taxonomy has size 1 (the root)
943
 
    assertEquals(TaxonomyReader.INVALID_ORDINAL, tr.getOrdinal(author));
944
 
    tw.addCategory(author);
945
 
    // before commit and refresh, no change:
946
 
    assertEquals(TaxonomyReader.INVALID_ORDINAL, tr.getOrdinal(author));
947
 
    assertEquals(1, tr.getSize()); // still root only...
948
 
    tr.refresh(); // this is not enough, because tw.commit() hasn't been done yet
949
 
    assertEquals(TaxonomyReader.INVALID_ORDINAL, tr.getOrdinal(author));
950
 
    assertEquals(1, tr.getSize()); // still root only...
951
 
    tw.commit();
952
 
    // still not enough before refresh:
953
 
    assertEquals(TaxonomyReader.INVALID_ORDINAL, tr.getOrdinal(author));
954
 
    assertEquals(1, tr.getSize()); // still root only...
955
 
    tr.refresh(); // finally
956
 
    assertEquals(1, tr.getOrdinal(author));
957
 
    assertEquals(2, tr.getSize()); // still root only...
958
 
    tw.close();
959
 
    tr.close();
960
 
    indexDir.close();
961
 
  }
962
 
  
963
 
  /**
964
 
   * Test what happens if we try to write to a locked taxonomy writer,
965
 
   * and see that we can unlock it and continue.
966
 
   */
967
 
  @Test
968
 
  public void testWriterLock() throws Exception {
969
 
    // native fslock impl gets angry if we use it, so use RAMDirectory explicitly.
970
 
    Directory indexDir = new RAMDirectory();
971
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
972
 
    tw.addCategory(new CategoryPath("hi", "there"));
973
 
    tw.commit();
974
 
    // we deliberately not close the write now, and keep it open and
975
 
    // locked.
976
 
    // Verify that the writer worked:
977
 
    TaxonomyReader tr = new DirectoryTaxonomyReader(indexDir);
978
 
    assertEquals(2, tr.getOrdinal(new CategoryPath("hi", "there")));
979
 
    // Try to open a second writer, with the first one locking the directory.
980
 
    // We expect to get a LockObtainFailedException.
981
 
    try {
982
 
      new DirectoryTaxonomyWriter(indexDir);
983
 
      fail("should have failed to write in locked directory");
984
 
    } catch (LockObtainFailedException e) {
985
 
      // this is what we expect to happen.
986
 
    }
987
 
    // Remove the lock, and now the open should succeed, and we can
988
 
    // write to the new writer.
989
 
    DirectoryTaxonomyWriter.unlock(indexDir);
990
 
    TaxonomyWriter tw2 = new DirectoryTaxonomyWriter(indexDir);
991
 
    tw2.addCategory(new CategoryPath("hey"));
992
 
    tw2.close();
993
 
    // See that the writer indeed wrote:
994
 
    tr.refresh();
995
 
    assertEquals(3, tr.getOrdinal(new CategoryPath("hey")));
996
 
    tr.close();
997
 
    tw.close();
998
 
    indexDir.close();
999
 
  }
1000
 
  
1001
 
  /**
1002
 
   * fillTaxonomyCheckPaths adds the categories in the categories[] array,
1003
 
   * and asserts that the additions return exactly paths specified in
1004
 
   * expectedPaths[]. This is the same add fillTaxonomy() but also checks
1005
 
   * the correctness of getParent(), not just addCategory().
1006
 
   * Note that this assumes that fillTaxonomyCheckPaths() is called on an empty
1007
 
   * taxonomy index. Calling it after something else was already added to the
1008
 
   * taxonomy index will surely have this method fail.
1009
 
   */
1010
 
  public static void fillTaxonomyCheckPaths(TaxonomyWriter tw) throws IOException {
1011
 
    for (int i = 0; i < categories.length; i++) {
1012
 
      int ordinal = tw.addCategory(new CategoryPath(categories[i]));
1013
 
      int expectedOrdinal = expectedPaths[i][expectedPaths[i].length-1];
1014
 
      if (ordinal!=expectedOrdinal) {
1015
 
        fail("For category "+showcat(categories[i])+" expected ordinal "+
1016
 
            expectedOrdinal+", but got "+ordinal);
1017
 
      }
1018
 
      for (int j=expectedPaths[i].length-2; j>=0; j--) {
1019
 
        ordinal = tw.getParent(ordinal);
1020
 
        expectedOrdinal = expectedPaths[i][j];
1021
 
        if (ordinal!=expectedOrdinal) {
1022
 
          fail("For category "+showcat(categories[i])+" expected ancestor level "+
1023
 
              (expectedPaths[i].length-1-j)+" was "+expectedOrdinal+
1024
 
              ", but got "+ordinal);
1025
 
        }
1026
 
      }    
1027
 
    }
1028
 
  }
1029
 
  
1030
 
  // After fillTaxonomy returned successfully, checkPaths() checks that
1031
 
  // the getParent() calls return as expected, from the table
1032
 
  public static void checkPaths(TaxonomyWriter tw) throws IOException {
1033
 
    for (int i = 0; i < categories.length; i++) {
1034
 
      int ordinal = expectedPaths[i][expectedPaths[i].length-1];
1035
 
      for (int j=expectedPaths[i].length-2; j>=0; j--) {
1036
 
        ordinal = tw.getParent(ordinal);
1037
 
        int expectedOrdinal = expectedPaths[i][j];
1038
 
        if (ordinal!=expectedOrdinal) {
1039
 
          fail("For category "+showcat(categories[i])+" expected ancestor level "+
1040
 
              (expectedPaths[i].length-1-j)+" was "+expectedOrdinal+
1041
 
              ", but got "+ordinal);
1042
 
        }
1043
 
      }
1044
 
      assertEquals(TaxonomyReader.ROOT_ORDINAL, tw.getParent(expectedPaths[i][0]));
1045
 
    }
1046
 
    assertEquals(TaxonomyReader.INVALID_ORDINAL, tw.getParent(TaxonomyReader.ROOT_ORDINAL));
1047
 
  }
1048
 
  
1049
 
  /**
1050
 
   * Basic test for TaxonomyWriter.getParent(). This is similar to testWriter
1051
 
   * above, except we also check the parents of the added categories, not just
1052
 
   * the categories themselves.
1053
 
   */
1054
 
  @Test
1055
 
  public void testWriterCheckPaths() throws Exception {
1056
 
    Directory indexDir = newDirectory();
1057
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
1058
 
    fillTaxonomyCheckPaths(tw);
1059
 
    // Also check TaxonomyWriter.getSize() - see that the taxonomy's size
1060
 
    // is what we expect it to be.
1061
 
    assertEquals(expectedCategories.length, tw.getSize());
1062
 
    tw.close();
1063
 
    indexDir.close();
1064
 
  }
1065
 
  
1066
 
  /**
1067
 
   * testWriterCheckPaths2 is the path-checking variant of testWriterTwice
1068
 
   * and testWriterTwice2. After adding all the categories, we add them again,
1069
 
   * and see that we get the same old ids and paths. We repeat the path checking
1070
 
   * yet again after closing and opening the index for writing again - to see
1071
 
   * that the reading of existing data from disk works as well.
1072
 
   */
1073
 
  @Test
1074
 
  public void testWriterCheckPaths2() throws Exception {
1075
 
    Directory indexDir = newDirectory();
1076
 
    TaxonomyWriter tw = new DirectoryTaxonomyWriter(indexDir);
1077
 
    fillTaxonomy(tw);
1078
 
    checkPaths(tw);
1079
 
    fillTaxonomy(tw);
1080
 
    checkPaths(tw);
1081
 
    tw.close();
1082
 
 
1083
 
    tw = new DirectoryTaxonomyWriter(indexDir);
1084
 
    checkPaths(tw);
1085
 
    fillTaxonomy(tw);
1086
 
    checkPaths(tw);
1087
 
    tw.close();
1088
 
    indexDir.close();
1089
 
  }
1090
 
 
1091
 
//  TODO (Facet): test multiple readers, one writer. Have the multiple readers
1092
 
//  using the same object (simulating threads) or different objects
1093
 
//  (simulating processes).
1094
 
}