blob: 28cc8eeaa3abc37315201ae577407cf81a8848fd [file] [log] [blame]
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();
}
}
}