--- /dev/null
+package org.apache.lucene.facet.search.params;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+
+import org.apache.lucene.facet.index.params.CategoryListParams;
+import org.apache.lucene.facet.search.CategoryListIterator;
+import org.apache.lucene.facet.search.FacetArrays;
+import org.apache.lucene.facet.search.FacetResultsHandler;
+import org.apache.lucene.facet.search.TopKFacetResultsHandler;
+import org.apache.lucene.facet.search.TopKInEachNodeHandler;
+import org.apache.lucene.facet.search.aggregator.Aggregator;
+import org.apache.lucene.facet.search.cache.CategoryListData;
+import org.apache.lucene.facet.search.cache.CategoryListCache;
+import org.apache.lucene.facet.taxonomy.CategoryPath;
+import org.apache.lucene.facet.taxonomy.TaxonomyReader;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Request to accumulate facet information for a specified facet and possibly
+ * also some of its descendants, upto a specified depth.
+ * <p>
+ * The facet request additionally defines what information should
+ * be computed within the facet results, if and how should results
+ * be ordered, etc.
+ * <P>
+ * An example facet request is to look at all sub-categories of "Author", and
+ * return the 10 with the highest counts (sorted by decreasing count).
+ *
+ * @lucene.experimental
+ */
+public abstract class FacetRequest implements Cloneable {
+
+ /**
+ * Default depth for facets accumulation.
+ * @see #getDepth()
+ */
+ public static final int DEFAULT_DEPTH = 1;
+
+ /**
+ * Default sort mode.
+ * @see #getSortBy()
+ */
+ public static final SortBy DEFAULT_SORT_BY = SortBy.VALUE;
+
+ /**
+ * Default result mode
+ * @see #getResultMode()
+ */
+ public static final ResultMode DEFAULT_RESULT_MODE = ResultMode.GLOBAL_FLAT;
+
+ private final CategoryPath categoryPath;
+ private final int numResults;
+ private int numLabel;
+ private int depth;
+ private SortOrder sortOrder;
+ private SortBy sortBy;
+
+ /**
+ * Computed at construction, this hashCode is based on two final members
+ * {@link CategoryPath} and <code>numResults</code>
+ */
+ private final int hashCode;
+
+ private ResultMode resultMode = DEFAULT_RESULT_MODE;
+
+ /**
+ * Initialize the request with a given path, and a requested number of facets
+ * results. By default, all returned results would be labeled - to alter this
+ * default see {@link #setNumLabel(int)}.
+ * <p>
+ * <b>NOTE:</b> if <code>numResults</code> is given as
+ * <code>Integer.MAX_VALUE</code> than all the facet results would be
+ * returned, without any limit.
+ * <p>
+ * <b>NOTE:</b> it is assumed that the given {@link CategoryPath} is not
+ * modified after construction of this object. Otherwise, some things may not
+ * function properly, e.g. {@link #hashCode()}.
+ *
+ * @throws IllegalArgumentException if numResults is ≤ 0
+ */
+ public FacetRequest(CategoryPath path, int numResults) {
+ if (numResults <= 0) {
+ throw new IllegalArgumentException("num results must be a positive (>0) number: " + numResults);
+ }
+ if (path == null) {
+ throw new IllegalArgumentException("category path cannot be null!");
+ }
+ categoryPath = path;
+ this.numResults = numResults;
+ numLabel = numResults;
+ depth = DEFAULT_DEPTH;
+ sortBy = DEFAULT_SORT_BY;
+ sortOrder = SortOrder.DESCENDING;
+
+ hashCode = categoryPath.hashCode() ^ this.numResults;
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ // Overridden to make it public
+ return super.clone();
+ }
+
+ public void setNumLabel(int numLabel) {
+ this.numLabel = numLabel;
+ }
+
+ public void setDepth(int depth) {
+ this.depth = depth;
+ }
+
+ public void setSortOrder(SortOrder sortOrder) {
+ this.sortOrder = sortOrder;
+ }
+
+ public void setSortBy(SortBy sortBy) {
+ this.sortBy = sortBy;
+ }
+
+ /**
+ * The root category of this facet request. The categories that are returned
+ * as a result of this request will all be descendants of this root.
+ * <p>
+ * <b>NOTE:</b> you should not modify the returned {@link CategoryPath}, or
+ * otherwise some methonds may not work properly, e.g. {@link #hashCode()}.
+ */
+ public final CategoryPath getCategoryPath() {
+ return categoryPath;
+ }
+
+ /**
+ * How deeply to look under the given category. If the depth is 0,
+ * only the category itself is counted. If the depth is 1, its immediate
+ * children are also counted, and so on. If the depth is Integer.MAX_VALUE,
+ * all the category's descendants are counted.<br>
+ * TODO (Facet): add AUTO_EXPAND option
+ */
+ public final int getDepth() {
+ return depth;
+ }
+
+ /**
+ * If getNumLabel()<getNumResults(), only the first getNumLabel() results
+ * will have their category paths calculated, and the rest will only be
+ * available as ordinals (category numbers) and will have null paths.
+ * <P>
+ * If Integer.MAX_VALUE is specified, all
+ * results are labled.
+ * <P>
+ * The purpose of this parameter is to avoid having to run the whole
+ * faceted search again when the user asks for more values for the facet;
+ * The application can ask (getNumResults()) for more values than it needs
+ * to show, but keep getNumLabel() only the number it wants to immediately
+ * show. The slow-down caused by finding more values is negligible, because
+ * the slowest part - finding the categories' paths, is avoided.
+ * <p>
+ * Depending on the {@link #getResultMode() LimitsMode},
+ * this limit is applied globally or per results node.
+ * In the global mode, if this limit is 3,
+ * only 3 top results would be labeled.
+ * In the per-node mode, if this limit is 3,
+ * 3 top children of {@link #getCategoryPath() the target category} would be labeled,
+ * as well as 3 top children of each of them, and so forth, until the depth defined
+ * by {@link #getDepth()}.
+ * @see #getResultMode()
+ */
+ public final int getNumLabel() {
+ return numLabel;
+ }
+
+ /**
+ * The number of sub-categories to return (at most).
+ * If the sub-categories are returned.
+ * <p>
+ * If Integer.MAX_VALUE is specified, all
+ * sub-categories are returned.
+ * <p>
+ * Depending on the {@link #getResultMode() LimitsMode},
+ * this limit is applied globally or per results node.
+ * In the global mode, if this limit is 3,
+ * only 3 top results would be computed.
+ * In the per-node mode, if this limit is 3,
+ * 3 top children of {@link #getCategoryPath() the target category} would be returned,
+ * as well as 3 top children of each of them, and so forth, until the depth defined
+ * by {@link #getDepth()}.
+ * @see #getResultMode()
+ */
+ public final int getNumResults() {
+ return numResults;
+ }
+
+ /**
+ * Sort options for facet results.
+ */
+ public enum SortBy {
+ /** sort by category ordinal with the taxonomy */
+ ORDINAL,
+
+ /** sort by computed category value */
+ VALUE
+ }
+
+ /** Specify how should results be sorted. */
+ public final SortBy getSortBy() {
+ return sortBy;
+ }
+
+ /** Requested sort order for the results. */
+ public enum SortOrder { ASCENDING, DESCENDING }
+
+ /** Return the requested order of results. */
+ public final SortOrder getSortOrder() {
+ return sortOrder;
+ }
+
+ @Override
+ public String toString() {
+ return categoryPath.toString()+" nRes="+numResults+" nLbl="+numLabel;
+ }
+
+ /**
+ * Creates a new {@link FacetResultsHandler} that matches the request logic
+ * and current settings, such as {@link #getDepth() depth},
+ * {@link #getResultMode() limits-mode}, etc, as well as the passed in
+ * {@link TaxonomyReader}.
+ *
+ * @param taxonomyReader taxonomy reader is needed e.g. for knowing the
+ * taxonomy size.
+ */
+ public FacetResultsHandler createFacetResultsHandler(TaxonomyReader taxonomyReader) {
+ try {
+ if (resultMode == ResultMode.PER_NODE_IN_TREE) {
+ return new TopKInEachNodeHandler(taxonomyReader, (FacetRequest) clone());
+ }
+ return new TopKFacetResultsHandler(taxonomyReader, (FacetRequest) clone());
+ } catch (CloneNotSupportedException e) {
+ // Shouldn't happen since we implement Cloneable. If it does happen, it is
+ // probably because the class was changed to not implement Cloneable
+ // anymore.
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Result structure manner of applying request's limits such as
+ * {@link #getNumLabel()} and
+ * {@link #getNumResults()}.
+ */
+ public enum ResultMode {
+ /** Limits are applied per node, and the result has a full tree structure. */
+ PER_NODE_IN_TREE,
+
+ /** Limits are applied globally, on total number of results, and the result has a flat structure. */
+ GLOBAL_FLAT
+ }
+
+ /** Return the requested result mode. */
+ public final ResultMode getResultMode() {
+ return resultMode;
+ }
+
+ /**
+ * @param resultMode the resultMode to set
+ * @see #getResultMode()
+ */
+ public void setResultMode(ResultMode resultMode) {
+ this.resultMode = resultMode;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof FacetRequest) {
+ FacetRequest that = (FacetRequest)o;
+ return that.hashCode == this.hashCode &&
+ that.categoryPath.equals(this.categoryPath) &&
+ that.numResults == this.numResults &&
+ that.depth == this.depth &&
+ that.resultMode == this.resultMode &&
+ that.numLabel == this.numLabel;
+ }
+ return false;
+ }
+
+ /**
+ * Create an aggregator for this facet request. Aggregator action depends on
+ * request definition. For a count request, it will usually increment the
+ * count for that facet.
+ *
+ * @param useComplements
+ * whether the complements optimization is being used for current
+ * computation.
+ * @param arrays
+ * provider for facet arrays in use for current computation.
+ * @param indexReader
+ * index reader in effect.
+ * @param taxonomy
+ * reader of taxonomy in effect.
+ * @throws IOException
+ */
+ public abstract Aggregator createAggregator(boolean useComplements,
+ FacetArrays arrays, IndexReader indexReader,
+ TaxonomyReader taxonomy) throws IOException;
+
+ /**
+ * Create the category list iterator for the specified partition.
+ * If a non null cache is provided which contains the required data,
+ * use it for the iteration.
+ */
+ public CategoryListIterator createCategoryListIterator(IndexReader reader,
+ TaxonomyReader taxo, FacetSearchParams sParams, int partition)
+ throws IOException {
+ CategoryListCache clCache = sParams.getClCache();
+ CategoryListParams clParams = sParams.getFacetIndexingParams().getCategoryListParams(categoryPath);
+ if (clCache!=null) {
+ CategoryListData clData = clCache.get(clParams);
+ if (clData!=null) {
+ return clData.iterator(partition);
+ }
+ }
+ return clParams.createCategoryListIterator(reader, partition);
+ }
+
+ /**
+ * Return the value of a category used for facets computations for this
+ * request. For a count request this would be the count for that facet, i.e.
+ * an integer number. but for other requests this can be the result of a more
+ * complex operation, and the result can be any double precision number.
+ * Having this method with a general name <b>value</b> which is double
+ * precision allows to have more compact API and code for handling counts and
+ * perhaps other requests (such as for associations) very similarly, and by
+ * the same code and API, avoiding code duplication.
+ *
+ * @param arrays
+ * provider for facet arrays in use for current computation.
+ * @param idx
+ * an index into the count arrays now in effect in
+ * <code>arrays</code>. E.g., for ordinal number <i>n</i>, with
+ * partition, of size <i>partitionSize</i>, now covering <i>n</i>,
+ * <code>getValueOf</code> would be invoked with <code>idx</code>
+ * being <i>n</i> % <i>partitionSize</i>.
+ */
+ public abstract double getValueOf(FacetArrays arrays, int idx);
+
+ /**
+ * Indicates whether this facet request is eligible for applying the complements optimization.
+ */
+ public boolean supportsComplements() {
+ return false; // by default: no
+ }
+
+ /** Indicates whether the results of this request depends on each result document's score */
+ public abstract boolean requireDocumentScore();
+
+}