org.apache.lucene.facet.taxonomy.FacetLabel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lucene-facet Show documentation
Show all versions of lucene-facet Show documentation
Apache Lucene (module: facet)
/*
* 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.
*/
package org.apache.lucene.facet.taxonomy;
import static org.apache.lucene.util.ByteBlockPool.BYTE_BLOCK_SIZE;
import java.util.Arrays;
import org.apache.lucene.facet.taxonomy.writercache.LruTaxonomyWriterCache; // javadocs
import org.apache.lucene.facet.taxonomy.writercache.NameHashIntCacheLRU; // javadocs
/**
* Holds a sequence of string components, specifying the hierarchical name of a category.
*
* @lucene.internal
*/
public class FacetLabel implements Comparable {
/*
* copied from DocumentWriterPerThread -- if a FacetLabel is resolved to a
* drill-down term which is encoded to a larger term than that length, it is
* silently dropped! Therefore, we limit the number of characters to MAX/4 to
* be on the safe side.
*/
/** The maximum number of characters a {@link FacetLabel} can have. */
public static final int MAX_CATEGORY_PATH_LENGTH = (BYTE_BLOCK_SIZE - 2) / 4;
/**
* The components of this {@link FacetLabel}. Note that this array may be shared with other {@link
* FacetLabel} instances, e.g. as a result of {@link #subpath(int)}, therefore you should traverse
* the array up to {@link #length} for this path's components.
*/
public final String[] components;
/** The number of components of this {@link FacetLabel}. */
public final int length;
// Used by sub-path
private FacetLabel(final FacetLabel copyFrom, final int prefixLen) {
// While the code which calls this method is safe, at some point a test
// tripped on AIOOBE in toString, but we failed to reproduce. Adding
// assert as a safety check.
assert prefixLen >= 0 && prefixLen <= copyFrom.components.length
: "prefixLen cannot be negative nor larger than the given components' length: prefixLen="
+ prefixLen
+ " components.length="
+ copyFrom.components.length;
this.components = copyFrom.components;
length = prefixLen;
}
/** Construct from the given path components. */
public FacetLabel(final String... components) {
this.components = components;
length = components.length;
checkComponents();
}
/** Construct from the dimension plus the given path components. */
public FacetLabel(String dim, String[] path) {
components = new String[1 + path.length];
components[0] = dim;
System.arraycopy(path, 0, components, 1, path.length);
length = components.length;
checkComponents();
}
private void checkComponents() {
long len = 0;
for (String comp : components) {
if (comp == null || comp.isEmpty()) {
throw new IllegalArgumentException(
"empty or null components not allowed: " + Arrays.toString(components));
}
len += comp.length();
}
len += components.length - 1; // add separators
if (len > MAX_CATEGORY_PATH_LENGTH) {
throw new IllegalArgumentException(
"category path exceeds maximum allowed path length: max="
+ MAX_CATEGORY_PATH_LENGTH
+ " len="
+ len
+ " path="
+ Arrays.toString(components).substring(0, 30)
+ "...");
}
}
/** Compares this path with another {@link FacetLabel} for lexicographic order. */
@Override
public int compareTo(FacetLabel other) {
final int len = Math.min(length, other.length);
for (int i = 0, j = 0; i < len; i++, j++) {
int cmp = components[i].compareTo(other.components[j]);
if (cmp < 0) {
return -1; // this is 'before'
}
if (cmp > 0) {
return 1; // this is 'after'
}
}
// one is a prefix of the other
return length - other.length;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof FacetLabel == false) {
return false;
}
FacetLabel other = (FacetLabel) obj;
if (length != other.length) {
return false; // not same length, cannot be equal
}
// CategoryPaths are more likely to differ at the last components, so start
// from last-first
for (int i = length - 1; i >= 0; i--) {
if (!components[i].equals(other.components[i])) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
if (length == 0) {
return 0;
}
int hash = length;
for (int i = 0; i < length; i++) {
hash = hash * 31 + components[i].hashCode();
}
return hash;
}
/**
* Calculate a 64-bit hash function for this path. This is necessary for {@link
* NameHashIntCacheLRU} (the default cache impl for {@link LruTaxonomyWriterCache}) to reduce the
* chance of "silent but deadly" collisions.
*/
public long longHashCode() {
if (length == 0) {
return 0;
}
long hash = length;
for (int i = 0; i < length; i++) {
hash = hash * 65599 + components[i].hashCode();
}
return hash;
}
/** Returns a sub-path of this path up to {@code length} components. */
public FacetLabel subpath(final int length) {
if (length >= this.length || length < 0) {
return this;
} else {
return new FacetLabel(this, length);
}
}
/** Returns a string representation of the path. */
@Override
public String toString() {
if (length == 0) {
return "FacetLabel: []";
}
String[] parts = new String[length];
System.arraycopy(components, 0, parts, 0, length);
return "FacetLabel: " + Arrays.toString(parts);
}
}