001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.lucene.demo.facet;
018
019import static org.apache.lucene.facet.FacetsConfig.DEFAULT_INDEX_FIELD_NAME;
020import static org.apache.lucene.sandbox.facet.utils.ComparableUtils.byAggregatedValue;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.List;
025import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
026import org.apache.lucene.document.Document;
027import org.apache.lucene.document.DoubleDocValuesField;
028import org.apache.lucene.document.NumericDocValuesField;
029import org.apache.lucene.facet.DrillDownQuery;
030import org.apache.lucene.facet.DrillSideways;
031import org.apache.lucene.facet.FacetField;
032import org.apache.lucene.facet.FacetResult;
033import org.apache.lucene.facet.FacetsConfig;
034import org.apache.lucene.facet.LabelAndValue;
035import org.apache.lucene.facet.MultiLongValuesSource;
036import org.apache.lucene.facet.range.LongRange;
037import org.apache.lucene.facet.taxonomy.FacetLabel;
038import org.apache.lucene.facet.taxonomy.TaxonomyReader;
039import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
040import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter;
041import org.apache.lucene.index.DirectoryReader;
042import org.apache.lucene.index.IndexWriter;
043import org.apache.lucene.index.IndexWriterConfig;
044import org.apache.lucene.index.IndexWriterConfig.OpenMode;
045import org.apache.lucene.sandbox.facet.FacetFieldCollectorManager;
046import org.apache.lucene.sandbox.facet.cutters.TaxonomyFacetsCutter;
047import org.apache.lucene.sandbox.facet.cutters.ranges.LongRangeFacetCutter;
048import org.apache.lucene.sandbox.facet.iterators.ComparableSupplier;
049import org.apache.lucene.sandbox.facet.iterators.OrdinalIterator;
050import org.apache.lucene.sandbox.facet.iterators.TaxonomyChildrenOrdinalIterator;
051import org.apache.lucene.sandbox.facet.iterators.TopnOrdinalIterator;
052import org.apache.lucene.sandbox.facet.labels.RangeOrdToLabel;
053import org.apache.lucene.sandbox.facet.labels.TaxonomyOrdLabelBiMap;
054import org.apache.lucene.sandbox.facet.recorders.CountFacetRecorder;
055import org.apache.lucene.sandbox.facet.recorders.LongAggregationsFacetRecorder;
056import org.apache.lucene.sandbox.facet.recorders.MultiFacetsRecorder;
057import org.apache.lucene.sandbox.facet.recorders.Reducer;
058import org.apache.lucene.sandbox.facet.utils.ComparableUtils;
059import org.apache.lucene.sandbox.facet.utils.DrillSidewaysFacetOrchestrator;
060import org.apache.lucene.sandbox.facet.utils.FacetBuilder;
061import org.apache.lucene.sandbox.facet.utils.FacetOrchestrator;
062import org.apache.lucene.sandbox.facet.utils.RangeFacetBuilderFactory;
063import org.apache.lucene.sandbox.facet.utils.TaxonomyFacetBuilder;
064import org.apache.lucene.search.DoubleValuesSource;
065import org.apache.lucene.search.IndexSearcher;
066import org.apache.lucene.search.LongValuesSource;
067import org.apache.lucene.search.MatchAllDocsQuery;
068import org.apache.lucene.search.MultiCollectorManager;
069import org.apache.lucene.search.TopDocs;
070import org.apache.lucene.search.TopScoreDocCollectorManager;
071import org.apache.lucene.store.ByteBuffersDirectory;
072import org.apache.lucene.store.Directory;
073import org.apache.lucene.util.IOUtils;
074
075/** Demo for sandbox faceting. */
076public class SandboxFacetsExample {
077
078  private final Directory indexDir = new ByteBuffersDirectory();
079  private final Directory taxoDir = new ByteBuffersDirectory();
080  private final FacetsConfig config = new FacetsConfig();
081
082  private SandboxFacetsExample() {
083    config.setHierarchical("Publish Date", true);
084    config.setHierarchical("Author", false);
085  }
086
087  /** Build the example index. */
088  void index() throws IOException {
089    IndexWriter indexWriter =
090        new IndexWriter(
091            indexDir, new IndexWriterConfig(new WhitespaceAnalyzer()).setOpenMode(OpenMode.CREATE));
092
093    // Writes facet ords to a separate directory from the main index
094    DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
095
096    Document doc = new Document();
097    doc.add(new FacetField("Author", "Bob"));
098    doc.add(new FacetField("Publish Date", "2010", "10", "15"));
099    doc.add(new NumericDocValuesField("Price", 10));
100    doc.add(new NumericDocValuesField("Units", 9));
101    doc.add(new DoubleDocValuesField("Popularity", 3.5d));
102    indexWriter.addDocument(config.build(taxoWriter, doc));
103
104    doc = new Document();
105    doc.add(new FacetField("Author", "Lisa"));
106    doc.add(new FacetField("Publish Date", "2010", "10", "20"));
107    doc.add(new NumericDocValuesField("Price", 4));
108    doc.add(new NumericDocValuesField("Units", 2));
109    doc.add(new DoubleDocValuesField("Popularity", 4.1D));
110    indexWriter.addDocument(config.build(taxoWriter, doc));
111
112    doc = new Document();
113    doc.add(new FacetField("Author", "Lisa"));
114    doc.add(new FacetField("Publish Date", "2012", "1", "1"));
115    doc.add(new NumericDocValuesField("Price", 3));
116    doc.add(new NumericDocValuesField("Units", 5));
117    doc.add(new DoubleDocValuesField("Popularity", 3.9D));
118    indexWriter.addDocument(config.build(taxoWriter, doc));
119
120    doc = new Document();
121    doc.add(new FacetField("Author", "Susan"));
122    doc.add(new FacetField("Publish Date", "2012", "1", "7"));
123    doc.add(new NumericDocValuesField("Price", 8));
124    doc.add(new NumericDocValuesField("Units", 7));
125    doc.add(new DoubleDocValuesField("Popularity", 4D));
126    indexWriter.addDocument(config.build(taxoWriter, doc));
127
128    doc = new Document();
129    doc.add(new FacetField("Author", "Frank"));
130    doc.add(new FacetField("Publish Date", "1999", "5", "5"));
131    doc.add(new NumericDocValuesField("Price", 9));
132    doc.add(new NumericDocValuesField("Units", 6));
133    doc.add(new DoubleDocValuesField("Popularity", 4.9D));
134    indexWriter.addDocument(config.build(taxoWriter, doc));
135
136    IOUtils.close(indexWriter, taxoWriter);
137  }
138
139  /**
140   * Example for {@link FacetBuilder} usage - simple API that provides results in a format very
141   * similar to classic facets module. It doesn't give all flexibility available with {@link
142   * org.apache.lucene.sandbox.facet.cutters.FacetCutter} and {@link
143   * org.apache.lucene.sandbox.facet.recorders.FacetRecorder} though, see below for lower level API
144   * usage examples.
145   */
146  private List<FacetResult> simpleFacetsWithSearch() throws IOException {
147    //// init readers and searcher
148    DirectoryReader indexReader = DirectoryReader.open(indexDir);
149    IndexSearcher searcher = new IndexSearcher(indexReader);
150    TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir);
151
152    //// build facets requests
153    FacetBuilder authorFacetBuilder =
154        new TaxonomyFacetBuilder(config, taxoReader, "Author").withTopN(10);
155    FacetBuilder priceFacetBuilder =
156        RangeFacetBuilderFactory.forLongRanges(
157            "Price",
158            new LongRange("0-10", 0, true, 10, true),
159            new LongRange("10-20", 10, true, 20, true));
160
161    //// Main hits collector
162    TopScoreDocCollectorManager hitsCollectorManager =
163        new TopScoreDocCollectorManager(2, Integer.MAX_VALUE);
164
165    //// Search and collect
166    TopDocs topDocs =
167        new FacetOrchestrator()
168            .addBuilder(authorFacetBuilder)
169            .addBuilder(priceFacetBuilder)
170            .collect(new MatchAllDocsQuery(), searcher, hitsCollectorManager);
171    System.out.println(
172        "Search results: totalHits: "
173            + topDocs.totalHits
174            + ", collected hits: "
175            + topDocs.scoreDocs.length);
176
177    //// Results
178    FacetResult authorResults = authorFacetBuilder.getResult();
179    FacetResult rangeResults = priceFacetBuilder.getResult();
180
181    IOUtils.close(indexReader, taxoReader);
182
183    return List.of(authorResults, rangeResults);
184  }
185
186  /** Example for {@link FacetBuilder} usage with {@link DrillSideways}. */
187  private List<FacetResult> simpleFacetsWithDrillSideways() throws IOException {
188    //// init readers and searcher
189    DirectoryReader indexReader = DirectoryReader.open(indexDir);
190    IndexSearcher searcher = new IndexSearcher(indexReader);
191    TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir);
192    DrillSideways ds = new DrillSideways(searcher, config, taxoReader);
193
194    //// build facets requests
195    FacetBuilder authorFacetBuilder =
196        new TaxonomyFacetBuilder(config, taxoReader, "Author").withTopN(10);
197    FacetBuilder priceFacetBuilder =
198        RangeFacetBuilderFactory.forLongRanges(
199            "Price",
200            new LongRange("0-10", 0, true, 10, true),
201            new LongRange("10-20", 10, true, 20, true));
202
203    //// Build query and collect
204    DrillDownQuery query = new DrillDownQuery(config);
205    query.add("Author", "Lisa");
206
207    new DrillSidewaysFacetOrchestrator()
208        .addDrillDownBuilder(priceFacetBuilder)
209        .addDrillSidewaysBuilder("Author", authorFacetBuilder)
210        .collect(query, ds);
211
212    //// Results
213    FacetResult authorResults = authorFacetBuilder.getResult();
214    FacetResult rangeResults = priceFacetBuilder.getResult();
215
216    IOUtils.close(indexReader, taxoReader);
217
218    return List.of(authorResults, rangeResults);
219  }
220
221  /** User runs a query and counts facets only without collecting the matching documents. */
222  List<FacetResult> facetsOnly() throws IOException {
223    //// (1) init readers and searcher
224    DirectoryReader indexReader = DirectoryReader.open(indexDir);
225    IndexSearcher searcher = new IndexSearcher(indexReader);
226    TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir);
227
228    //// (2) init collector
229    TaxonomyFacetsCutter defaultTaxoCutter =
230        new TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, taxoReader);
231    CountFacetRecorder defaultRecorder = new CountFacetRecorder();
232
233    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
234        new FacetFieldCollectorManager<>(defaultTaxoCutter, defaultRecorder);
235
236    // (2.1) if we need to collect data using multiple different collectors, e.g. taxonomy and
237    // ranges, or even two taxonomy facets that use different Category List Field, we can
238    // use MultiCollectorManager, e.g.:
239    //
240    // TODO: add a demo for it.
241    // TaxonomyFacetsCutter publishDateCutter = new
242    // TaxonomyFacetsCutter(config.getDimConfig("Publish Date"), taxoReader);
243    // CountFacetRecorder publishDateRecorder = new CountFacetRecorder(false);
244    // FacetFieldCollectorManager<CountFacetRecorder> publishDateCollectorManager = new
245    // FacetFieldCollectorManager<>(publishDateCutter, publishDateRecorder);
246    // MultiCollectorManager drillDownCollectorManager = new
247    // MultiCollectorManager(authorCollectorManager, publishDateCollectorManager);
248    // Object[] results = searcher.search(new MatchAllDocsQuery(), drillDownCollectorManager);
249
250    //// (3) search
251    // Search returns the same Recorder we created - so we can ignore results
252    searcher.search(new MatchAllDocsQuery(), collectorManager);
253
254    //// (4) Get top 10 results by count for Author and Publish Date
255    // This object is used to get topN results by count
256    ComparableSupplier<ComparableUtils.ByCountComparable> countComparable =
257        ComparableUtils.byCount(defaultRecorder);
258    // We don't actually need to use FacetResult, it is up to client what to do with the results.
259    // Here we just want to demo that we can still do FacetResult as well
260    List<FacetResult> results = new ArrayList<>(2);
261    // This object provides labels for ordinals.
262    TaxonomyOrdLabelBiMap ordLabels = new TaxonomyOrdLabelBiMap(taxoReader);
263    for (String dimension : List.of("Author", "Publish Date")) {
264      //// (4.1) Chain two ordinal iterators to get top N children
265      int dimOrdinal = ordLabels.getOrd(new FacetLabel(dimension));
266      OrdinalIterator childrenIterator =
267          new TaxonomyChildrenOrdinalIterator(
268              defaultRecorder.recordedOrds(),
269              taxoReader.getParallelTaxonomyArrays().parents(),
270              dimOrdinal);
271      OrdinalIterator topByCountOrds =
272          new TopnOrdinalIterator<>(childrenIterator, countComparable, 10);
273      // Get array of final ordinals - we need to use all of them to get labels first, and then to
274      // get counts,
275      // but OrdinalIterator only allows reading ordinals once.
276      int[] resultOrdinals = topByCountOrds.toArray();
277
278      //// (4.2) Use faceting results
279      FacetLabel[] labels = ordLabels.getLabels(resultOrdinals);
280      List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length);
281      for (int i = 0; i < resultOrdinals.length; i++) {
282        labelsAndValues.add(
283            new LabelAndValue(
284                labels[i].lastComponent(), defaultRecorder.getCount(resultOrdinals[i])));
285      }
286      int dimensionValue = defaultRecorder.getCount(dimOrdinal);
287      results.add(
288          new FacetResult(
289              dimension,
290              new String[0],
291              dimensionValue,
292              labelsAndValues.toArray(new LabelAndValue[0]),
293              labelsAndValues.size()));
294    }
295
296    IOUtils.close(indexReader, taxoReader);
297    return results;
298  }
299
300  /**
301   * User runs a query and counts facets for exclusive ranges without collecting the matching
302   * documents
303   */
304  List<FacetResult> exclusiveRangesCountFacetsOnly() throws IOException {
305    DirectoryReader indexReader = DirectoryReader.open(indexDir);
306    IndexSearcher searcher = new IndexSearcher(indexReader);
307
308    MultiLongValuesSource valuesSource = MultiLongValuesSource.fromLongField("Price");
309
310    // Exclusive ranges example
311    LongRange[] inputRanges = new LongRange[2];
312    inputRanges[0] = new LongRange("0-5", 0, true, 5, true);
313    inputRanges[1] = new LongRange("5-10", 5, false, 10, true);
314
315    LongRangeFacetCutter longRangeFacetCutter =
316        LongRangeFacetCutter.create(valuesSource, inputRanges);
317    CountFacetRecorder countRecorder = new CountFacetRecorder();
318
319    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
320        new FacetFieldCollectorManager<>(longRangeFacetCutter, countRecorder);
321    searcher.search(new MatchAllDocsQuery(), collectorManager);
322    RangeOrdToLabel ordToLabels = new RangeOrdToLabel(inputRanges);
323
324    ComparableSupplier<ComparableUtils.ByCountComparable> countComparable =
325        ComparableUtils.byCount(countRecorder);
326    OrdinalIterator topByCountOrds =
327        new TopnOrdinalIterator<>(countRecorder.recordedOrds(), countComparable, 10);
328
329    List<FacetResult> results = new ArrayList<>(2);
330
331    int[] resultOrdinals = topByCountOrds.toArray();
332    FacetLabel[] labels = ordToLabels.getLabels(resultOrdinals);
333    List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length);
334    for (int i = 0; i < resultOrdinals.length; i++) {
335      labelsAndValues.add(
336          new LabelAndValue(labels[i].lastComponent(), countRecorder.getCount(resultOrdinals[i])));
337    }
338
339    results.add(
340        new FacetResult(
341            "Price", new String[0], 0, labelsAndValues.toArray(new LabelAndValue[0]), 0));
342
343    System.out.println("Computed counts");
344    IOUtils.close(indexReader);
345    return results;
346  }
347
348  List<FacetResult> overlappingRangesCountFacetsOnly() throws IOException {
349    DirectoryReader indexReader = DirectoryReader.open(indexDir);
350    IndexSearcher searcher = new IndexSearcher(indexReader);
351
352    MultiLongValuesSource valuesSource = MultiLongValuesSource.fromLongField("Price");
353
354    // overlapping ranges example
355    LongRange[] inputRanges = new LongRange[2];
356    inputRanges[0] = new LongRange("0-5", 0, true, 5, true);
357    inputRanges[1] = new LongRange("0-10", 0, true, 10, true);
358
359    LongRangeFacetCutter longRangeFacetCutter =
360        LongRangeFacetCutter.create(valuesSource, inputRanges);
361    CountFacetRecorder countRecorder = new CountFacetRecorder();
362
363    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
364        new FacetFieldCollectorManager<>(longRangeFacetCutter, countRecorder);
365    searcher.search(new MatchAllDocsQuery(), collectorManager);
366    RangeOrdToLabel ordToLabels = new RangeOrdToLabel(inputRanges);
367
368    ComparableSupplier<ComparableUtils.ByCountComparable> countComparable =
369        ComparableUtils.byCount(countRecorder);
370    OrdinalIterator topByCountOrds =
371        new TopnOrdinalIterator<>(countRecorder.recordedOrds(), countComparable, 10);
372
373    List<FacetResult> results = new ArrayList<>(2);
374
375    int[] resultOrdinals = topByCountOrds.toArray();
376    FacetLabel[] labels = ordToLabels.getLabels(resultOrdinals);
377    List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length);
378    for (int i = 0; i < resultOrdinals.length; i++) {
379      labelsAndValues.add(
380          new LabelAndValue(labels[i].lastComponent(), countRecorder.getCount(resultOrdinals[i])));
381    }
382
383    results.add(
384        new FacetResult(
385            "Price", new String[0], 0, labelsAndValues.toArray(new LabelAndValue[0]), 0));
386
387    System.out.println("Computed counts");
388    IOUtils.close(indexReader);
389    return results;
390  }
391
392  List<FacetResult> exclusiveRangesAggregationFacets() throws IOException {
393    DirectoryReader indexReader = DirectoryReader.open(indexDir);
394    IndexSearcher searcher = new IndexSearcher(indexReader);
395
396    MultiLongValuesSource valuesSource = MultiLongValuesSource.fromLongField("Price");
397
398    // Exclusive ranges example
399    LongRange[] inputRanges = new LongRange[2];
400    inputRanges[0] = new LongRange("0-5", 0, true, 5, true);
401    inputRanges[1] = new LongRange("5-10", 5, false, 10, true);
402
403    LongRangeFacetCutter longRangeFacetCutter =
404        LongRangeFacetCutter.create(valuesSource, inputRanges);
405
406    // initialise the aggregations to be computed - a values source + reducer
407    LongValuesSource[] longValuesSources = new LongValuesSource[2];
408    Reducer[] reducers = new Reducer[2];
409    // popularity:max
410    longValuesSources[0] = DoubleValuesSource.fromDoubleField("Popularity").toLongValuesSource();
411    reducers[0] = Reducer.MAX;
412    // units:sum
413    longValuesSources[1] = LongValuesSource.fromLongField("Units");
414    reducers[1] = Reducer.SUM;
415
416    LongAggregationsFacetRecorder longAggregationsFacetRecorder =
417        new LongAggregationsFacetRecorder(longValuesSources, reducers);
418
419    CountFacetRecorder countRecorder = new CountFacetRecorder();
420
421    // Compute both counts and aggregations
422    MultiFacetsRecorder multiFacetsRecorder =
423        new MultiFacetsRecorder(countRecorder, longAggregationsFacetRecorder);
424
425    FacetFieldCollectorManager<MultiFacetsRecorder> collectorManager =
426        new FacetFieldCollectorManager<>(longRangeFacetCutter, multiFacetsRecorder);
427    searcher.search(new MatchAllDocsQuery(), collectorManager);
428    RangeOrdToLabel ordToLabels = new RangeOrdToLabel(inputRanges);
429
430    // Get recorded ords - use either count/aggregations recorder
431    OrdinalIterator recordedOrds = longAggregationsFacetRecorder.recordedOrds();
432
433    // We don't actually need to use FacetResult, it is up to client what to do with the results.
434    // Here we just want to demo that we can still do FacetResult as well
435    List<FacetResult> results = new ArrayList<>(2);
436    ComparableSupplier<ComparableUtils.ByAggregatedValueComparable> comparableSupplier;
437    OrdinalIterator topOrds;
438    int[] resultOrdinals;
439    FacetLabel[] labels;
440    List<LabelAndValue> labelsAndValues;
441
442    // Sort results by units:sum and tie-break by count
443    comparableSupplier = byAggregatedValue(countRecorder, longAggregationsFacetRecorder, 1);
444    topOrds = new TopnOrdinalIterator<>(recordedOrds, comparableSupplier, 10);
445
446    resultOrdinals = topOrds.toArray();
447    labels = ordToLabels.getLabels(resultOrdinals);
448    labelsAndValues = new ArrayList<>(labels.length);
449    for (int i = 0; i < resultOrdinals.length; i++) {
450      labelsAndValues.add(
451          new LabelAndValue(
452              labels[i].lastComponent(),
453              longAggregationsFacetRecorder.getRecordedValue(resultOrdinals[i], 1)));
454    }
455    results.add(
456        new FacetResult(
457            "Price", new String[0], 0, labelsAndValues.toArray(new LabelAndValue[0]), 0));
458
459    // note: previous ordinal iterator was exhausted
460    recordedOrds = longAggregationsFacetRecorder.recordedOrds();
461    // Sort results by popularity:max and tie-break by count
462    comparableSupplier = byAggregatedValue(countRecorder, longAggregationsFacetRecorder, 0);
463    topOrds = new TopnOrdinalIterator<>(recordedOrds, comparableSupplier, 10);
464    resultOrdinals = topOrds.toArray();
465    labels = ordToLabels.getLabels(resultOrdinals);
466    labelsAndValues = new ArrayList<>(labels.length);
467    for (int i = 0; i < resultOrdinals.length; i++) {
468      labelsAndValues.add(
469          new LabelAndValue(
470              labels[i].lastComponent(),
471              longAggregationsFacetRecorder.getRecordedValue(resultOrdinals[i], 0)));
472    }
473    results.add(
474        new FacetResult(
475            "Price", new String[0], 0, labelsAndValues.toArray(new LabelAndValue[0]), 0));
476
477    return results;
478  }
479
480  /** User runs a query and counts facets. */
481  private List<FacetResult> facetsWithSearch() throws IOException {
482    //// (1) init readers and searcher
483    DirectoryReader indexReader = DirectoryReader.open(indexDir);
484    IndexSearcher searcher = new IndexSearcher(indexReader);
485    TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir);
486
487    //// (2) init collectors
488    // Facet collectors
489    TaxonomyFacetsCutter defaultTaxoCutter =
490        new TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, taxoReader);
491    CountFacetRecorder defaultRecorder = new CountFacetRecorder();
492    FacetFieldCollectorManager<CountFacetRecorder> taxoFacetsCollectorManager =
493        new FacetFieldCollectorManager<>(defaultTaxoCutter, defaultRecorder);
494    // Hits collector
495    TopScoreDocCollectorManager hitsCollectorManager =
496        new TopScoreDocCollectorManager(2, Integer.MAX_VALUE);
497    // Now wrap them with MultiCollectorManager to collect both hits and facets.
498    MultiCollectorManager collectorManager =
499        new MultiCollectorManager(hitsCollectorManager, taxoFacetsCollectorManager);
500
501    //// (3) search
502    Object[] results = searcher.search(new MatchAllDocsQuery(), collectorManager);
503    TopDocs topDocs = (TopDocs) results[0];
504    System.out.println(
505        "Search results: totalHits: "
506            + topDocs.totalHits
507            + ", collected hits: "
508            + topDocs.scoreDocs.length);
509    // FacetFieldCollectorManager returns the same Recorder it gets - so we can ignore read the
510    // results from original recorder
511    // and ignore this value.
512    // CountFacetRecorder defaultRecorder = (CountFacetRecorder) results[1];
513
514    //// (4) Get top 10 results by count for Author and Publish Date
515    // This object is used to get topN results by count
516    ComparableSupplier<ComparableUtils.ByCountComparable> countComparable =
517        ComparableUtils.byCount(defaultRecorder);
518    // We don't actually need to use FacetResult, it is up to client what to do with the results.
519    // Here we just want to demo that we can still do FacetResult as well
520    List<FacetResult> facetResults = new ArrayList<>(2);
521    // This object provides labels for ordinals.
522    TaxonomyOrdLabelBiMap ordLabels = new TaxonomyOrdLabelBiMap(taxoReader);
523    for (String dimension : List.of("Author", "Publish Date")) {
524      int dimensionOrdinal = ordLabels.getOrd(new FacetLabel(dimension));
525      //// (4.1) Chain two ordinal iterators to get top N children
526      OrdinalIterator childrenIterator =
527          new TaxonomyChildrenOrdinalIterator(
528              defaultRecorder.recordedOrds(),
529              taxoReader.getParallelTaxonomyArrays().parents(),
530              dimensionOrdinal);
531      OrdinalIterator topByCountOrds =
532          new TopnOrdinalIterator<>(childrenIterator, countComparable, 10);
533      // Get array of final ordinals - we need to use all of them to get labels first, and then to
534      // get counts,
535      // but OrdinalIterator only allows reading ordinals once.
536      int[] resultOrdinals = topByCountOrds.toArray();
537
538      //// (4.2) Use faceting results
539      FacetLabel[] labels = ordLabels.getLabels(resultOrdinals);
540      List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length);
541      for (int i = 0; i < resultOrdinals.length; i++) {
542        labelsAndValues.add(
543            new LabelAndValue(
544                labels[i].lastComponent(), defaultRecorder.getCount(resultOrdinals[i])));
545      }
546      int dimensionValue = defaultRecorder.getCount(dimensionOrdinal);
547      facetResults.add(
548          new FacetResult(
549              dimension,
550              new String[0],
551              dimensionValue,
552              labelsAndValues.toArray(new LabelAndValue[0]),
553              labelsAndValues.size()));
554    }
555
556    IOUtils.close(indexReader, taxoReader);
557    return facetResults;
558  }
559
560  /** User drills down on 'Publish Date/2010', and we return facets for 'Author' */
561  FacetResult drillDown() throws IOException {
562    //// (1) init readers and searcher
563    DirectoryReader indexReader = DirectoryReader.open(indexDir);
564    IndexSearcher searcher = new IndexSearcher(indexReader);
565    TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir);
566
567    //// (2) init collector
568    TaxonomyFacetsCutter defaultTaxoCutter =
569        new TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, taxoReader);
570    CountFacetRecorder defaultRecorder = new CountFacetRecorder();
571
572    FacetFieldCollectorManager<CountFacetRecorder> collectorManager =
573        new FacetFieldCollectorManager<>(defaultTaxoCutter, defaultRecorder);
574
575    DrillDownQuery q = new DrillDownQuery(config);
576    q.add("Publish Date", "2010");
577
578    //// (3) search
579    // Right now we return the same Recorder we created - so we can ignore results
580    searcher.search(q, collectorManager);
581
582    //// (4) Get top 10 results by count for Author and Publish Date
583    // This object is used to get topN results by count
584    ComparableSupplier<ComparableUtils.ByCountComparable> countComparable =
585        ComparableUtils.byCount(defaultRecorder);
586
587    // This object provides labels for ordinals.
588    TaxonomyOrdLabelBiMap ordLabels = new TaxonomyOrdLabelBiMap(taxoReader);
589    String dimension = "Author";
590    //// (4.1) Chain two ordinal iterators to get top N children
591    int dimOrdinal = ordLabels.getOrd(new FacetLabel(dimension));
592    OrdinalIterator childrenIterator =
593        new TaxonomyChildrenOrdinalIterator(
594            defaultRecorder.recordedOrds(),
595            taxoReader.getParallelTaxonomyArrays().parents(),
596            dimOrdinal);
597    OrdinalIterator topByCountOrds =
598        new TopnOrdinalIterator<>(childrenIterator, countComparable, 10);
599    // Get array of final ordinals - we need to use all of them to get labels first, and then to get
600    // counts,
601    // but OrdinalIterator only allows reading ordinals once.
602    int[] resultOrdinals = topByCountOrds.toArray();
603
604    //// (4.2) Use faceting results
605    FacetLabel[] labels = ordLabels.getLabels(resultOrdinals);
606    List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length);
607    for (int i = 0; i < resultOrdinals.length; i++) {
608      labelsAndValues.add(
609          new LabelAndValue(
610              labels[i].lastComponent(), defaultRecorder.getCount(resultOrdinals[i])));
611    }
612
613    IOUtils.close(indexReader, taxoReader);
614    int dimensionValue = defaultRecorder.getCount(dimOrdinal);
615    // We don't actually need to use FacetResult, it is up to client what to do with the results.
616    // Here we just want to demo that we can still do FacetResult as well
617    return new FacetResult(
618        dimension,
619        new String[0],
620        dimensionValue,
621        labelsAndValues.toArray(new LabelAndValue[0]),
622        labelsAndValues.size());
623  }
624
625  /**
626   * User drills down on 'Publish Date/2010', and we return facets for both 'Publish Date' and
627   * 'Author', using DrillSideways.
628   */
629  private List<FacetResult> drillSideways() throws IOException {
630    //// (1) init readers and searcher
631    DirectoryReader indexReader = DirectoryReader.open(indexDir);
632    IndexSearcher searcher = new IndexSearcher(indexReader);
633    TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir);
634
635    //// (2) init drill down query and collectors
636    TaxonomyFacetsCutter defaultTaxoCutter =
637        new TaxonomyFacetsCutter(DEFAULT_INDEX_FIELD_NAME, config, taxoReader);
638    CountFacetRecorder drillDownRecorder = new CountFacetRecorder();
639    FacetFieldCollectorManager<CountFacetRecorder> drillDownCollectorManager =
640        new FacetFieldCollectorManager<>(defaultTaxoCutter, drillDownRecorder);
641
642    DrillDownQuery q = new DrillDownQuery(config);
643
644    //// (2.1) add query and collector dimensions
645    q.add("Publish Date", "2010");
646    CountFacetRecorder publishDayDimensionRecorder = new CountFacetRecorder();
647    // Note that it is safe to use the same FacetsCutter here because we create Leaf cutter for each
648    // leaf for each
649    // FacetFieldCollectorManager anyway, and leaf cutter are not merged or anything like that.
650    FacetFieldCollectorManager<CountFacetRecorder> publishDayDimensionCollectorManager =
651        new FacetFieldCollectorManager<>(defaultTaxoCutter, publishDayDimensionRecorder);
652    List<FacetFieldCollectorManager<CountFacetRecorder>> drillSidewaysManagers =
653        List.of(publishDayDimensionCollectorManager);
654
655    //// (3) search
656    // Right now we return the same Recorder we created - so we can ignore results
657    DrillSideways ds = new DrillSideways(searcher, config, taxoReader);
658    ds.search(q, drillDownCollectorManager, drillSidewaysManagers);
659
660    //// (4) Get top 10 results by count for Author
661    List<FacetResult> facetResults = new ArrayList<>(2);
662    // This object provides labels for ordinals.
663    TaxonomyOrdLabelBiMap ordLabels = new TaxonomyOrdLabelBiMap(taxoReader);
664    // This object is used to get topN results by count
665    ComparableSupplier<ComparableUtils.ByCountComparable> countComparable =
666        ComparableUtils.byCount(drillDownRecorder);
667    //// (4.1) Chain two ordinal iterators to get top N children
668    int dimOrdinal = ordLabels.getOrd(new FacetLabel("Author"));
669    OrdinalIterator childrenIterator =
670        new TaxonomyChildrenOrdinalIterator(
671            drillDownRecorder.recordedOrds(),
672            taxoReader.getParallelTaxonomyArrays().parents(),
673            dimOrdinal);
674    OrdinalIterator topByCountOrds =
675        new TopnOrdinalIterator<>(childrenIterator, countComparable, 10);
676    // Get array of final ordinals - we need to use all of them to get labels first, and then to get
677    // counts,
678    // but OrdinalIterator only allows reading ordinals once.
679    int[] resultOrdinals = topByCountOrds.toArray();
680
681    //// (4.2) Use faceting results
682    FacetLabel[] labels = ordLabels.getLabels(resultOrdinals);
683    List<LabelAndValue> labelsAndValues = new ArrayList<>(labels.length);
684    for (int i = 0; i < resultOrdinals.length; i++) {
685      labelsAndValues.add(
686          new LabelAndValue(
687              labels[i].lastComponent(), drillDownRecorder.getCount(resultOrdinals[i])));
688    }
689    int dimensionValue = drillDownRecorder.getCount(dimOrdinal);
690    facetResults.add(
691        new FacetResult(
692            "Author",
693            new String[0],
694            dimensionValue,
695            labelsAndValues.toArray(new LabelAndValue[0]),
696            labelsAndValues.size()));
697
698    //// (5) Same process, but for Publish Date drill sideways dimension
699    countComparable = ComparableUtils.byCount(publishDayDimensionRecorder);
700    //// (4.1) Chain two ordinal iterators to get top N children
701    dimOrdinal = ordLabels.getOrd(new FacetLabel("Publish Date"));
702    childrenIterator =
703        new TaxonomyChildrenOrdinalIterator(
704            publishDayDimensionRecorder.recordedOrds(),
705            taxoReader.getParallelTaxonomyArrays().parents(),
706            dimOrdinal);
707    topByCountOrds = new TopnOrdinalIterator<>(childrenIterator, countComparable, 10);
708    // Get array of final ordinals - we need to use all of them to get labels first, and then to get
709    // counts,
710    // but OrdinalIterator only allows reading ordinals once.
711    resultOrdinals = topByCountOrds.toArray();
712
713    //// (4.2) Use faceting results
714    labels = ordLabels.getLabels(resultOrdinals);
715    labelsAndValues = new ArrayList<>(labels.length);
716    for (int i = 0; i < resultOrdinals.length; i++) {
717      labelsAndValues.add(
718          new LabelAndValue(
719              labels[i].lastComponent(), publishDayDimensionRecorder.getCount(resultOrdinals[i])));
720    }
721    dimensionValue = publishDayDimensionRecorder.getCount(dimOrdinal);
722    facetResults.add(
723        new FacetResult(
724            "Publish Date",
725            new String[0],
726            dimensionValue,
727            labelsAndValues.toArray(new LabelAndValue[0]),
728            labelsAndValues.size()));
729
730    IOUtils.close(indexReader, taxoReader);
731    return facetResults;
732  }
733
734  /** Runs the simple search example. */
735  public List<FacetResult> runSimpleFacetsWithSearch() throws IOException {
736    index();
737    return simpleFacetsWithSearch();
738  }
739
740  /** Runs the simple drill sideways example. */
741  public List<FacetResult> runSimpleFacetsWithDrillSideways() throws IOException {
742    index();
743    return simpleFacetsWithDrillSideways();
744  }
745
746  /** Runs the search example. */
747  public List<FacetResult> runFacetOnly() throws IOException {
748    index();
749    return facetsOnly();
750  }
751
752  /** Runs the search example. */
753  public List<FacetResult> runSearch() throws IOException {
754    index();
755    return facetsWithSearch();
756  }
757
758  /** Runs the drill-down example. */
759  public FacetResult runDrillDown() throws IOException {
760    index();
761    return drillDown();
762  }
763
764  /** Runs the drill-sideways example. */
765  public List<FacetResult> runDrillSideways() throws IOException {
766    index();
767    return drillSideways();
768  }
769
770  /** Runs the example of non overlapping range facets */
771  public List<FacetResult> runNonOverlappingRangesCountFacetsOnly() throws IOException {
772    index();
773    return exclusiveRangesCountFacetsOnly();
774  }
775
776  /** Runs the example of overlapping range facets */
777  public List<FacetResult> runOverlappingRangesCountFacetsOnly() throws IOException {
778    index();
779    return overlappingRangesCountFacetsOnly();
780  }
781
782  /** Runs the example of collecting long aggregations for non overlapping range facets. */
783  public List<FacetResult> runNonOverlappingRangesAggregationFacets() throws IOException {
784    index();
785    return exclusiveRangesAggregationFacets();
786  }
787
788  /** Runs the search and drill-down examples and prints the results. */
789  public static void main(String[] args) throws Exception {
790    SandboxFacetsExample example = new SandboxFacetsExample();
791
792    System.out.println("Simple facet counting example:");
793    System.out.println("---------------------------------------------");
794    for (FacetResult result : example.runSimpleFacetsWithSearch()) {
795      System.out.println(result);
796    }
797
798    System.out.println("Simple facet counting for drill sideways example:");
799    System.out.println("---------------------------------------------");
800    for (FacetResult result : example.runSimpleFacetsWithDrillSideways()) {
801      System.out.println(result);
802    }
803
804    System.out.println("Facet counting example:");
805    System.out.println("-----------------------");
806    List<FacetResult> results1 = example.runFacetOnly();
807    System.out.println("Author: " + results1.get(0));
808    System.out.println("Publish Date: " + results1.get(1));
809
810    System.out.println("Facet counting example (combined facets and search):");
811    System.out.println("-----------------------");
812    List<FacetResult> results = example.runSearch();
813    System.out.println("Author: " + results.get(0));
814    System.out.println("Publish Date: " + results.get(1));
815
816    System.out.println("Facet drill-down example (Publish Date/2010):");
817    System.out.println("---------------------------------------------");
818    System.out.println("Author: " + example.runDrillDown());
819
820    System.out.println("Facet drill-sideways example (Publish Date/2010):");
821    System.out.println("---------------------------------------------");
822    for (FacetResult result : example.runDrillSideways()) {
823      System.out.println(result);
824    }
825
826    System.out.println("Facet counting example with exclusive ranges:");
827    System.out.println("---------------------------------------------");
828    for (FacetResult result : example.runNonOverlappingRangesCountFacetsOnly()) {
829      System.out.println(result);
830    }
831
832    System.out.println("Facet counting example with overlapping ranges:");
833    System.out.println("---------------------------------------------");
834    for (FacetResult result : example.runOverlappingRangesCountFacetsOnly()) {
835      System.out.println(result);
836    }
837
838    System.out.println("Facet aggregation example with exclusive ranges:");
839    System.out.println("---------------------------------------------");
840    for (FacetResult result : example.runNonOverlappingRangesAggregationFacets()) {
841      System.out.println(result);
842    }
843  }
844}