diff --git a/src/dorkbox/collections/LockFreeIntStringMap.java b/src/dorkbox/collections/LockFreeIntStringMap.java
deleted file mode 100644
index b226698..0000000
--- a/src/dorkbox/collections/LockFreeIntStringMap.java
+++ /dev/null
@@ -1,225 +0,0 @@
-package dorkbox.collections;
-
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-/**
- * 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
- *
- * This is an unordered map that uses int keys. This implementation is a cuckoo hash map using 3 hashes, random walking, and a small stash
- * for problematic keys. Null values are allowed. No allocation is done except when growing the table size.
- *
- * This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
- * depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
- * next higher POT size.
- */
-@SuppressWarnings("unchecked")
-public
-class LockFreeIntStringMap {
- public static final String version = Collections.version;
-
- private static final AtomicReferenceFieldUpdater mapREF = AtomicReferenceFieldUpdater.newUpdater(
- LockFreeIntStringMap.class,
- IntMap.class,
- "map");
-
- private volatile IntMap map;
-
-
- public LockFreeIntStringMap() {
- this.map = new IntMap();
- }
-
- /**
- * Constructs an empty IntMap with the specified initial
- * capacity and the default load factor (0.75).
- *
- * @param initialCapacity the initial capacity.
- *
- * @throws IllegalArgumentException if the initial capacity is negative.
- */
- public
- LockFreeIntStringMap(int initialCapacity) {
- map = new IntMap(initialCapacity);
- }
-
- /**
- * Constructs an empty IntMap with the specified initial
- * capacity and the default load factor (0.75).
- *
- * @throws IllegalArgumentException if the initial capacity is negative.
- */
- public
- LockFreeIntStringMap(LockFreeIntStringMap map) {
- this.map = new IntMap(map.map);
- }
-
- /**
- * Constructs an empty IntMap with the specified initial
- * capacity and load factor.
- *
- * @param initialCapacity the initial capacity
- * @param loadFactor the load factor
- *
- * @throws IllegalArgumentException if the initial capacity is negative
- * or the load factor is nonpositive
- */
- public
- LockFreeIntStringMap(int initialCapacity, float loadFactor) {
- this.map = new IntMap(initialCapacity, loadFactor);
- }
-
-
- public
- int size() {
- // use the SWP to get a lock-free get of the value
- return mapREF.get(this)
- .size;
- }
-
- public
- boolean isEmpty() {
- // use the SWP to get a lock-free get of the value
- return mapREF.get(this)
- .size == 0;
- }
-
- public
- boolean containsKey(final int key) {
- // use the SWP to get a lock-free get of the value
- return mapREF.get(this)
- .containsKey(key);
- }
-
- public
- boolean containsKey(final String key) {
- // use the SWP to get a lock-free get of the value
- return mapREF.get(this)
- .containsKey(key.hashCode());
- }
-
- /**
- * Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may be
- * an expensive operation.
- *
- * @param identity If true, uses == to compare the specified value with values in the map. If false, uses
- * {@link #equals(Object)}.
- */
- public
- boolean containsValue(final Object value, boolean identity) {
- // use the SWP to get a lock-free get of the value
- return mapREF.get(this)
- .containsValue(value, identity);
- }
-
- public
- V get(final int key) {
- // use the SWP to get a lock-free get of the value
- return (V) mapREF.get(this)
- .get(key);
- }
-
- public
- V get(final String key) {
- // use the SWP to get a lock-free get of the value
- return (V) mapREF.get(this)
- .get(key.hashCode());
- }
-
- public synchronized
- V put(final int key, final V value) {
- return map.put(key, value);
- }
-
- public synchronized
- V put(final String key, final V value) {
- return map.put(key.hashCode(), value);
- }
-
- public synchronized
- V remove(final int key) {
- return map.remove(key);
- }
-
- public synchronized
- V remove(final String key) {
- return map.remove(key.hashCode());
- }
-
- public synchronized
- void putAll(final IntMap map) {
- this.map.putAll(map);
- }
-
- /**
- * 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 IntMap.Entries} constructor for nested or multi-threaded iteration.
- */
- public
- IntMap.Keys keys() {
- return mapREF.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 IntMap.Entries} constructor for nested or multi-threaded iteration.
- */
- public
- IntMap.Values values() {
- return mapREF.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 IntMap.Entries} constructor for nested or multi-threaded iteration.
- */
- public
- IntMap.Entries entries() {
- return mapREF.get(this)
- .entries();
- }
-
- public synchronized
- void clear() {
- map.clear();
- }
-
- /**
- * Identity equals only!
- */
- @Override
- public
- boolean equals(final Object o) {
- return this == o;
- }
-
- @Override
- public
- int hashCode() {
- return mapREF.get(this)
- .hashCode();
- }
-
- @Override
- public
- String toString() {
- return mapREF.get(this)
- .toString();
- }
-}
diff --git a/src/dorkbox/collections/LockFreeIntStringMap.kt b/src/dorkbox/collections/LockFreeIntStringMap.kt
new file mode 100644
index 0000000..d05b434
--- /dev/null
+++ b/src/dorkbox/collections/LockFreeIntStringMap.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.*
+
+/**
+ * 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 LockFreeIntStringMap : MutableMap, Cloneable, Serializable {
+
+ @Volatile
+ private var hashMap: IntMap
+
+
+ // 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)
+
+ /**
+ * Constructs an empty HashMap with the default initial capacity
+ * (16) and the default load factor (0.75).
+ */
+ constructor() {
+ hashMap = IntMap()
+ }
+
+ /**
+ * Constructs an empty HashMap with the specified initial
+ * capacity and the default load factor (0.75).
+ *
+ * @param initialCapacity the initial capacity.
+ *
+ * @throws IllegalArgumentException if the initial capacity is negative.
+ */
+ constructor(initialCapacity: Int) {
+ hashMap = IntMap(initialCapacity)
+ }
+
+ /**
+ * Constructs a new HashMap with the same mappings as the
+ * specified Map. The HashMap is created with
+ * default load factor (0.75) and an initial capacity sufficient to
+ * hold the mappings in the specified Map.
+ *
+ * @param map the map whose mappings are to be placed in this map
+ *
+ * @throws NullPointerException if the specified map is null
+ */
+ constructor(map: Map) {
+ hashMap = IntMap(map.size)
+ map.forEach { (index, key) ->
+ hashMap.put(index, key)
+ }
+ }
+
+ constructor(map: LockFreeIntStringMap) {
+ hashMap = IntMap(map.hashMap)
+ }
+
+ /**
+ * Constructs an empty HashMap with the specified initial
+ * capacity and load factor.
+ *
+ * @param initialCapacity the initial capacity
+ * @param loadFactor the load factor
+ *
+ * @throws IllegalArgumentException if the initial capacity is negative
+ * or the load factor is nonpositive
+ */
+ constructor(initialCapacity: Int, loadFactor: Float) {
+ hashMap = IntMap(initialCapacity, loadFactor)
+ }
+
+ val map: IntMap
+ get() {
+ @Suppress("UNCHECKED_CAST")
+ return mapREF[this] as IntMap
+ }
+
+
+ override val size: Int
+ get() {
+ // use the SWP to get a lock-free get of the value
+ return mapREF[this].size
+ }
+
+ override val keys: MutableSet
+ get() {
+ return map.keys()
+ }
+
+ override val values: MutableCollection
+ get() {
+ @Suppress("UNCHECKED_CAST")
+ return map.values() as MutableCollection
+ }
+
+ override val entries: MutableSet>
+ get() {
+ @Suppress("UNCHECKED_CAST")
+ return map.entries() as MutableSet>
+ }
+
+ override fun isEmpty(): Boolean {
+ // use the SWP to get a lock-free get of the value
+ return mapREF[this].isEmpty()
+ }
+
+ override fun containsKey(key: Int): Boolean {
+ // use the SWP to get a lock-free get of the value
+ return mapREF[this].containsKey(key)
+ }
+
+ override fun containsValue(value: String?): Boolean {
+ // use the SWP to get a lock-free get of the value
+ return mapREF[this].containsValue(value)
+ }
+
+ override operator fun get(key: Int): String? {
+ return mapREF[this][key] as String?
+ }
+
+ @Synchronized
+ override fun put(key: Int, value: String?): String? {
+ return hashMap.put(key, value)
+ }
+
+ @Synchronized
+ override fun putAll(from: Map) {
+ hashMap.putAll(from)
+ }
+
+ @Synchronized
+ override fun remove(key: Int): String? {
+ return hashMap.remove(key)
+ }
+
+ @Synchronized
+ fun removeAllValues(value: String) {
+ val iterator = hashMap.entries().iterator()
+ while (iterator.hasNext()) {
+ val value1 = iterator.next()
+ if (value1.value == value) {
+ iterator.remove()
+ }
+ }
+ }
+
+ @Synchronized
+ fun replaceAll(hashMap: IntMap) {
+ this.hashMap.clear()
+ this.hashMap.putAll(hashMap)
+ }
+
+ @Synchronized
+ override fun clear() {
+ hashMap.clear()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return (mapREF[this] == other)
+ }
+
+ override fun hashCode(): Int {
+ return mapREF[this].hashCode()
+ }
+
+ override fun toString(): String {
+ return mapREF[this].toString()
+ }
+
+ // this must be at the end of the file!
+ companion object {
+ const val version = Collections.version
+
+ // Recommended for best performance while adhering to the "single writer principle". Must be static-final
+ private val mapREF = AtomicReferenceFieldUpdater.newUpdater(
+ LockFreeIntStringMap::class.java, IntMap::class.java, "hashMap"
+ )
+ }
+}