--- /dev/null
+package org.apache.lucene.facet.search.results;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+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.
+ */
+
+/**
+ * Mutable implementation for Result of faceted search for a certain taxonomy node.
+ *
+ * @lucene.experimental
+ */
+public class MutableFacetResultNode implements FacetResultNode {
+
+ /**
+ * Empty sub results to be returned when there are no results.
+ * We never return null, so that code using this can remain simpler.
+ */
+ private static final ArrayList<FacetResultNode> EMPTY_SUB_RESULTS = new ArrayList<FacetResultNode>();
+
+ private int ordinal;
+ private CategoryPath label = null;
+ private double value;
+ private double residue;
+ private List<FacetResultNode> subResults;
+
+ /**
+ * Create a Facet Result Node.
+ *
+ * @param ordinal
+ * ordinal in the taxonomy of the category of this result.
+ * @param value
+ * value this result.
+ */
+ public MutableFacetResultNode(int ordinal, double value) {
+ this(ordinal, value, 0, null, null);
+ }
+
+ /**
+ * Reset a facet Result Node.
+ * <p>
+ * Used at the population of facet results, not intended for regular use by
+ * applications.
+ *
+ * @param ordinal
+ * ordinal in the taxonomy of the category of this result.
+ * @param value
+ * value of this result.
+ */
+ public void reset(int ordinal, double value) {
+ this.ordinal = ordinal;
+ this.value = value;
+ if (subResults != null) {
+ subResults.clear();
+ }
+ label = null;
+ residue = 0;
+ }
+
+ /**
+ * Create a Facet Result Node.
+ *
+ * @param ordinal
+ * ordinal in the taxonomy of the category of this result.
+ * @param value
+ * value of this result.
+ * @param residue
+ * Value of screened out sub results.
+ * @param label
+ * label of the category path of this result.
+ * @param subResults
+ * - sub results, usually descendants, sometimes child results, of
+ * this result - depending on the request.
+ */
+ public MutableFacetResultNode(int ordinal, double value, double residue,
+ CategoryPath label, List<FacetResultNode> subResults) {
+ this.ordinal = ordinal;
+ this.value = value;
+ this.residue = residue;
+ this.label = label;
+ this.subResults = subResults;
+ }
+
+ /**
+ * Create a mutable facet result node from another result node
+ * @param other other result node to copy from
+ * @param takeSubResults set to true to take also sub results of other node
+ */
+ public MutableFacetResultNode(FacetResultNode other, boolean takeSubResults) {
+ this(other.getOrdinal(), other.getValue(), other.getResidue(), other
+ .getLabel(), takeSubResults ? resultsToList(other.getSubResults())
+ : null);
+ }
+
+ private static List<FacetResultNode> resultsToList(
+ Iterable<? extends FacetResultNode> subResults) {
+ if (subResults == null) {
+ return null;
+ }
+ ArrayList<FacetResultNode> res = new ArrayList<FacetResultNode>();
+ for (FacetResultNode r : subResults) {
+ res.add(r);
+ }
+ return res;
+ }
+
+ @Override
+ public String toString() {
+ return toString("");
+ }
+
+ /**
+ * Number of sub results.
+ */
+ private int numSubResults() {
+ if (subResults == null) {
+ return 0;
+ }
+ return subResults.size();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.apache.lucene.facet.search.results2.FacetResultNode#toString(java.lang.
+ * String)
+ */
+ public String toString(String prefix) {
+ StringBuilder sb = new StringBuilder(prefix);
+
+ sb.append("Facet Result Node with ").append(numSubResults()).append(
+ " sub result nodes.\n");
+
+ // label
+ sb.append(prefix).append("Name: ").append(getLabel()).append("\n");
+
+ // value
+ sb.append(prefix).append("Value: ").append(value).append("\n");
+
+ // residue
+ sb.append(prefix).append("Residue: ").append(residue).append("\n");
+
+ if (subResults != null) {
+ int i = 0;
+ for (FacetResultNode subRes : subResults) {
+ sb.append("\n").append(prefix).append("Subresult #").append(i++)
+ .append("\n").append(subRes.toString(prefix + "\t"));
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public final int getOrdinal() {
+ return ordinal;
+ }
+
+ public final CategoryPath getLabel() {
+ return label;
+ }
+
+ /**
+ * Set the label of the category of this result.
+ * @param label the label to set.
+ * @see #getLabel()
+ */
+ public void setLabel(CategoryPath label) {
+ this.label = label;
+ }
+
+ public final double getValue() {
+ return value;
+ }
+
+ /**
+ * Set the value of this result.
+ *
+ * @param value
+ * the value to set
+ * @see #getValue()
+ */
+ public void setValue(double value) {
+ this.value = value;
+ }
+
+ /**
+ * increase the value for this result.
+ * @param addedValue the value to add
+ * @see #getValue()
+ */
+ public void increaseValue(double addedValue) {
+ this.value += addedValue;
+ }
+
+ public final double getResidue() {
+ return residue;
+ }
+
+ /**
+ * Set the residue.
+ * @param residue the residue to set
+ * @see #getResidue()
+ */
+ public void setResidue(double residue) {
+ this.residue = residue;
+ }
+
+ /**
+ * increase the residue for this result.
+ * @param addedResidue the residue to add
+ * @see #getResidue()
+ */
+ public void increaseResidue(double addedResidue) {
+ this.residue += addedResidue;
+ }
+
+ public final Iterable<? extends FacetResultNode> getSubResults() {
+ return subResults != null ? subResults : EMPTY_SUB_RESULTS;
+ }
+
+ /**
+ * Trim sub results to a given size.
+ * <p>
+ * Note: Although the {@link #getResidue()} is not guaranteed to be
+ * accurate, it is worth fixing it, as possible, by taking under account the
+ * trimmed sub-nodes.
+ */
+ public void trimSubResults(int size) {
+ if (subResults == null || subResults.size() == 0) {
+ return;
+ }
+
+ ArrayList<FacetResultNode> trimmed = new ArrayList<FacetResultNode>(size);
+ for (int i = 0; i < subResults.size() && i < size; i++) {
+ MutableFacetResultNode trimmedNode = toImpl(subResults.get(i));
+ trimmedNode.trimSubResults(size);
+ trimmed.add(trimmedNode);
+ }
+
+ /*
+ * If we are trimming, it means Sampling is in effect and the extra
+ * (over-sampled) results are being trimmed. Although the residue is not
+ * guaranteed to be accurate for Sampling, we try our best to fix it.
+ * The node's residue now will take under account the sub-nodes we're
+ * trimming.
+ */
+ for (int i = size; i < subResults.size(); i++) {
+ increaseResidue(subResults.get(i).getValue());
+ }
+
+ subResults = trimmed;
+ }
+
+ /**
+ * Set the sub results.
+ * @param subResults the sub-results to set
+ */
+ public void setSubResults(List<FacetResultNode> subResults) {
+ this.subResults = subResults;
+ }
+
+ /**
+ * Append a sub result (as last).
+ * @param subRes sub-result to be appended
+ */
+ public void appendSubResult(FacetResultNode subRes) {
+ if (subResults == null) {
+ subResults = new ArrayList<FacetResultNode>();
+ }
+ subResults.add(subRes);
+ }
+
+ /**
+ * Insert sub result (as first).
+ * @param subRes sub-result to be inserted
+ */
+ public void insertSubResult(FacetResultNode subRes) {
+ if (subResults == null) {
+ subResults = new ArrayList<FacetResultNode>();
+ }
+ subResults.add(0, subRes);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.apache.lucene.facet.search.results.FacetResultNode#getLabel(org.apache.lucene
+ * .facet.taxonomy.TaxonomyReader)
+ */
+ public final CategoryPath getLabel(TaxonomyReader taxonomyReader)
+ throws IOException {
+ if (label == null) {
+ label = taxonomyReader.getPath(ordinal);
+ }
+ return label;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.lucene.facet.search.results.FacetResultNode#getNumSubResults()
+ */
+ public final int getNumSubResults() {
+ return subResults == null ? 0 : subResults.size();
+ }
+
+ /**
+ * Internal utility: turn a result node into an implementation class
+ * with richer API that allows modifying it.
+ * <p>
+ * In case that input result node is already of an implementation
+ * class only casting is done, but in any case we pay the price
+ * of checking "instance of".
+ * @param frn facet result node to be turned into an implementation class object
+ */
+ public static MutableFacetResultNode toImpl(FacetResultNode frn) {
+ if (frn instanceof MutableFacetResultNode) {
+ return (MutableFacetResultNode) frn;
+ }
+ return new MutableFacetResultNode(frn, true);
+ }
+
+}