diff --git a/collections/src/main/java/space/vectrix/sync/collections/BucketSyncMap.java b/collections/src/main/java/space/vectrix/sync/collections/BucketSyncMap.java index 749f389..d7ccc5c 100644 --- a/collections/src/main/java/space/vectrix/sync/collections/BucketSyncMap.java +++ b/collections/src/main/java/space/vectrix/sync/collections/BucketSyncMap.java @@ -397,6 +397,13 @@ public BucketSyncMap(final int initialCapacity, final float loadFactor) { */ @SuppressWarnings({"rawtypes", "unchecked"}) public BucketSyncMap(final SpreadFunction spreadFunction, final int initialCapacity, final float loadFactor) { + requireNonNull(spreadFunction, "spreadFunction"); + + if(initialCapacity < 0) throw new IllegalArgumentException("Initial capacity must be non-negative"); + if(loadFactor <= 0.0f || !Float.isFinite(loadFactor)) { + throw new IllegalArgumentException("Load factor must be positive and finite"); + } + final int capacity = initialCapacity >= BucketSyncMap.MAXIMUM_CAPACITY ? BucketSyncMap.MAXIMUM_CAPACITY : BucketSyncMap.tableSizeFor(initialCapacity); @@ -532,9 +539,8 @@ public boolean containsKey(final Object key) { } @Override - public V getOrDefault(final Object key, final V defaultValue) { + public @Nullable V getOrDefault(final Object key, final @Nullable V defaultValue) { requireNonNull(key, "key"); - requireNonNull(defaultValue, "defaultValue"); Node@UnknownNullability [] table = this.immutableTable; int length = table.length; @@ -733,7 +739,7 @@ public V getOrDefault(final Object key, final V defaultValue) { long count = 0L; Node[] immutable; - Node@org.jetbrains.annotations.Nullable [] mutable; + Node@org.jetbrains.annotations.Nullable [] mutable = null; int length; Node node; @@ -776,10 +782,12 @@ public V getOrDefault(final Object key, final V defaultValue) { } } - if(!this.amended || (mutable = this.mutableTable) == null) return null; - final int index; - if((node = BucketSyncMap.getNode(mutable, index = (mutable.length - 1) & hash)) != null) { + if(!this.amended || (mutable == null && (mutable = this.mutableTable) == null) || (node = BucketSyncMap.getNode(mutable, index = (mutable.length - 1) & hash)) == null) { + return null; + } else if(node.hash == BucketSyncMap.NODE_MOVED) { + mutable = this.forward((ForwardingNode) node); + } else { synchronized(node) { if(BucketSyncMap.getNodePlain(mutable, index) == node) { for(Node previousNode = null; ; ) { @@ -1219,7 +1227,7 @@ private void amendNode(final int hash, final K key, final ObjectReference refere Object previous; Node[] immutable; - Node@org.jetbrains.annotations.Nullable [] mutable; + Node@org.jetbrains.annotations.Nullable [] mutable = null; int length; Node node; @@ -1257,10 +1265,12 @@ private void amendNode(final int hash, final K key, final ObjectReference refere } } - if(!this.amended || (mutable = this.mutableTable) == null) return null; - final int index; - if((node = BucketSyncMap.getNode(mutable, index = (mutable.length - 1) & hash)) != null) { + if(!this.amended || (mutable == null && (mutable = this.mutableTable) == null) || (node = BucketSyncMap.getNode(mutable, index = (mutable.length - 1) & hash)) == null) { + return null; + } else if(node.hash == BucketSyncMap.NODE_MOVED) { + mutable = this.forward((ForwardingNode) node); + } else { synchronized(node) { if(BucketSyncMap.getNodePlain(mutable, index) == node) { for(Node previousNode = null; ; ) { @@ -1308,7 +1318,7 @@ public boolean remove(final Object key, final Object value) { requireNonNull(value, "value"); Node[] immutable; - Node@org.jetbrains.annotations.Nullable [] mutable; + Node@org.jetbrains.annotations.Nullable [] mutable = null; int length; Node node; @@ -1347,10 +1357,12 @@ public boolean remove(final Object key, final Object value) { } } - if(!this.amended || (mutable = this.mutableTable) == null) return false; - final int index; - if((node = BucketSyncMap.getNode(mutable, index = (mutable.length - 1) & hash)) != null) { + if(!this.amended || (mutable == null && (mutable = this.mutableTable) == null) || (node = BucketSyncMap.getNode(mutable, index = (mutable.length - 1) & hash)) == null) { + return false; + } else if(node.hash == BucketSyncMap.NODE_MOVED) { + mutable = this.forward((ForwardingNode) node); + } else { synchronized(node) { if(BucketSyncMap.getNodePlain(mutable, index) == node) { for(Node previousNode = null; ; ) { @@ -1402,7 +1414,7 @@ public boolean remove(final Object key, final Object value) { Object previous; Node[] immutable; - Node@org.jetbrains.annotations.Nullable [] mutable; + Node@org.jetbrains.annotations.Nullable [] mutable = null; int length; Node node; @@ -1440,10 +1452,12 @@ public boolean remove(final Object key, final Object value) { } } - if(!this.amended || (mutable = this.mutableTable) == null) return null; - final int index; - if((node = BucketSyncMap.getNode(mutable, index = (mutable.length - 1) & hash)) != null) { + if(!this.amended || (mutable == null && (mutable = this.mutableTable) == null) || (node = BucketSyncMap.getNode(mutable, index = (mutable.length - 1) & hash)) == null) { + return null; + } else if(node.hash == BucketSyncMap.NODE_MOVED) { + mutable = this.forward((ForwardingNode) node); + } else { synchronized(node) { if(BucketSyncMap.getNodePlain(mutable, index) == node) { for(; ; ) { @@ -1485,7 +1499,7 @@ public boolean replace(final K key, final V oldValue, final V newValue) { Object previous; Node[] immutable; - Node@org.jetbrains.annotations.Nullable [] mutable; + Node@org.jetbrains.annotations.Nullable [] mutable = null; int length; Node node; @@ -1524,10 +1538,12 @@ public boolean replace(final K key, final V oldValue, final V newValue) { } } - if(!this.amended || (mutable = this.mutableTable) == null) return false; - final int index; - if((node = BucketSyncMap.getNode(mutable, index = (mutable.length - 1) & hash)) != null) { + if(!this.amended || (mutable == null && (mutable = this.mutableTable) == null) || (node = BucketSyncMap.getNode(mutable, index = (mutable.length - 1) & hash)) == null) { + return false; + } else if(node.hash == BucketSyncMap.NODE_MOVED) { + mutable = this.forward((ForwardingNode) node); + } else { synchronized(node) { if(BucketSyncMap.getNodePlain(mutable, index) == node) { for(; ; ) { @@ -1599,7 +1615,7 @@ public void clear() { final ObjectReference reference = node.referencePlain(); Object current = reference.get(); for(; ; ) { - if(current == null || current == BucketSyncMap.EXPUNGED) continue; + if(current == null || current == BucketSyncMap.EXPUNGED) break; final Object witness = reference.compareAndExchange(current, null); if(witness != current) { @@ -1749,6 +1765,7 @@ private void resize() { if(!this.amended || (source = this.mutableTable) == null || (length = source.length) <= 0 + || length >= BucketSyncMap.MAXIMUM_CAPACITY || this.size.sum() < ((long) length * this.loadFactor)) return; operation = StampLock.operation(state); @@ -2319,7 +2336,7 @@ private boolean valueExists() { } @SuppressWarnings("unchecked") - private V valueOr(final V defaultValue) { + private @Nullable V valueOr(final @Nullable V defaultValue) { final Object value; return ((value = ObjectReference.VALUE.getAcquire(this)) != null && value != BucketSyncMap.EXPUNGED) ? (V) value : defaultValue; } @@ -2493,7 +2510,7 @@ public K getKey() { @Override public int hashCode() { - return Objects.hash(this.key, this.value); + return Objects.hashCode(this.key) ^ Objects.hashCode(this.value); } @Override @@ -2506,7 +2523,7 @@ public boolean equals(final @Nullable Object other) { @Override public String toString() { - return "SyncMap.Entry{key=" + this.key + ", value=" + this.value + "}"; + return "BucketSyncMap.Entry{key=" + this.key + ", value=" + this.value + "}"; } } diff --git a/collections/src/test/java/space/vectrix/sync/collections/AbstractMapTest.java b/collections/src/test/java/space/vectrix/sync/collections/AbstractMapTest.java index eab2a29..8cdaec8 100644 --- a/collections/src/test/java/space/vectrix/sync/collections/AbstractMapTest.java +++ b/collections/src/test/java/space/vectrix/sync/collections/AbstractMapTest.java @@ -163,7 +163,6 @@ public void test_getOrDefault_full() { @Test public void test_getOrDefault_nullKey() { final Map map = this.createMap(); - assertThrows(NullPointerException.class, () -> map.getOrDefault(this.key(3), null), "Map should throw exception when given a null default value."); assertThrows(NullPointerException.class, () -> map.getOrDefault(null, this.value(3)), "Map should throw exception when given a null key."); }