1 package org.apache.lucene.facet.search.params;
3 import java.io.IOException;
5 import org.apache.lucene.index.IndexReader;
7 import org.apache.lucene.facet.index.params.CategoryListParams;
8 import org.apache.lucene.facet.search.CategoryListIterator;
9 import org.apache.lucene.facet.search.FacetArrays;
10 import org.apache.lucene.facet.search.FacetResultsHandler;
11 import org.apache.lucene.facet.search.TopKFacetResultsHandler;
12 import org.apache.lucene.facet.search.TopKInEachNodeHandler;
13 import org.apache.lucene.facet.search.aggregator.Aggregator;
14 import org.apache.lucene.facet.search.cache.CategoryListData;
15 import org.apache.lucene.facet.search.cache.CategoryListCache;
16 import org.apache.lucene.facet.taxonomy.CategoryPath;
17 import org.apache.lucene.facet.taxonomy.TaxonomyReader;
20 * Licensed to the Apache Software Foundation (ASF) under one or more
21 * contributor license agreements. See the NOTICE file distributed with
22 * this work for additional information regarding copyright ownership.
23 * The ASF licenses this file to You under the Apache License, Version 2.0
24 * (the "License"); you may not use this file except in compliance with
25 * the License. You may obtain a copy of the License at
27 * http://www.apache.org/licenses/LICENSE-2.0
29 * Unless required by applicable law or agreed to in writing, software
30 * distributed under the License is distributed on an "AS IS" BASIS,
31 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32 * See the License for the specific language governing permissions and
33 * limitations under the License.
37 * Request to accumulate facet information for a specified facet and possibly
38 * also some of its descendants, upto a specified depth.
40 * The facet request additionally defines what information should
41 * be computed within the facet results, if and how should results
44 * An example facet request is to look at all sub-categories of "Author", and
45 * return the 10 with the highest counts (sorted by decreasing count).
47 * @lucene.experimental
49 public abstract class FacetRequest implements Cloneable {
52 * Default depth for facets accumulation.
55 public static final int DEFAULT_DEPTH = 1;
61 public static final SortBy DEFAULT_SORT_BY = SortBy.VALUE;
65 * @see #getResultMode()
67 public static final ResultMode DEFAULT_RESULT_MODE = ResultMode.GLOBAL_FLAT;
69 private final CategoryPath categoryPath;
70 private final int numResults;
73 private SortOrder sortOrder;
74 private SortBy sortBy;
77 * Computed at construction, this hashCode is based on two final members
78 * {@link CategoryPath} and <code>numResults</code>
80 private final int hashCode;
82 private ResultMode resultMode = DEFAULT_RESULT_MODE;
85 * Initialize the request with a given path, and a requested number of facets
86 * results. By default, all returned results would be labeled - to alter this
87 * default see {@link #setNumLabel(int)}.
89 * <b>NOTE:</b> if <code>numResults</code> is given as
90 * <code>Integer.MAX_VALUE</code> than all the facet results would be
91 * returned, without any limit.
93 * <b>NOTE:</b> it is assumed that the given {@link CategoryPath} is not
94 * modified after construction of this object. Otherwise, some things may not
95 * function properly, e.g. {@link #hashCode()}.
97 * @throws IllegalArgumentException if numResults is ≤ 0
99 public FacetRequest(CategoryPath path, int numResults) {
100 if (numResults <= 0) {
101 throw new IllegalArgumentException("num results must be a positive (>0) number: " + numResults);
104 throw new IllegalArgumentException("category path cannot be null!");
107 this.numResults = numResults;
108 numLabel = numResults;
109 depth = DEFAULT_DEPTH;
110 sortBy = DEFAULT_SORT_BY;
111 sortOrder = SortOrder.DESCENDING;
113 hashCode = categoryPath.hashCode() ^ this.numResults;
117 public Object clone() throws CloneNotSupportedException {
118 // Overridden to make it public
119 return super.clone();
122 public void setNumLabel(int numLabel) {
123 this.numLabel = numLabel;
126 public void setDepth(int depth) {
130 public void setSortOrder(SortOrder sortOrder) {
131 this.sortOrder = sortOrder;
134 public void setSortBy(SortBy sortBy) {
135 this.sortBy = sortBy;
139 * The root category of this facet request. The categories that are returned
140 * as a result of this request will all be descendants of this root.
142 * <b>NOTE:</b> you should not modify the returned {@link CategoryPath}, or
143 * otherwise some methonds may not work properly, e.g. {@link #hashCode()}.
145 public final CategoryPath getCategoryPath() {
150 * How deeply to look under the given category. If the depth is 0,
151 * only the category itself is counted. If the depth is 1, its immediate
152 * children are also counted, and so on. If the depth is Integer.MAX_VALUE,
153 * all the category's descendants are counted.<br>
154 * TODO (Facet): add AUTO_EXPAND option
156 public final int getDepth() {
161 * If getNumLabel()<getNumResults(), only the first getNumLabel() results
162 * will have their category paths calculated, and the rest will only be
163 * available as ordinals (category numbers) and will have null paths.
165 * If Integer.MAX_VALUE is specified, all
166 * results are labled.
168 * The purpose of this parameter is to avoid having to run the whole
169 * faceted search again when the user asks for more values for the facet;
170 * The application can ask (getNumResults()) for more values than it needs
171 * to show, but keep getNumLabel() only the number it wants to immediately
172 * show. The slow-down caused by finding more values is negligible, because
173 * the slowest part - finding the categories' paths, is avoided.
175 * Depending on the {@link #getResultMode() LimitsMode},
176 * this limit is applied globally or per results node.
177 * In the global mode, if this limit is 3,
178 * only 3 top results would be labeled.
179 * In the per-node mode, if this limit is 3,
180 * 3 top children of {@link #getCategoryPath() the target category} would be labeled,
181 * as well as 3 top children of each of them, and so forth, until the depth defined
182 * by {@link #getDepth()}.
183 * @see #getResultMode()
185 public final int getNumLabel() {
190 * The number of sub-categories to return (at most).
191 * If the sub-categories are returned.
193 * If Integer.MAX_VALUE is specified, all
194 * sub-categories are returned.
196 * Depending on the {@link #getResultMode() LimitsMode},
197 * this limit is applied globally or per results node.
198 * In the global mode, if this limit is 3,
199 * only 3 top results would be computed.
200 * In the per-node mode, if this limit is 3,
201 * 3 top children of {@link #getCategoryPath() the target category} would be returned,
202 * as well as 3 top children of each of them, and so forth, until the depth defined
203 * by {@link #getDepth()}.
204 * @see #getResultMode()
206 public final int getNumResults() {
211 * Sort options for facet results.
214 /** sort by category ordinal with the taxonomy */
217 /** sort by computed category value */
221 /** Specify how should results be sorted. */
222 public final SortBy getSortBy() {
226 /** Requested sort order for the results. */
227 public enum SortOrder { ASCENDING, DESCENDING }
229 /** Return the requested order of results. */
230 public final SortOrder getSortOrder() {
235 public String toString() {
236 return categoryPath.toString()+" nRes="+numResults+" nLbl="+numLabel;
240 * Creates a new {@link FacetResultsHandler} that matches the request logic
241 * and current settings, such as {@link #getDepth() depth},
242 * {@link #getResultMode() limits-mode}, etc, as well as the passed in
243 * {@link TaxonomyReader}.
245 * @param taxonomyReader taxonomy reader is needed e.g. for knowing the
248 public FacetResultsHandler createFacetResultsHandler(TaxonomyReader taxonomyReader) {
250 if (resultMode == ResultMode.PER_NODE_IN_TREE) {
251 return new TopKInEachNodeHandler(taxonomyReader, (FacetRequest) clone());
253 return new TopKFacetResultsHandler(taxonomyReader, (FacetRequest) clone());
254 } catch (CloneNotSupportedException e) {
255 // Shouldn't happen since we implement Cloneable. If it does happen, it is
256 // probably because the class was changed to not implement Cloneable
258 throw new RuntimeException(e);
263 * Result structure manner of applying request's limits such as
264 * {@link #getNumLabel()} and
265 * {@link #getNumResults()}.
267 public enum ResultMode {
268 /** Limits are applied per node, and the result has a full tree structure. */
271 /** Limits are applied globally, on total number of results, and the result has a flat structure. */
275 /** Return the requested result mode. */
276 public final ResultMode getResultMode() {
281 * @param resultMode the resultMode to set
282 * @see #getResultMode()
284 public void setResultMode(ResultMode resultMode) {
285 this.resultMode = resultMode;
289 public int hashCode() {
294 public boolean equals(Object o) {
295 if (o instanceof FacetRequest) {
296 FacetRequest that = (FacetRequest)o;
297 return that.hashCode == this.hashCode &&
298 that.categoryPath.equals(this.categoryPath) &&
299 that.numResults == this.numResults &&
300 that.depth == this.depth &&
301 that.resultMode == this.resultMode &&
302 that.numLabel == this.numLabel;
308 * Create an aggregator for this facet request. Aggregator action depends on
309 * request definition. For a count request, it will usually increment the
310 * count for that facet.
312 * @param useComplements
313 * whether the complements optimization is being used for current
316 * provider for facet arrays in use for current computation.
318 * index reader in effect.
320 * reader of taxonomy in effect.
321 * @throws IOException
323 public abstract Aggregator createAggregator(boolean useComplements,
324 FacetArrays arrays, IndexReader indexReader,
325 TaxonomyReader taxonomy) throws IOException;
328 * Create the category list iterator for the specified partition.
329 * If a non null cache is provided which contains the required data,
330 * use it for the iteration.
332 public CategoryListIterator createCategoryListIterator(IndexReader reader,
333 TaxonomyReader taxo, FacetSearchParams sParams, int partition)
335 CategoryListCache clCache = sParams.getClCache();
336 CategoryListParams clParams = sParams.getFacetIndexingParams().getCategoryListParams(categoryPath);
338 CategoryListData clData = clCache.get(clParams);
340 return clData.iterator(partition);
343 return clParams.createCategoryListIterator(reader, partition);
347 * Return the value of a category used for facets computations for this
348 * request. For a count request this would be the count for that facet, i.e.
349 * an integer number. but for other requests this can be the result of a more
350 * complex operation, and the result can be any double precision number.
351 * Having this method with a general name <b>value</b> which is double
352 * precision allows to have more compact API and code for handling counts and
353 * perhaps other requests (such as for associations) very similarly, and by
354 * the same code and API, avoiding code duplication.
357 * provider for facet arrays in use for current computation.
359 * an index into the count arrays now in effect in
360 * <code>arrays</code>. E.g., for ordinal number <i>n</i>, with
361 * partition, of size <i>partitionSize</i>, now covering <i>n</i>,
362 * <code>getValueOf</code> would be invoked with <code>idx</code>
363 * being <i>n</i> % <i>partitionSize</i>.
365 public abstract double getValueOf(FacetArrays arrays, int idx);
368 * Indicates whether this facet request is eligible for applying the complements optimization.
370 public boolean supportsComplements() {
371 return false; // by default: no
374 /** Indicates whether the results of this request depends on each result document's score */
375 public abstract boolean requireDocumentScore();