/* with LHash|QHash|DHash hash Mutable|Updatable mutability true|false concurrentModificationChecked */ /* if !(QHash|DHash hash Mutable mutability) */ /* * Copyright 2014 the original author or authors. * * Licensed 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 com.koloboke.collect.impl.hash; import com.koloboke.collect.hash.HashConfig; import static com.koloboke.collect.impl.Maths.isPowerOf2; import static com.koloboke.collect.impl.hash.LHashCapacities.*; public abstract class MutableLHash extends HashWithoutRemovedSlots implements LHash { /* if LHash hash */ static void verifyConfig(HashConfig config) { /* if impl project */ assert config.getGrowthFactor() == 2.0; /* elif compile project */ if (config.getGrowthFactor() != 2.0) { throw new IllegalArgumentException(config + " passed, HashConfig for a hashtable\n" + "implementation with linear probing must have growthFactor of 2.0.\n" + "A Koloboke Compile-generated hashtable implementation could have\n" + "a different growth factor, if the implemented type is annotated with\n" + "@com.koloboke.compile.hash.algo.openaddressing.QuadraticProbing or\n" + "@com.koloboke.compile.hash.algo.openaddressing.DoubleHashing"); } /* endif */ } /* endif */ //////////////////////////// // Fields private HashConfigWrapper configWrapper; int size; private int maxSize; /* if true concurrentModificationChecked */private int modCount = 0;/* endif */ ///////////////////////////// // Getters @Override public final HashConfigWrapper configWrapper() { return configWrapper; } @Override public final int size() { return size; } @Override public final int modCount() { /* if true concurrentModificationChecked */ return modCount; /* elif false concurrentModificationChecked */ return 0; /* endif */ } /* if true concurrentModificationChecked */ final void incrementModCount() { modCount++; } /* endif */ //////////////////////// // Initialization and construction operations final void copy(LHash hash) { configWrapper = hash.configWrapper(); size = hash.size(); int capacity = hash.capacity(); maxSize = maxSize(capacity); } final void init(HashConfigWrapper configWrapper, int size) { /* if LHash hash */verifyConfig(configWrapper.config());/* endif */ this.configWrapper = configWrapper; this.size = 0; internalInit(targetCapacity(size)); } private void internalInit(int capacity) { /* if LHash hash */assert isPowerOf2(capacity);/* endif */ maxSize = maxSize(capacity); allocateArrays(capacity); } private int maxSize(int capacity) { // No sense in trying to rehash after each insertion // if the capacity is already reached the limit. return !isMaxCapacity(capacity) ? configWrapper.maxSize(capacity) : capacity - 1; } /** * Allocates arrays of {@code capacity} length to hold states, elements, keys or values in. * * <p>Subclasses should override this method, but MUST NOT call it. This method is called * in {@code MutableLHash} from {@link #initForRehash(int)} and * {@link #init(HashConfigWrapper, int)} methods. * * @param capacity length of arrays, comprising the hashtable */ abstract void allocateArrays(int capacity); /** * Moves elements to the new arrays of {@code newCapacity} length. * * <p>This method should be implemented as follows: * * 1. Copy references to the old arrays comprising the hashtable from fields to local variables * * 2. Call {@link #initForRehash(int)} * * 3. Move elements, entries, etc. from the old arrays to the new ones. * * <p>Subclasses should implement, but MUST NOT call this method. This method is called * in {@code MutableLHash} from {@link #postInsertHook()}, {@link #ensureCapacity(long)} * and {@link #shrink()} methods. */ abstract void rehash(int newCapacity); /** * This method just increments modification count (see {@link #modCount()}) * and calls {@link #internalInit(int)}. Should be called by subclasses * in {@link #rehash(int)} implementation. */ final void initForRehash(int newCapacity) { /* if true concurrentModificationChecked */modCount++;/* endif */ internalInit(newCapacity); } ////////////////////////////// // Roots of chain operations /** * Empties the hash. */ @Override public void clear() { /* if true concurrentModificationChecked */modCount++;/* endif */ size = 0; } /* if Mutable mutability */ abstract void removeAt(int index); /* endif */ ///////////////////////////// // Modification hooks and rehash logic @Override public boolean shrink() { int newCapacity = targetCapacity(size); if (newCapacity < capacity()) { rehash(newCapacity); return true; } else { return false; } } private boolean tryRehashForExpansion(int newCapacity) { if (newCapacity > capacity()) { rehash(newCapacity); return true; } else { return false; } } @Override public final boolean ensureCapacity(long minSize) { int intMinSize = (int) Math.min(minSize, (long) Integer.MAX_VALUE); if (minSize < 0L) throw new IllegalArgumentException( "Min size should be positive, " + minSize + " given."); return intMinSize > maxSize && tryRehashForExpansion(targetCapacity(intMinSize)); } /* if Mutable mutability */ final void postRemoveHook() { size--; } /* endif */ final void postInsertHook() { if (++size > maxSize) { /* if LHash hash */ int capacity = capacity(); if (!isMaxCapacity(capacity)) { rehash(capacity << 1); } /* elif !(LHash hash) */ tryRehashForExpansion(grownCapacity()); /* endif */ } } /** * LongLong, LongDouble, DoubleDouble and DoubleLong maps might use array of doubled size * as table to layout keys and values in parallel. They should override this method to return * {@code true}. * * <p>IntInt and smaller maps would better use array of larger primitives ({@code long[]} in * this particular case), because it 1) guarantees that each key and value pair lay on the same * cache line and 2) allows higher max capacity. * * <p>It is MutableLHash's concern in order to treat edge cases of capacities near to Java * array size limit correctly. * * @return if the hash is a map which use double sized array to layout keys and values * in parallel */ boolean doubleSizedArrays() { return false; } private int targetCapacity(int size) { return LHashCapacities.capacity(configWrapper, size, doubleSizedArrays()); } private boolean isMaxCapacity(int capacity) { return LHashCapacities.isMaxCapacity(capacity, doubleSizedArrays()); } /* if !(LHash hash) */ private int grownCapacity() { return nearestGreaterCapacity(configWrapper.grow(capacity()), size, doubleSizedArrays()); } /* endif */ }