| 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; |
| } |
| } |
| } |