/* * 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.writercache; import java.util.Iterator; import java.util.NoSuchElementException; import org.apache.lucene.facet.taxonomy.FacetLabel; /** * HashMap to store colliding labels. See {@link CompactLabelToOrdinal} for * details. * * @lucene.experimental */ public class CollisionMap { private int capacity; private float loadFactor; private int size; private int threshold; static class Entry { int offset; int cid; Entry next; int hash; Entry(int offset, int cid, int h, Entry e) { this.offset = offset; this.cid = cid; this.next = e; this.hash = h; } } private CharBlockArray labelRepository; private Entry[] entries; CollisionMap(CharBlockArray labelRepository) { this(16 * 1024, 0.75f, labelRepository); } CollisionMap(int initialCapacity, CharBlockArray labelRepository) { this(initialCapacity, 0.75f, labelRepository); } private CollisionMap(int initialCapacity, float loadFactor, CharBlockArray labelRepository) { this.labelRepository = labelRepository; this.loadFactor = loadFactor; this.capacity = CompactLabelToOrdinal.determineCapacity(2, initialCapacity); this.entries = new Entry[this.capacity]; this.threshold = (int) (this.capacity * this.loadFactor); } /** How many mappings. */ public int size() { return this.size; } /** How many slots are allocated. */ public int capacity() { return this.capacity; } private void grow() { int newCapacity = this.capacity * 2; Entry[] newEntries = new Entry[newCapacity]; Entry[] src = this.entries; for (int j = 0; j < src.length; j++) { Entry e = src[j]; if (e != null) { src[j] = null; do { Entry next = e.next; int hash = e.hash; int i = indexFor(hash, newCapacity); e.next = newEntries[i]; newEntries[i] = e; e = next; } while (e != null); } } this.capacity = newCapacity; this.entries = newEntries; this.threshold = (int) (this.capacity * this.loadFactor); } /** Return the mapping, or {@link * LabelToOrdinal#INVALID_ORDINAL} if the label isn't * recognized. */ public int get(FacetLabel label, int hash) { int bucketIndex = indexFor(hash, this.capacity); Entry e = this.entries[bucketIndex]; while (e != null && !(hash == e.hash && CategoryPathUtils.equalsToSerialized(label, labelRepository, e.offset))) { e = e.next; } if (e == null) { return LabelToOrdinal.INVALID_ORDINAL; } return e.cid; } /** Add another mapping. */ public int addLabel(FacetLabel label, int hash, int cid) { int bucketIndex = indexFor(hash, this.capacity); for (Entry e = this.entries[bucketIndex]; e != null; e = e.next) { if (e.hash == hash && CategoryPathUtils.equalsToSerialized(label, labelRepository, e.offset)) { return e.cid; } } // new string; add to label repository int offset = labelRepository.length(); CategoryPathUtils.serialize(label, labelRepository); addEntry(offset, cid, hash, bucketIndex); return cid; } /** * This method does not check if the same value is already in the map because * we pass in an char-array offset, so so we now that we're in resize-mode * here. */ public void addLabelOffset(int hash, int offset, int cid) { int bucketIndex = indexFor(hash, this.capacity); addEntry(offset, cid, hash, bucketIndex); } private void addEntry(int offset, int cid, int hash, int bucketIndex) { Entry e = this.entries[bucketIndex]; this.entries[bucketIndex] = new Entry(offset, cid, hash, e); if (this.size++ >= this.threshold) { grow(); } } Iterator<CollisionMap.Entry> entryIterator() { return new EntryIterator(entries, size); } /** * Returns index for hash code h. */ static int indexFor(int h, int length) { return h & (length - 1); } /** * Returns an estimate of the memory usage of this CollisionMap. * @return The approximate number of bytes used by this structure. */ int getMemoryUsage() { int memoryUsage = 0; if (this.entries != null) { for (Entry e : this.entries) { if (e != null) { memoryUsage += (4 * 4); for (Entry ee = e.next; ee != null; ee = ee.next) { memoryUsage += (4 * 4); } } } } return memoryUsage; } private static class EntryIterator implements Iterator<Entry> { Entry next; // next entry to return int index; // current slot Entry[] ents; EntryIterator(Entry[] entries, int size) { this.ents = entries; Entry[] t = entries; int i = t.length; Entry n = null; if (size != 0) { // advance to first entry while (i > 0 && (n = t[--i]) == null) { // advance } } this.next = n; this.index = i; } @Override public boolean hasNext() { return this.next != null; } @Override public Entry next() { Entry e = this.next; if (e == null) throw new NoSuchElementException(); Entry n = e.next; Entry[] t = ents; int i = this.index; while (n == null && i > 0) { n = t[--i]; } this.index = i; this.next = n; return e; } @Override public void remove() { throw new UnsupportedOperationException(); } } }