From dc45dcf0e5279df168f11671c062a7cd38acea18 Mon Sep 17 00:00:00 2001 From: Robinson Date: Wed, 2 Aug 2023 20:15:40 -0600 Subject: [PATCH] ported LF int bi-map --- src/dorkbox/collections/LockFreeIntBiMap.java | 420 ---------------- src/dorkbox/collections/LockFreeIntBiMap.kt | 446 +++++++++++++++++ .../collections/LockFreeObjectIntBiMap.java | 402 ---------------- .../collections/LockFreeObjectIntBiMap.kt | 454 ++++++++++++++++++ 4 files changed, 900 insertions(+), 822 deletions(-) delete mode 100644 src/dorkbox/collections/LockFreeIntBiMap.java create mode 100644 src/dorkbox/collections/LockFreeIntBiMap.kt delete mode 100644 src/dorkbox/collections/LockFreeObjectIntBiMap.java create mode 100644 src/dorkbox/collections/LockFreeObjectIntBiMap.kt diff --git a/src/dorkbox/collections/LockFreeIntBiMap.java b/src/dorkbox/collections/LockFreeIntBiMap.java deleted file mode 100644 index d27e246..0000000 --- a/src/dorkbox/collections/LockFreeIntBiMap.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright 2018 dorkbox, llc. - * - * 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 dorkbox.collections; - -import static dorkbox.collections.IntMap.Entries; -import static dorkbox.collections.IntMap.Keys; -import static dorkbox.collections.IntMap.Values; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; - -/** - * A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as that of its keys. This constraint - * enables bimaps to support an "inverse view", which is another bimap containing the same entries as this bimap but with reversed keys and values. - * - * This class uses the "single-writer-principle" for lock-free publication. - * - * Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by - * one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only - * one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free - * manner. - * - * According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for - * contended writes. - * - * This data structure is for many-read/few-write scenarios - */ -public -class LockFreeIntBiMap { - public static final String version = Collections.version; - - // Recommended for best performance while adhering to the "single writer principle". Must be static-final - private static final AtomicReferenceFieldUpdater forwardREF = AtomicReferenceFieldUpdater.newUpdater( - LockFreeIntBiMap.class, - IntMap.class, - "forwardHashMap"); - - private static final AtomicReferenceFieldUpdater reverseREF = AtomicReferenceFieldUpdater.newUpdater( - LockFreeIntBiMap.class, - ObjectIntMap.class, - "reverseHashMap"); - - private volatile IntMap forwardHashMap; - private volatile ObjectIntMap reverseHashMap; - - private final int defaultReturnValue; - private final LockFreeObjectIntBiMap inverse; - - // synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this - // section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our - // use-case 99% of the time) - - /** - * Creates a new bimap using @{link Integer#MIN_VALUE}. - */ - public - LockFreeIntBiMap() { - this(Integer.MIN_VALUE); - } - - /** - * The default return value is used for various get/put operations on the IntMap/ObjectIntMap. - * - * @param defaultReturnValue value used for various get/put operations on the IntMap/ObjectIntMap. - */ - public - LockFreeIntBiMap(int defaultReturnValue) { - this(new IntMap(), new ObjectIntMap(), defaultReturnValue); - } - - /** - * The default return value is used for various get/put operations on the IntMap/ObjectIntMap. - * - * @param defaultReturnValue value used for various get/put operations on the IntMap/ObjectIntMap. - */ - public - LockFreeIntBiMap(IntMap forwardHashMap, ObjectIntMap reverseHashMap, int defaultReturnValue) { - this.forwardHashMap = forwardHashMap; - this.reverseHashMap = reverseHashMap; - this.defaultReturnValue = defaultReturnValue; - - this.inverse = new LockFreeObjectIntBiMap(reverseHashMap, forwardHashMap, defaultReturnValue, this); - } - - LockFreeIntBiMap(final IntMap forwardHashMap, - final ObjectIntMap reverseHashMap, - final int defaultReturnValue, - final LockFreeObjectIntBiMap inverse) { - - this.forwardHashMap = forwardHashMap; - this.reverseHashMap = reverseHashMap; - this.defaultReturnValue = defaultReturnValue; - this.inverse = inverse; - } - - /** - * Removes all of the mappings from this bimap. - * The bimap will be empty after this call returns. - */ - public synchronized - void clear() { - forwardHashMap.clear(); - reverseHashMap.clear(); - } - - /** - * @return the inverse view of this bimap, which maps each of this bimap's values to its associated key. - */ - public - LockFreeObjectIntBiMap inverse() { - return inverse; - } - - /** - * Associates the specified value with the specified key in this bimap. - * If the bimap previously contained a mapping for the key, the old - * value is replaced. If the given value is already bound to a different - * key in this bimap, the bimap will remain unmodified. To avoid throwing - * an exception, call {@link #putForce(int, Object)} putForce(K, V) instead. - * - * @param key key with which the specified value is to be associated - * @param value value to be associated with the specified key - * - * @return the previous value associated with key, or - * null if there was no mapping for key. - * (A null return can also indicate that the map - * previously associated null with key.) - * - * @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain - * unmodified in this event. To avoid this exception, call {@link #putForce(int, Object)} putForce(K, V) instead. - */ - public synchronized - V put(final int key, final V value) throws IllegalArgumentException { - V prevForwardValue = this.forwardHashMap.put(key, value); - if (prevForwardValue != null) { - reverseHashMap.remove(prevForwardValue, defaultReturnValue); - } - - int prevReverseValue = this.reverseHashMap.get(value, defaultReturnValue); - this.reverseHashMap.put(value, key); - if (prevReverseValue != defaultReturnValue) { - // put the old value back - if (prevForwardValue != null) { - this.forwardHashMap.put(key, prevForwardValue); - } - else { - this.forwardHashMap.remove(key); - } - - this.reverseHashMap.put(value, prevReverseValue); - - throw new IllegalArgumentException("Value already exists. Keys and values must both be unique!"); - } - - return prevForwardValue; - } - - /** - * Associates the specified value with the specified key in this bimap. - * If the bimap previously contained a mapping for the key, the old - * value is replaced. This is an alternate form of {@link #put(int, Object)} - * that will silently ignore duplicates - * - * @param key key with which the specified value is to be associated - * @param value value to be associated with the specified key - * - * @return the previous value associated with key, or - * null if there was no mapping for key. - * (A null return can also indicate that the map - * previously associated null with key.) - */ - public synchronized - V putForce(final int key, final V value) { - V prevForwardValue = this.forwardHashMap.put(key, value); - if (prevForwardValue != null) { - reverseHashMap.remove(prevForwardValue, defaultReturnValue); - } - - - int prevReverseValue = this.reverseHashMap.get(value, defaultReturnValue); - this.reverseHashMap.put(value, key); - - if (prevReverseValue != defaultReturnValue) { - forwardHashMap.remove(prevReverseValue); - } - - return prevForwardValue; - } - - /** - * Copies all of the mappings from the specified map to this map. - * These mappings will replace any mappings that this map had for - * any of the keys currently in the specified map. - * - * @param hashMap mappings to be stored in this map - * - * @throws NullPointerException if the specified map is null - * - * @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain - * unmodified in this event. To avoid this exception, call {@link #putAllForce(Map)} instead. - */ - public synchronized - void putAll(final Map hashMap) throws IllegalArgumentException { - LockFreeIntBiMap biMap = new LockFreeIntBiMap(); - - try { - for (Map.Entry entry : hashMap.entrySet()) { - Integer key = entry.getKey(); - V value = entry.getValue(); - - biMap.put(key, value); - - // we have to verify that the keys/values between the bimaps are unique - if (this.forwardHashMap.containsKey(key)) { - throw new IllegalArgumentException("Key already exists. Keys and values must both be unique!"); - } - - if (this.reverseHashMap.containsKey(value)) { - throw new IllegalArgumentException("Value already exists. Keys and values must both be unique!"); - } - } - } catch (IllegalArgumentException e) { - // do nothing if there is an exception - throw e; - } - - - // we have checked to make sure that the bimap is unique, AND have checked that we don't already have any of the key/values in ourselves - this.forwardHashMap.putAll(biMap.forwardHashMap); - this.reverseHashMap.putAll(biMap.reverseHashMap); - } - - /** - * Copies all of the mappings from the specified map to this map. - * These mappings will replace any mappings that this map had for - * any of the keys currently in the specified map. This is an alternate - * form of {@link #putAll(Map)} putAll(K, V) that will silently - * ignore duplicates - * - * @param hashMap mappings to be stored in this map - * - * @throws NullPointerException if the specified map is null - */ - public synchronized - void putAllForce(final Map hashMap) { - for (Map.Entry entry : hashMap.entrySet()) { - Integer key = entry.getKey(); - V value = entry.getValue(); - - putForce(key, value); - } - } - - /** - * Removes the mapping for the specified key from this map if present. - * - * @param key key whose mapping is to be removed from the map - * - * @return the previous value associated with key, or - * null if there was no mapping for key. - * (A null return can also indicate that the map - * previously associated null with key.) - */ - public synchronized - V remove(final int key) { - V value = forwardHashMap.remove(key); - if (value != null) { - reverseHashMap.remove(value, defaultReturnValue); - } - return value; - } - - - /** - * Returns the value to which the specified key is mapped, - * or {@code null} if this map contains no mapping for the key. - *

- *

More formally, if this map contains a mapping from a key - * {@code k} to a value {@code v} such that {@code (key==null ? k==null : - * key.equals(k))}, then this method returns {@code v}; otherwise - * it returns {@code null}. (There can be at most one such mapping.) - *

- *

A return value of {@code null} does not necessarily - * indicate that the map contains no mapping for the key; it's also - * possible that the map explicitly maps the key to {@code null}. - * The {@link HashMap#containsKey containsKey} operation may be used to - * distinguish these two cases. - * - * @see #put(int, Object) - */ - @SuppressWarnings("unchecked") - public - V get(final int key) { - // use the SWP to get a lock-free get of the value - return (V) forwardREF.get(this).get(key); - } - - /** - * Returns true if this bimap contains no key-value mappings. - * - * @return true if this bimap contains no key-value mappings - */ - public - boolean isEmpty() { - // use the SWP to get a lock-free get of the value - return forwardREF.get(this) - .size == 0; - } - - /** - * Returns the number of key-value mappings in this map. If the - * map contains more than Integer.MAX_VALUE elements, returns - * Integer.MAX_VALUE. - * - * @return the number of key-value mappings in this map - */ - public - int size() { - // use the SWP to get a lock-free get of the value - return forwardREF.get(this) - .size; - } - - /** - * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility! - * - * Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each - * time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. - */ - public - Keys keys() { - return forwardREF.get(this) - .keys(); - } - - /** - * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility! - * - * Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each - * time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. - */ - @SuppressWarnings("unchecked") - public - Values values() { - return forwardREF.get(this) - .values(); - } - - /** - * DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility! - * - * Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each - * time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. - */ - @SuppressWarnings("unchecked") - public - Entries entries() { - return forwardREF.get(this) - .entries(); - } - - /** - * Identity equals only! - */ - @Override - public - boolean equals(final Object o) { - return this == o; - } - - @Override - public - int hashCode() { - int result = forwardREF.get(this).hashCode(); - result = 31 * result + reverseREF.get(this).hashCode(); - result = 31 * result + defaultReturnValue; - return result; - } - - @Override - public - String toString() { - StringBuilder builder = new StringBuilder("LockFreeIntBiMap {"); - - Keys keys = keys(); - Iterator values = values(); - - while (keys.hasNext) { - builder.append(keys.next()); - builder.append(" (") - .append(values.next()) - .append("), "); - } - - int length = builder.length(); - if (length > 1) { - // delete the ', ' - builder.delete(length - 2, length); - } - - builder.append('}'); - - return builder.toString(); - } -} diff --git a/src/dorkbox/collections/LockFreeIntBiMap.kt b/src/dorkbox/collections/LockFreeIntBiMap.kt new file mode 100644 index 0000000..4a16bb8 --- /dev/null +++ b/src/dorkbox/collections/LockFreeIntBiMap.kt @@ -0,0 +1,446 @@ +/* + * Copyright 2023 dorkbox, llc + * + * 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 dorkbox.collections + +import java.io.Serializable +import java.util.concurrent.atomic.* + +/** + * A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as that of its keys. This constraint + * enables bimaps to support an "inverse view", which is another bimap containing the same entries as this bimap but with reversed keys and values. + * + * This class uses the "single-writer-principle" for lock-free publication. + * + * Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by + * one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only + * one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free + * manner. + * + * According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for + * contended writes. + * + * This data structure is for many-read/few-write scenarios + */ +class LockFreeIntBiMap : MutableMap, Cloneable, Serializable { + private val defaultReturnValue: Int + + @Volatile + private var forwardHashMap: IntMap + + @Volatile + private var reverseHashMap: ObjectIntMap + private val inverse: LockFreeObjectIntBiMap + + // synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this + // section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our + // use-case 99% of the time)s + constructor(): this(Int.MIN_VALUE) + + constructor(defaultReturnValue: Int) { + forwardHashMap = IntMap() + reverseHashMap = ObjectIntMap() + inverse = LockFreeObjectIntBiMap(reverseHashMap, forwardHashMap, this, defaultReturnValue) + this.defaultReturnValue = defaultReturnValue + } + + internal constructor(forwardHashMap: IntMap, reverseHashMap: ObjectIntMap, inverse: LockFreeObjectIntBiMap, defaultReturnValue: Int) { + this.forwardHashMap = forwardHashMap + this.reverseHashMap = reverseHashMap + this.inverse = inverse + this.defaultReturnValue = defaultReturnValue + } + + + override val size: Int + get() = forwardHashMap.size + + /** + * Removes all of the mappings from this bimap. + * The bimap will be empty after this call returns. + */ + @Synchronized + override fun clear() { + forwardHashMap.clear() + reverseHashMap.clear() + } + + override fun containsValue(value: V): Boolean { + // use the SWP to get a lock-free get of the value + return forwardREF[this].containsValue(value) + } + + override fun containsKey(key: Int): Boolean { + // use the SWP to get a lock-free get of the value + return forwardREF[this].containsKey(key) + } + + /** + * @return the inverse view of this bimap, which maps each of this bimap's values to its associated key. + */ + fun inverse(): LockFreeObjectIntBiMap { + return inverse + } + + /** + * Replaces all of the mappings from the specified map to this bimap. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. + * + * @param hashMap mappings to be stored in this map + * + * @throws NullPointerException if the specified map is null + * + * @throws IllegalArgumentException if a given value in the map is already bound to a different key in this bimap. The bimap will remain + * unmodified in this event. To avoid this exception, call [.replaceAllForce] replaceAllForce(map) instead. + */ + @Synchronized + @Throws(IllegalArgumentException::class) + fun replaceAll(hashMap: Map?) { + if (hashMap == null) { + throw NullPointerException("hashMap") + } + val biMap = LockFreeIntBiMap() + try { + biMap.putAll(hashMap) + } + catch (e: IllegalArgumentException) { + // do nothing if there is an exception + throw e + } + + // only if there are no problems with the creation of the new bimap. + forwardHashMap.clear() + reverseHashMap.clear() + forwardHashMap.putAll(biMap.forwardHashMap) + reverseHashMap.putAll(biMap.reverseHashMap) + } + + /** + * Replaces all the mappings from the specified map to this bimap. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. This is an alternate + * form of [.replaceAll] replaceAll(K, V) that will silently + * ignore duplicates + * + * @param hashMap mappings to be stored in this map + * + * @throws NullPointerException if the specified map is null + */ + @Synchronized + fun replaceAllForce(hashMap: Map?) { + if (hashMap == null) { + throw NullPointerException("hashMap") + } + + // only if there are no problems with the creation of the new bimap. + forwardHashMap.clear() + reverseHashMap.clear() + putAllForce(hashMap) + } + + /** + * Associates the specified value with the specified key in this bimap. + * If the bimap previously contained a mapping for the key, the old + * value is replaced. If the given value is already bound to a different + * key in this bimap, the bimap will remain unmodified. To avoid throwing + * an exception, call [.putForce] putForce(K, V) instead. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + * + * @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain + * unmodified in this event. To avoid this exception, call [.putForce] putForce(K, V) instead. + */ + @Synchronized + @Throws(IllegalArgumentException::class) + override fun put(key: Int, value: V): V? { + val prevForwardValue = forwardHashMap.put(key, value) + if (prevForwardValue != null) { + reverseHashMap.remove(prevForwardValue, defaultReturnValue) + } + + val prevReverseValue = reverseHashMap[value, defaultReturnValue]!! + reverseHashMap.put(value, key) + if (prevReverseValue != defaultReturnValue) { + // put the old value back + if (prevForwardValue != null) { + forwardHashMap.put(key, prevForwardValue) + } + else { + forwardHashMap.remove(key) + } + reverseHashMap.put(value, prevReverseValue) + throw java.lang.IllegalArgumentException("Value already exists. Keys and values must both be unique!") + } + + return prevForwardValue + } + + /** + * Associates the specified value with the specified key in this bimap. + * If the bimap previously contained a mapping for the key, the old + * value is replaced. This is an alternate form of [.put] put(K, V) + * that will silently ignore duplicates + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + */ + @Synchronized + fun putForce(key: Int, value: V): V? { + val prevForwardValue = forwardHashMap.put(key, value) + if (prevForwardValue != null) { + reverseHashMap.remove(prevForwardValue, defaultReturnValue) + } + val prevReverseValue = reverseHashMap.get(value, defaultReturnValue); + reverseHashMap.put(value, key) + + if (prevReverseValue != defaultReturnValue) { + forwardHashMap.remove(prevReverseValue) + } + + return prevForwardValue + } + + /** + * Copies all of the mappings from the specified map to this map. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. + * + * @param from mappings to be stored in this map + * + * @throws NullPointerException if the specified map is null + * + * @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain + * unmodified in this event. To avoid this exception, call [.putAllForce] putAllForce(K, V) instead. + */ + @Synchronized + @Throws(IllegalArgumentException::class) + override fun putAll(from: Map) { + val biMap = LockFreeIntBiMap() + try { + for ((key, value) in from) { + biMap.put(key, value) + + // we have to verify that the keys/values between the bimaps are unique + require(!forwardHashMap.containsKey(key)) { "Key already exists. Keys and values must both be unique!" } + require(!reverseHashMap.containsKey(value)) { "Value already exists. Keys and values must both be unique!" } + } + } + catch (e: IllegalArgumentException) { + // do nothing if there is an exception + throw e + } + + // only if there are no problems with the creation of the new bimap AND the uniqueness constrain is guaranteed + forwardHashMap.putAll(biMap.forwardHashMap) + reverseHashMap.putAll(biMap.reverseHashMap) + } + + /** + * Copies all the mappings from the specified map to this map. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. This is an alternate + * form of [.putAll] putAll(K, V) that will silently + * ignore duplicates + * + * @param hashMap mappings to be stored in this map + * + * @throws NullPointerException if the specified map is null + */ + @Synchronized + fun putAllForce(hashMap: Map) { + for ((key, value) in hashMap) { + putForce(key, value) + } + } + + /** + * Removes the mapping for the specified key from this map if present. + * + * @param key key whose mapping is to be removed from the map + * + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + */ + @Synchronized + override fun remove(key: Int): V? { + val value = forwardHashMap.remove(key) + if (value != null) { + reverseHashMap.remove(value, defaultReturnValue) + } + return value + } + + /** + * Returns the value to which the specified key is mapped, + * or `null` if this map contains no mapping for the key. + * + * More formally, if this map contains a mapping from a key + * `k` to a value `v` such that `(key==null ? k==null : + * key.equals(k))`, then this method returns `v`; otherwise + * it returns `null`. (There can be at most one such mapping.) + * + * A return value of `null` does not *necessarily* + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to `null`. + * The [containsKey][HashMap.containsKey] operation may be used to + * distinguish these two cases. + * + * @see .put + */ + override operator fun get(key: Int): V? { + // use the SWP to get a lock-free get of the value + @Suppress("UNCHECKED_CAST") + return forwardREF[this][key] as V? + } + + /** + * Returns the reverse key to which the specified key is mapped, + * or `null` if this map contains no mapping for the key. + * + * More formally, if this map contains a mapping from a key + * `k` to a value `v` such that `(key==null ? k==null : + * key.equals(k))`, then this method returns `v`; otherwise + * it returns `null`. (There can be at most one such mapping.) + * + * A return value of `null` does not *necessarily* + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to `null`. + * The [containsKey][HashMap.containsKey] operation may be used to + * distinguish these two cases. + * + * @see .put + */ + fun getReverse(key: V): Int? { + // use the SWP to get a lock-free get of the value + return reverseREF[this][key] + } + + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation), the results of + * the iteration are undefined. The set supports element removal, + * which removes the corresponding mapping from the map, via the + * Iterator.remove, Set.remove, + * removeAll, retainAll, and clear + * operations. It does not support the add or addAll + * operations. + * + * @return a set view of the keys contained in this map + */ + override val entries: MutableSet> + // use the SWP to get a lock-free get of the value + @Suppress("UNCHECKED_CAST") + get() = forwardREF[this].keys as MutableSet> + + + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation), the results of + * the iteration are undefined. The set supports element removal, + * which removes the corresponding mapping from the map, via the + * Iterator.remove, Set.remove, + * removeAll, retainAll, and clear + * operations. It does not support the add or addAll + * operations. + * + * @return a set view of the keys contained in this map + */ + override val keys: MutableSet + // use the SWP to get a lock-free get of the value + get() = forwardREF[this].keys + + /** + * Returns a [Collection] view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress + * (except through the iterator's own remove operation), + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Collection.remove, removeAll, + * retainAll and clear operations. It does not + * support the add or addAll operations. + * + * @return a view of the values contained in this map + */ + override val values: MutableCollection + // use the SWP to get a lock-free get of the value + @Suppress("UNCHECKED_CAST") + get() = forwardREF[this].values as MutableCollection + + /** + * Returns true if this bimap contains no key-value mappings. + * + * @return true if this bimap contains no key-value mappings + */ + override fun isEmpty(): Boolean { + // use the SWP to get a lock-free get of the value + return forwardREF[this].isEmpty() + } + + /** + * Returns a [Collection] view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress + * (except through the iterator's own remove operation), + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Collection.remove, removeAll, + * retainAll and clear operations. It does not + * support the add or addAll operations. + * + * @return a view of the values contained in this map + */ + val reverseValues: MutableCollection + // use the SWP to get a lock-free get of the value + get() = reverseREF[this].values + + companion object { + const val version = Collections.version + + // Recommended for best performance while adhering to the "single writer principle". Must be static-final + private val forwardREF = AtomicReferenceFieldUpdater.newUpdater( + LockFreeIntBiMap::class.java, IntMap::class.java, "forwardHashMap" + ) + private val reverseREF = AtomicReferenceFieldUpdater.newUpdater( + LockFreeIntBiMap::class.java, ObjectIntMap::class.java, "reverseHashMap" + ) + } +} diff --git a/src/dorkbox/collections/LockFreeObjectIntBiMap.java b/src/dorkbox/collections/LockFreeObjectIntBiMap.java deleted file mode 100644 index ea122f0..0000000 --- a/src/dorkbox/collections/LockFreeObjectIntBiMap.java +++ /dev/null @@ -1,402 +0,0 @@ -/* - * Copyright 2018 dorkbox, llc. - * - * 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 dorkbox.collections; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; - -import dorkbox.collections.IntMap.*; - -/** - * A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as that of its keys. This constraint - * enables bimaps to support an "inverse view", which is another bimap containing the same entries as this bimap but with reversed keys and values. - * - * This class uses the "single-writer-principle" for lock-free publication. - * - * Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by - * one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only - * one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free - * manner. - * - * According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for - * contended writes. - * - * This data structure is for many-read/few-write scenarios - */ -public -class LockFreeObjectIntBiMap { - public static final String version = Collections.version; - - // Recommended for best performance while adhering to the "single writer principle". Must be static-final - private static final AtomicReferenceFieldUpdater forwardREF = AtomicReferenceFieldUpdater.newUpdater( - LockFreeObjectIntBiMap.class, - ObjectIntMap.class, - "forwardHashMap"); - - private static final AtomicReferenceFieldUpdater reverseREF = AtomicReferenceFieldUpdater.newUpdater( - LockFreeObjectIntBiMap.class, - IntMap.class, - "reverseHashMap"); - - private volatile ObjectIntMap forwardHashMap; - private volatile IntMap reverseHashMap; - - private final int defaultReturnValue; - private final LockFreeIntBiMap inverse; - - // synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this - // section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our - // use-case 99% of the time) - - /** - * Creates a new bimap using @{link Integer#MIN_VALUE}. - */ - public - LockFreeObjectIntBiMap() { - this(Integer.MIN_VALUE); - } - - /** - * The default return value is used for various get/put operations on the IntMap/ObjectIntMap. - * - * @param defaultReturnValue value used for various get/put operations on the IntMap/ObjectIntMap. - */ - public - LockFreeObjectIntBiMap(int defaultReturnValue) { - this(new ObjectIntMap(), new IntMap(), defaultReturnValue); - } - - /** - * The default return value is used for various get/put operations on the IntMap/ObjectIntMap. - * - * @param defaultReturnValue value used for various get/put operations on the IntMap/ObjectIntMap. - */ - LockFreeObjectIntBiMap(ObjectIntMap forwardHashMap, IntMap reverseHashMap, int defaultReturnValue) { - this.forwardHashMap = forwardHashMap; - this.reverseHashMap = reverseHashMap; - this.defaultReturnValue = defaultReturnValue; - - this.inverse = new LockFreeIntBiMap(reverseHashMap, forwardHashMap, defaultReturnValue, this); - } - - LockFreeObjectIntBiMap(final ObjectIntMap forwardHashMap, - final IntMap reverseHashMap, - final int defaultReturnValue, - final LockFreeIntBiMap inverse) { - - this.forwardHashMap = forwardHashMap; - this.reverseHashMap = reverseHashMap; - this.defaultReturnValue = defaultReturnValue; - this.inverse = inverse; - } - - /** - * Removes all of the mappings from this bimap. - * - * The bimap will be empty after this call returns. - */ - public synchronized - void clear() { - forwardHashMap.clear(); - reverseHashMap.clear(); - } - - /** - * @return the inverse view of this bimap, which maps each of this bimap's values to its associated key. - */ - public - LockFreeIntBiMap inverse() { - return inverse; - } - - /** - * Associates the specified value with the specified key in this bimap. - * If the bimap previously contained a mapping for the key, the old - * value is replaced. If the given value is already bound to a different - * key in this bimap, the bimap will remain unmodified. To avoid throwing - * an exception, call {@link #putForce(Object, int)} instead. - * - * @param key key with which the specified value is to be associated - * @param value value to be associated with the specified key - * - * @return the previous value associated with key, or - * null if there was no mapping for key. - * (A null return can also indicate that the map - * previously associated null with key.) - * - * @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain - * unmodified in this event. To avoid this exception, call {@link #putForce(Object, int)} instead. - */ - public synchronized - int put(final V key, final int value) throws IllegalArgumentException { - int prevForwardValue = this.forwardHashMap.get(key, defaultReturnValue); - this.forwardHashMap.put(key, value); - if (prevForwardValue != defaultReturnValue) { - reverseHashMap.remove(prevForwardValue); - } - - V prevReverseValue = this.reverseHashMap.put(value, key); - if (prevReverseValue != null) { - // put the old value back - if (prevForwardValue != defaultReturnValue) { - this.forwardHashMap.put(key, prevForwardValue); - } - else { - this.forwardHashMap.remove(key, defaultReturnValue); - } - - this.reverseHashMap.put(value, prevReverseValue); - - throw new IllegalArgumentException("Value already exists. Keys and values must both be unique!"); - } - - return prevForwardValue; - } - - /** - * Associates the specified value with the specified key in this bimap. - * If the bimap previously contained a mapping for the key, the old - * value is replaced. This is an alternate form of {@link #put(Object, int)} - * that will silently ignore duplicates - * - * @param key key with which the specified value is to be associated - * @param value value to be associated with the specified key - * - * @return the previous value associated with key, or - * null if there was no mapping for key. - * (A null return can also indicate that the map - * previously associated null with key.) - */ - public synchronized - int putForce(final V key, final int value) { - int prevForwardValue = this.forwardHashMap.get(key, defaultReturnValue); - this.forwardHashMap.put(key, value); - if (prevForwardValue != defaultReturnValue) { - reverseHashMap.remove(prevForwardValue); - } - - - V prevReverseValue = this.reverseHashMap.get(value); - this.reverseHashMap.put(value, key); - - if (prevReverseValue != null) { - forwardHashMap.remove(prevReverseValue, defaultReturnValue); - } - - return prevForwardValue; - } - - /** - * Copies all of the mappings from the specified map to this map. - * These mappings will replace any mappings that this map had for - * any of the keys currently in the specified map. - * - * @param hashMap mappings to be stored in this map - * - * @throws NullPointerException if the specified map is null - * - * @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain - * unmodified in this event. To avoid this exception, call {@link #putAllForce(Map)} instead. - */ - public synchronized - void putAll(final Map hashMap) throws IllegalArgumentException { - LockFreeObjectIntBiMap biMap = new LockFreeObjectIntBiMap(); - - try { - for (Map.Entry entry : hashMap.entrySet()) { - V key = entry.getKey(); - Integer value = entry.getValue(); - - biMap.put(key, value); - - // we have to verify that the keys/values between the bimaps are unique - if (this.forwardHashMap.containsKey(key)) { - throw new IllegalArgumentException("Key already exists. Keys and values must both be unique!"); - } - - if (this.reverseHashMap.containsKey(value)) { - throw new IllegalArgumentException("Value already exists. Keys and values must both be unique!"); - } - } - } catch (IllegalArgumentException e) { - // do nothing if there is an exception - throw e; - } - - // we have checked to make sure that the bimap is unique, AND have checked that we don't already have any of the key/values in ourselves - this.forwardHashMap.putAll(biMap.forwardHashMap); - this.reverseHashMap.putAll(biMap.reverseHashMap); - } - - /** - * Copies all of the mappings from the specified map to this map. - * These mappings will replace any mappings that this map had for - * any of the keys currently in the specified map. This is an alternate - * form of {@link #putAll(Map)} putAll(K, V) that will silently - * ignore duplicates - * - * @param hashMap mappings to be stored in this map - * - * @throws NullPointerException if the specified map is null - */ - public synchronized - void putAllForce(final Map hashMap) { - for (Map.Entry entry : hashMap.entrySet()) { - V key = entry.getKey(); - Integer value = entry.getValue(); - - putForce(key, value); - } - } - - /** - * Removes the mapping for the specified key from this map if present. - * - * @param key key whose mapping is to be removed from the map - * - * @return the previous value associated with key, or - * defaultReturnValue if there was no mapping for key. - * (A defaultReturnValue return can also indicate that the map - * previously associated defaultReturnValue with key.) - */ - public synchronized - int remove(final V key) { - int value = forwardHashMap.remove(key, defaultReturnValue); - if (value != defaultReturnValue) { - reverseHashMap.remove(value); - } - return value; - } - - - /** - * Returns the value to which the specified key is mapped, - * or {@code defaultReturnValue} if this map contains no mapping for the key. - *

- *

More formally, if this map contains a mapping from a key - * {@code k} to a value {@code v} such that {@code (key==null ? k==null : - * key.equals(k))}, then this method returns {@code v}; otherwise - * it returns {@code defaultReturnValue}. (There can be at most one such mapping.) - *

- *

A return value of {@code defaultReturnValue} does not necessarily - * indicate that the map contains no mapping for the key; it's also - * possible that the map explicitly maps the key to {@code null}. - * The {@link HashMap#containsKey containsKey} operation may be used to - * distinguish these two cases. - * - * @see #put(Object, int) - */ - @SuppressWarnings("unchecked") - public - int get(final V key) { - // use the SWP to get a lock-free get of the value - return forwardREF.get(this).get(key, defaultReturnValue); - } - - /** - * Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each - * time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. - */ - @SuppressWarnings("unchecked") - public - Iterator keys() { - // the ObjectIntMap doesn't have iterators, but the IntMap does - return inverse.values(); - } - - /** - * Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each - * time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. - */ - @SuppressWarnings("unchecked") - public - Keys values() { - // the ObjectIntMap doesn't have iterators, but the IntMap does - return inverse.keys(); - } - - /** - * Returns true if this bimap contains no key-value mappings. - * - * @return true if this bimap contains no key-value mappings - */ - public - boolean isEmpty() { - // use the SWP to get a lock-free get of the value - return forwardREF.get(this) - .size == 0; - } - - /** - * Returns the number of key-value mappings in this map. If the - * map contains more than Integer.MAX_VALUE elements, returns - * Integer.MAX_VALUE. - * - * @return the number of key-value mappings in this map - */ - public - int size() { - // use the SWP to get a lock-free get of the value - return forwardREF.get(this) - .size; - } - - /** - * Identity equals only! - */ - @Override - public - boolean equals(final Object o) { - return this == o; - } - - @Override - public - int hashCode() { - int result = forwardREF.get(this).hashCode(); - result = 31 * result + reverseREF.get(this).hashCode(); - result = 31 * result + defaultReturnValue; - return result; - } - - @Override - public - String toString() { - StringBuilder builder = new StringBuilder("LockFreeObjectIntBiMap {"); - - Iterator keys = keys(); - Keys values = values(); - - while (keys.hasNext()) { - builder.append(keys.next()); - builder.append(" (") - .append(values.next()) - .append("), "); - } - - int length = builder.length(); - if (length > 1) { - // delete the ', ' - builder.delete(length - 2, length); - } - - builder.append('}'); - - return builder.toString(); - } -} diff --git a/src/dorkbox/collections/LockFreeObjectIntBiMap.kt b/src/dorkbox/collections/LockFreeObjectIntBiMap.kt new file mode 100644 index 0000000..7c7eb15 --- /dev/null +++ b/src/dorkbox/collections/LockFreeObjectIntBiMap.kt @@ -0,0 +1,454 @@ +/* + * Copyright 2023 dorkbox, llc + * + * 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 dorkbox.collections + +import java.io.Serializable +import java.util.concurrent.atomic.* + +/** + * A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as that of its keys. This constraint + * enables bimaps to support an "inverse view", which is another bimap containing the same entries as this bimap but with reversed keys and values. + * + * This class uses the "single-writer-principle" for lock-free publication. + * + * Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by + * one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only + * one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free + * manner. + * + * According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for + * contended writes. + * + * This data structure is for many-read/few-write scenarios + */ +class LockFreeObjectIntBiMap : MutableMap, Cloneable, Serializable { + val defaultReturnValue: Int + + @Volatile + private var forwardHashMap: ObjectIntMap + + @Volatile + private var reverseHashMap: IntMap + private val inverse: LockFreeIntBiMap + + // synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this + // section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our + // use-case 99% of the time)s + constructor(): this(Int.MIN_VALUE) + + constructor(defaultReturnValue: Int) { + forwardHashMap = ObjectIntMap() + reverseHashMap = IntMap() + inverse = LockFreeIntBiMap(reverseHashMap, forwardHashMap, this, defaultReturnValue) + this.defaultReturnValue = defaultReturnValue + } + + internal constructor(forwardHashMap: ObjectIntMap, reverseHashMap: IntMap, inverse: LockFreeIntBiMap, defaultReturnValue: Int) { + this.forwardHashMap = forwardHashMap + this.reverseHashMap = reverseHashMap + this.inverse = inverse + this.defaultReturnValue = defaultReturnValue + } + + + override val size: Int + get() = forwardHashMap.size + + /** + * Removes all of the mappings from this bimap. + * The bimap will be empty after this call returns. + */ + @Synchronized + override fun clear() { + forwardHashMap.clear() + reverseHashMap.clear() + } + + override fun containsValue(value: Int): Boolean { + // use the SWP to get a lock-free get of the value + return forwardREF[this].containsValue(value) + } + + override fun containsKey(key: K): Boolean { + // use the SWP to get a lock-free get of the value + @Suppress("UNCHECKED_CAST") + return (forwardREF[this] as ObjectIntMap).containsKey(key) + } + + /** + * @return the inverse view of this bimap, which maps each of this bimap's values to its associated key. + */ + fun inverse(): LockFreeIntBiMap { + return inverse + } + + /** + * Replaces all of the mappings from the specified map to this bimap. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. + * + * @param hashMap mappings to be stored in this map + * + * @throws NullPointerException if the specified map is null + * + * @throws IllegalArgumentException if a given value in the map is already bound to a different key in this bimap. The bimap will remain + * unmodified in this event. To avoid this exception, call [.replaceAllForce] replaceAllForce(map) instead. + */ + @Synchronized + @Throws(IllegalArgumentException::class) + fun replaceAll(hashMap: Map?) { + if (hashMap == null) { + throw NullPointerException("hashMap") + } + val biMap = LockFreeObjectIntBiMap() + try { + biMap.putAll(hashMap) + } + catch (e: IllegalArgumentException) { + // do nothing if there is an exception + throw e + } + + // only if there are no problems with the creation of the new bimap. + forwardHashMap.clear() + reverseHashMap.clear() + forwardHashMap.putAll(biMap.forwardHashMap) + reverseHashMap.putAll(biMap.reverseHashMap) + } + + /** + * Replaces all of the mappings from the specified map to this bimap. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. This is an alternate + * form of [.replaceAll] replaceAll(K, V) that will silently + * ignore duplicates + * + * @param hashMap mappings to be stored in this map + * + * @throws NullPointerException if the specified map is null + */ + @Synchronized + fun replaceAllForce(hashMap: Map?) { + if (hashMap == null) { + throw NullPointerException("hashMap") + } + + // only if there are no problems with the creation of the new bimap. + forwardHashMap.clear() + reverseHashMap.clear() + putAllForce(hashMap) + } + + /** + * Associates the specified value with the specified key in this bimap. + * If the bimap previously contained a mapping for the key, the old + * value is replaced. If the given value is already bound to a different + * key in this bimap, the bimap will remain unmodified. To avoid throwing + * an exception, call [.putForce] putForce(K, V) instead. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + * + * @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain + * unmodified in this event. To avoid this exception, call [.putForce] putForce(K, V) instead. + */ + @Synchronized + @Throws(IllegalArgumentException::class) + override fun put(key: K, value: Int): Int { + val prevForwardValue = forwardHashMap[key, defaultReturnValue]!! + forwardHashMap.put(key, value) + if (prevForwardValue != defaultReturnValue) { + reverseHashMap.remove(prevForwardValue) + } + + val prevReverseValue = reverseHashMap.put(value, key) + if (prevReverseValue != null) { + // put the old value back + if (prevForwardValue != defaultReturnValue) { + forwardHashMap.put(key, prevForwardValue) + } + else { + forwardHashMap.remove(key, defaultReturnValue) + } + reverseHashMap.put(value, prevReverseValue) + throw java.lang.IllegalArgumentException("Value already exists. Keys and values must both be unique!") + } + + return prevForwardValue + } + + /** + * Associates the specified value with the specified key in this bimap. + * If the bimap previously contained a mapping for the key, the old + * value is replaced. This is an alternate form of [.put] put(K, V) + * that will silently ignore duplicates + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + */ + @Synchronized + fun putForce(key: K, value: Int): Int { + val prevForwardValue = forwardHashMap[key, defaultReturnValue]!! + forwardHashMap.put(key, value) + if (prevForwardValue != defaultReturnValue) { + reverseHashMap.remove(prevForwardValue) + } + + + val prevReverseValue = reverseHashMap[value] + reverseHashMap.put(value, key) + + if (prevReverseValue != null) { + forwardHashMap.remove(prevReverseValue, defaultReturnValue) + } + + return prevForwardValue + } + + /** + * Copies all the mappings from the specified map to this map. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. + * + * @param from mappings to be stored in this map + * + * @throws NullPointerException if the specified map is null + * + * @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain + * unmodified in this event. To avoid this exception, call [.putAllForce] putAllForce(K, V) instead. + */ + @Synchronized + @Throws(IllegalArgumentException::class) + override fun putAll(from: Map) { + val biMap = LockFreeObjectIntBiMap() + try { + for ((key, value) in from) { + biMap.put(key, value) + + // we have to verify that the keys/values between the bimaps are unique + require(!forwardHashMap.containsKey(key)) { "Key already exists. Keys and values must both be unique!" } + require(!reverseHashMap.containsKey(value)) { "Value already exists. Keys and values must both be unique!" } + } + } + catch (e: IllegalArgumentException) { + // do nothing if there is an exception + throw e + } + + // only if there are no problems with the creation of the new bimap AND the uniqueness constrain is guaranteed + forwardHashMap.putAll(biMap.forwardHashMap) + reverseHashMap.putAll(biMap.reverseHashMap) + } + + /** + * Copies all the mappings from the specified map to this map. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. This is an alternate + * form of [.putAll] putAll(K, V) that will silently + * ignore duplicates + * + * @param hashMap mappings to be stored in this map + * + * @throws NullPointerException if the specified map is null + */ + @Synchronized + fun putAllForce(hashMap: Map) { + for ((key, value) in hashMap) { + putForce(key, value) + } + } + + /** + * Removes the mapping for the specified key from this map if present. + * + * @param key key whose mapping is to be removed from the map + * + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + */ + @Synchronized + override fun remove(key: K): Int? { + val value = forwardHashMap.remove(key) + reverseHashMap.remove(value) + return value + } + + /** + * Returns the value to which the specified key is mapped, + * or `null` if this map contains no mapping for the key. + * + * + * More formally, if this map contains a mapping from a key + * `k` to a value `v` such that `(key==null ? k==null : + * key.equals(k))`, then this method returns `v`; otherwise + * it returns `null`. (There can be at most one such mapping.) + * + * + * A return value of `null` does not *necessarily* + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to `null`. + * The [containsKey][HashMap.containsKey] operation may be used to + * distinguish these two cases. + * + * @see .put + */ + override operator fun get(key: K): Int { + // use the SWP to get a lock-free get of the value + @Suppress("UNCHECKED_CAST") + return (forwardREF[this] as ObjectIntMap)[key] as Int + } + + /** + * Returns the reverse key to which the specified key is mapped, + * or `null` if this map contains no mapping for the key. + * + * + * More formally, if this map contains a mapping from a key + * `k` to a value `v` such that `(key==null ? k==null : + * key.equals(k))`, then this method returns `v`; otherwise + * it returns `null`. (There can be at most one such mapping.) + * + * + * A return value of `null` does not *necessarily* + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to `null`. + * The [containsKey][HashMap.containsKey] operation may be used to + * distinguish these two cases. + * + * @see .put + */ + fun getReverse(key: Int): K? { + // use the SWP to get a lock-free get of the value + @Suppress("UNCHECKED_CAST") + return reverseREF[this][key] as K? + } + + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation), the results of + * the iteration are undefined. The set supports element removal, + * which removes the corresponding mapping from the map, via the + * Iterator.remove, Set.remove, + * removeAll, retainAll, and clear + * operations. It does not support the add or addAll + * operations. + * + * @return a set view of the keys contained in this map + */ + override val entries: MutableSet> + // use the SWP to get a lock-free get of the value + @Suppress("UNCHECKED_CAST") + get() = forwardREF[this].keys() as MutableSet> + + + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation), the results of + * the iteration are undefined. The set supports element removal, + * which removes the corresponding mapping from the map, via the + * Iterator.remove, Set.remove, + * removeAll, retainAll, and clear + * operations. It does not support the add or addAll + * operations. + * + * @return a set view of the keys contained in this map + */ + override val keys: MutableSet + // use the SWP to get a lock-free get of the value + @Suppress("UNCHECKED_CAST") + get() = forwardREF[this].keys as MutableSet + + /** + * Returns a [Collection] view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress + * (except through the iterator's own remove operation), + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Collection.remove, removeAll, + * retainAll and clear operations. It does not + * support the add or addAll operations. + * + * @return a view of the values contained in this map + */ + override val values: MutableCollection + // use the SWP to get a lock-free get of the value + get() = forwardREF[this].values + + /** + * Returns true if this bimap contains no key-value mappings. + * + * @return true if this bimap contains no key-value mappings + */ + override fun isEmpty(): Boolean { + // use the SWP to get a lock-free get of the value + return forwardREF[this].isEmpty() + } + + /** + * Returns a [Collection] view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress + * (except through the iterator's own remove operation), + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Collection.remove, removeAll, + * retainAll and clear operations. It does not + * support the add or addAll operations. + * + * @return a view of the values contained in this map + */ + val reverseValues: MutableCollection + // use the SWP to get a lock-free get of the value + @Suppress("UNCHECKED_CAST") + get() = reverseREF[this].values as MutableCollection + + companion object { + const val version = Collections.version + + // Recommended for best performance while adhering to the "single writer principle". Must be static-final + private val forwardREF = AtomicReferenceFieldUpdater.newUpdater( + LockFreeObjectIntBiMap::class.java, ObjectIntMap::class.java, "forwardHashMap" + ) + private val reverseREF = AtomicReferenceFieldUpdater.newUpdater( + LockFreeObjectIntBiMap::class.java, IntMap::class.java, "reverseHashMap" + ) + } +}