Index: lucene/CHANGES.txt =================================================================== --- lucene/CHANGES.txt (revision 1464407) +++ lucene/CHANGES.txt (working copy) @@ -160,6 +160,9 @@ * LUCENE-4645: Added support for the "Contains" spatial predicate for RecursivePrefixTreeStrategy. (David Smiley) +* LUCENE-4897: Added TaxonomyReader.getChildren for traversing a category's + children. (Shai Erera) + Optimizations * LUCENE-4839: SorterTemplate.merge can now be overridden in order to replace Index: lucene/facet/src/java/org/apache/lucene/facet/taxonomy/TaxonomyReader.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/taxonomy/TaxonomyReader.java (revision 1464407) +++ lucene/facet/src/java/org/apache/lucene/facet/taxonomy/TaxonomyReader.java (working copy) @@ -5,6 +5,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.lucene.facet.collections.IntIterator; import org.apache.lucene.store.AlreadyClosedException; /* @@ -167,6 +168,33 @@ */ public abstract ParallelTaxonomyArrays getParallelTaxonomyArrays() throws IOException; + /** Returns the children of the given ordinal. */ + public IntIterator getChildren(final int ordinal) throws IOException { + return new IntIterator() { + private final ParallelTaxonomyArrays arrays = getParallelTaxonomyArrays(); + private final int[] siblings = arrays.siblings(); + // first child of ordinal + private int child = ordinal != INVALID_ORDINAL ? arrays.children()[ordinal] : INVALID_ORDINAL; + + @Override + public void remove() { + throw new UnsupportedOperationException("does not support removing items"); + } + + @Override + public int next() { + int res = child; + child = child == INVALID_ORDINAL ? INVALID_ORDINAL : siblings[child]; + return res; + } + + @Override + public boolean hasNext() { + return child != INVALID_ORDINAL; + } + }; + } + /** * Retrieve user committed data. * Index: lucene/facet/src/java/org/apache/lucene/facet/util/PrintTaxonomyStats.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/util/PrintTaxonomyStats.java (revision 1464407) +++ lucene/facet/src/java/org/apache/lucene/facet/util/PrintTaxonomyStats.java (working copy) @@ -21,8 +21,8 @@ import java.io.IOException; import java.io.PrintStream; +import org.apache.lucene.facet.collections.IntIterator; import org.apache.lucene.facet.taxonomy.CategoryPath; -import org.apache.lucene.facet.taxonomy.ParallelTaxonomyArrays; import org.apache.lucene.facet.taxonomy.TaxonomyReader; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader; import org.apache.lucene.store.Directory; @@ -55,45 +55,41 @@ } public static void printStats(TaxonomyReader r, PrintStream out, boolean printTree) throws IOException { - ParallelTaxonomyArrays arrays = r.getParallelTaxonomyArrays(); - //int[] parents = arrays.parents(); - int[] children = arrays.children(); - int[] siblings = arrays.siblings(); out.println(r.getSize() + " total categories."); - int childOrd = children[TaxonomyReader.ROOT_ORDINAL]; - while(childOrd != -1) { - CategoryPath cp = r.getPath(childOrd); - int childOrd2 = children[childOrd]; + IntIterator it = r.getChildren(TaxonomyReader.ROOT_ORDINAL); + while (it.hasNext()) { + int childOrd = it.next(); + IntIterator chilrenIt = r.getChildren(childOrd); int numImmediateChildren = 0; - while(childOrd2 != -1) { + while (chilrenIt.hasNext()) { + chilrenIt.next(); numImmediateChildren++; - childOrd2 = siblings[childOrd2]; } - out.println("/" + cp + ": " + numImmediateChildren + " immediate children; " + (1+countAllChildren(r, childOrd, children, siblings)) + " total categories"); + CategoryPath cp = r.getPath(childOrd); + out.println("/" + cp + ": " + numImmediateChildren + " immediate children; " + (1+countAllChildren(r, childOrd)) + " total categories"); + printAllChildren(out, r, childOrd, " ", 1); if (printTree) { - printAllChildren(out, r, childOrd, children, siblings, " ", 1); } - childOrd = siblings[childOrd]; } } - private static int countAllChildren(TaxonomyReader r, int ord, int[] children, int[] siblings) throws IOException { - int childOrd = children[ord]; + private static int countAllChildren(TaxonomyReader r, int ord) throws IOException { + IntIterator it = r.getChildren(ord); int count = 0; - while(childOrd != -1) { - count += 1+countAllChildren(r, childOrd, children, siblings); - childOrd = siblings[childOrd]; + while (it.hasNext()) { + int childOrd = it.next(); + count += 1 + countAllChildren(r, childOrd); } return count; } - private static void printAllChildren(PrintStream out, TaxonomyReader r, int ord, int[] children, int[] siblings, String indent, int depth) throws IOException { - int childOrd = children[ord]; - while(childOrd != -1) { - out.println(indent + "/" + r.getPath(childOrd).components[depth]); - printAllChildren(out, r, childOrd, children, siblings, indent + " ", depth+1); - childOrd = siblings[childOrd]; + private static void printAllChildren(PrintStream out, TaxonomyReader r, int ord, String indent, int depth) throws IOException { + IntIterator it = r.getChildren(ord); + while (it.hasNext()) { + int child = it.next(); + out.println(indent + "/" + r.getPath(child).components[depth]); + printAllChildren(out, r, child, indent + " ", depth+1); } } } Index: lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyReader.java =================================================================== --- lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyReader.java (revision 1464407) +++ lucene/facet/src/test/org/apache/lucene/facet/taxonomy/directory/TestDirectoryTaxonomyReader.java (working copy) @@ -5,6 +5,7 @@ import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.facet.FacetTestCase; +import org.apache.lucene.facet.collections.IntIterator; import org.apache.lucene.facet.taxonomy.CategoryPath; import org.apache.lucene.facet.taxonomy.TaxonomyReader; import org.apache.lucene.facet.taxonomy.TaxonomyWriter; @@ -461,5 +462,54 @@ src.close(); } + + @Test + public void testGetChildren() throws Exception { + Directory dir = newDirectory(); + DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(dir); + int numCategories = atLeast(10); + int numA = 0, numB = 0; + Random random = random(); + for (int i = 0; i < numCategories; i++) { + if (random.nextBoolean()) { + taxoWriter.addCategory(new CategoryPath("a", Integer.toString(i))); + ++numA; + } else { + taxoWriter.addCategory(new CategoryPath("b", Integer.toString(i))); + ++numB; + } + } + // add category with no children + taxoWriter.addCategory(new CategoryPath("c")); + taxoWriter.close(); + + DirectoryTaxonomyReader taxoReader = new DirectoryTaxonomyReader(dir); + IntIterator it = taxoReader.getChildren(taxoReader.getOrdinal(new CategoryPath("invalid"))); + assertFalse(it.hasNext()); + assertEquals(TaxonomyReader.INVALID_ORDINAL, it.next()); + + it = taxoReader.getChildren(taxoReader.getOrdinal(new CategoryPath("c"))); + assertFalse(it.hasNext()); + assertEquals(TaxonomyReader.INVALID_ORDINAL, it.next()); + + for (int i = 0; i < 2; i++) { + CategoryPath cp = i == 0 ? new CategoryPath("a") : new CategoryPath("b"); + int ordinal = taxoReader.getOrdinal(cp); + it = taxoReader.getChildren(ordinal); + int numChildren = 0; + while (it.hasNext()) { + int child = it.next(); + CategoryPath path = taxoReader.getPath(child); + assertEquals(2, path.length); + assertEquals(path.components[0], i == 0 ? "a" : "b"); + ++numChildren; + } + int expected = i == 0 ? numA : numB; + assertEquals("invalid num children", expected, numChildren); + } + taxoReader.close(); + + dir.close(); + } }