/* * 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<FacetLabel> { /* * 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 final static 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 subpath 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 the // 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 = length < other.length ? 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)) { 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); } }