/**
* Copyright (c) 2015 Lemur Consulting Ltd.
* <p/>
* Licensed 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.
*/
package uk.co.flax.biosolr;
import java.io.Serializable;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
/**
* POJO representing an entry in the hierarchical facet tree.
*
* <p>Implements Comparable, so that entries in the tree may be ordered by their
* value count.</p>
*
* @author mlp
*/
public class TreeFacetField implements Comparable<TreeFacetField>, Serializable, Cloneable {
private static final long serialVersionUID = 5709339278691781478L;
private static final String LABEL_KEY = "label";
private static final String VALUE_KEY = "value";
private static final String COUNT_KEY = "count";
private static final String TOTAL_KEY = "total";
private static final String HIERARCHY_KEY = "hierarchy";
private final String label;
private final String value;
private final long count;
private long childCount;
private final SortedSet<TreeFacetField> hierarchy;
/**
* Construct a new TreeFacetField node.
* @param label the label for the node (optional).
* @param value the facet value.
* @param count the actual facet count for this node.
* @param childCount the total count for facets which are children of this
* node.
* @param hierarchy the set of nodes which comprise the children of this node.
*/
public TreeFacetField(String label, String value, long count, long childCount, SortedSet<TreeFacetField> hierarchy) {
this.label = label;
this.value = value;
this.count = count;
this.childCount = childCount;
this.hierarchy = hierarchy;
}
public String getLabel() {
return label;
}
public String getValue() {
return value;
}
public long getCount() {
return count;
}
public long getChildCount() {
return childCount;
}
public long getTotal() {
return count + childCount;
}
public SortedSet<TreeFacetField> getHierarchy() {
return hierarchy;
}
/**
* @return <code>true</code> if this node has a non-empty hierarchy.
*/
public boolean hasChildren() {
return hierarchy != null && hierarchy.size() > 0;
}
/**
* Recalculate and update the child count for this node, in the event that
* one or more child nodes have been removed (possibly further down the
* hierarchy).
* @return the new total count for this node, which can be used to recurse
* down the tree.
*/
public long recalculateChildCount() {
// Reset the child count
childCount = 0;
if (hasChildren()) {
for (TreeFacetField childNode : hierarchy) {
childCount += childNode.recalculateChildCount();
}
}
return getTotal();
}
@Override
public int compareTo(TreeFacetField o) {
int ret = 0;
if (o == null) {
ret = 1;
} else if (this.equals(o)) {
// As per the definition of SortedSet, if these items
// are equivalent, only one may exist in the Set.
ret = 0;
} else {
// Compare the total, then the count if the totals are the same
ret = (int) (getTotal() - o.getTotal()) | (int) (count - o.count);
if (ret == 0) {
// If all the counts are the same, compare the ID as well, to double-check
// whether they're actually the same entry
ret = getValue().compareTo(o.getValue());
if (ret == 0 && StringUtils.isNotBlank(label) && StringUtils.isNotBlank(o.getLabel())) {
ret = getLabel().compareTo(o.getLabel());
}
}
}
return ret;
}
/**
* Convert this object to a SimpleOrderedMap, making it easier to serialize.
* @return the equivalent SimpleOrderedMap for this object.
*/
public SimpleOrderedMap<Object> toMap() {
SimpleOrderedMap<Object> map = new SimpleOrderedMap<>();
if (label != null) {
map.add(LABEL_KEY, label);
}
map.add(VALUE_KEY, value);
map.add(COUNT_KEY, count);
map.add(TOTAL_KEY, getTotal());
if (hierarchy != null && hierarchy.size() > 0) {
// Recurse through the child nodes, converting each to a map
List<NamedList<Object>> hierarchyList =
hierarchy.stream().map(TreeFacetField::toMap).collect(Collectors.toList());
map.add(HIERARCHY_KEY, hierarchyList);
}
return map;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (count ^ (count >>> 32));
result = prime * result + ((value == null) ? 0 : value.hashCode());
result = prime * result + ((label == null) ? 0 : label.hashCode());
result = prime * result + (int) (childCount ^ (childCount >>> 32));
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof TreeFacetField)) {
return false;
}
TreeFacetField other = (TreeFacetField) obj;
if (count != other.count) {
return false;
}
if (childCount != other.childCount) {
return false;
}
if (value == null) {
if (other.value != null) {
return false;
}
} else if (!value.equals(other.value)) {
return false;
}
if (label == null) {
if (other.label != null) {
return false;
}
} else if (!label.equals(other.label)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(value);
if (StringUtils.isNotBlank(label)) {
sb.append(" [").append(label).append("]");
}
sb.append(" ").append(count).append("/").append(getTotal());
return sb.toString();
}
@Override
public TreeFacetField clone() {
// Recursively clone the hierarchy
return new TreeFacetField(label, value, count, childCount, cloneHierarchy(this.hierarchy));
}
private SortedSet<TreeFacetField> cloneHierarchy(SortedSet<TreeFacetField> orig) {
SortedSet<TreeFacetField> cloned = null;
if (orig != null) {
cloned = new TreeSet<>(orig.comparator());
for (TreeFacetField tff : orig) {
cloned.add(tff.clone());
}
}
return cloned;
}
}