| package org.apache.lucene.facet.taxonomy; |
| |
| /* |
| * 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. |
| */ |
| |
| 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); |
| } |
| } |