| package org.apache.lucene.facet.taxonomy.writercache; |
| |
| import java.util.Iterator; |
| import java.util.NoSuchElementException; |
| |
| import org.apache.lucene.facet.taxonomy.FacetLabel; |
| |
| /* |
| * 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. |
| */ |
| |
| /** |
| * 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 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(); |
| } |
| |
| } |
| |
| } |