blob: 9ebc1c9936ab63710e9c7c86fe7f1ba3a4f9e296 [file] [log] [blame]
using System;
using System.Collections;
using System.Collections.Generic;
namespace Lucene.Net.Facet.Taxonomy.WriterCache
{
/*
* 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.
*/
/// <summary>
/// HashMap to store colliding labels. See <see cref="CompactLabelToOrdinal"/> for
/// details.
///
/// @lucene.experimental
/// </summary>
public class CollisionMap
{
private int capacity;
private readonly float loadFactor;
private int size;
private int threshold;
internal class Entry
{
internal int offset;
internal int cid;
internal Entry next;
internal int hash;
internal Entry(int offset, int cid, int h, Entry e)
{
this.offset = offset;
this.cid = cid;
this.next = e;
this.hash = h;
}
}
private readonly CharBlockArray labelRepository;
private Entry[] entries;
internal CollisionMap(CharBlockArray labelRepository)
: this(16 * 1024, 0.75f, labelRepository)
{
}
internal 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);
}
/// <summary>
/// How many mappings.
/// </summary>
public virtual int Count => this.size;
/// <summary>
/// How many slots are allocated.
/// </summary>
public virtual int Capacity => 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);
}
/// <summary>
/// Return the mapping, or <see cref="LabelToOrdinal.INVALID_ORDINAL"/>
/// if the label isn't recognized.
/// </summary>
public virtual 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;
}
/// <summary>
/// Add another mapping.
/// </summary>
public virtual 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;
}
/// <summary>
/// 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.
/// </summary>
public virtual 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();
}
}
internal virtual IEnumerator<CollisionMap.Entry> GetEnumerator()
{
return new EntryEnumerator(entries, size);
}
/// <summary>
/// Returns index for hash code h.
/// </summary>
internal static int IndexFor(int h, int length)
{
return h & (length - 1);
}
/// <summary>
/// Returns an estimate of the memory usage of this CollisionMap. </summary>
/// <returns> The approximate number of bytes used by this structure. </returns>
internal virtual int GetMemoryUsage()
{
int memoryUsage = 0;
if (this.entries != null)
{
foreach (Entry e in 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 EntryEnumerator : IEnumerator<Entry>
{
internal Entry next; // next entry to return
internal int index; // current slot
internal Entry[] ents;
internal EntryEnumerator(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;
}
private bool HasNext => this.next != null;
public Entry Next()
{
Entry e = this.next;
if (e == null)
{
throw new InvalidOperationException(this.GetType() + " cannot get next entry"); ;
}
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;
}
// LUCENENET specific - .NET doesn't support Remove() anyway, so we can nix this
//public void Remove()
//{
// throw new NotSupportedException();
//}
public void Dispose()
{
}
public bool MoveNext()
{
if (!HasNext)
return false;
Current = Next();
return true;
}
public void Reset()
{
index = 0;
}
public Entry Current { get; private set; }
object IEnumerator.Current => Current;
}
}
}