From bc1d644fa25e04fdb067c4226648aebd459a93f3 Mon Sep 17 00:00:00 2001 From: John DeRegnaucourt Date: Sun, 28 Apr 2024 22:13:27 -0400 Subject: [PATCH] - Added the SealableXXX collection classes. - Strengthened the implementation of ConcurrentList and ConcurrentHashSet. - Renamed ConcurrentHashSet to ConcurrentSet --- .../util/CaseInsensitiveMap.java | 6 +- .../util/CaseInsensitiveSet.java | 24 ++-- .../cedarsoftware/util/ConcurrentHashSet.java | 79 ----------- .../cedarsoftware/util/ConcurrentList.java | 119 +++++----------- .../com/cedarsoftware/util/ConcurrentSet.java | 47 +++++++ .../com/cedarsoftware/util/SealableList.java | 1 + .../com/cedarsoftware/util/SealableMap.java | 1 + .../util/SealableNavigableMap.java | 79 +++++------ .../util/SealableNavigableSet.java | 69 +++++----- .../com/cedarsoftware/util/SealableSet.java | 5 +- .../util/ConcurrentList2Test.java | 127 ++++++++++++++++++ .../util/ConcurrentListTest.java | 5 +- ...ashSetTest.java => ConcurrentSetTest.java} | 16 +-- .../cedarsoftware/util/SealableListTest.java | 3 +- 14 files changed, 318 insertions(+), 263 deletions(-) delete mode 100644 src/main/java/com/cedarsoftware/util/ConcurrentHashSet.java create mode 100644 src/main/java/com/cedarsoftware/util/ConcurrentSet.java create mode 100644 src/test/java/com/cedarsoftware/util/ConcurrentList2Test.java rename src/test/java/com/cedarsoftware/util/{ConcurrentHashSetTest.java => ConcurrentSetTest.java} (89%) diff --git a/src/main/java/com/cedarsoftware/util/CaseInsensitiveMap.java b/src/main/java/com/cedarsoftware/util/CaseInsensitiveMap.java index 26336dcb..6b37d404 100644 --- a/src/main/java/com/cedarsoftware/util/CaseInsensitiveMap.java +++ b/src/main/java/com/cedarsoftware/util/CaseInsensitiveMap.java @@ -28,10 +28,10 @@ * entrySet() APIs return the original Strings, not the internally * wrapped CaseInsensitiveString. * - * As an added benefit, .keySet() returns a case-insenstive + * As an added benefit, .keySet() returns a case-insensitive * Set, however, again, the contents of the entries are actual Strings. * Similarly, .entrySet() returns a case-insensitive entry set, such that - * .getKey() on the entry is case insensitive when compared, but the + * .getKey() on the entry is case-insensitive when compared, but the * returned key is a String. * * @author John DeRegnaucourt (jdereg@gmail.com) @@ -314,11 +314,13 @@ public Collection values() return map.values(); } + @Deprecated public Map minus(Object removeMe) { throw new UnsupportedOperationException("Unsupported operation [minus] or [-] between Maps. Use removeAll() or retainAll() instead."); } + @Deprecated public Map plus(Object right) { throw new UnsupportedOperationException("Unsupported operation [plus] or [+] between Maps. Use putAll() instead."); diff --git a/src/main/java/com/cedarsoftware/util/CaseInsensitiveSet.java b/src/main/java/com/cedarsoftware/util/CaseInsensitiveSet.java index 23361212..56731ca9 100644 --- a/src/main/java/com/cedarsoftware/util/CaseInsensitiveSet.java +++ b/src/main/java/com/cedarsoftware/util/CaseInsensitiveSet.java @@ -42,7 +42,7 @@ public class CaseInsensitiveSet implements Set public CaseInsensitiveSet(Collection collection) { - if (collection instanceof ConcurrentSkipListSet || collection instanceof ConcurrentHashSet) + if (collection instanceof ConcurrentSkipListSet || collection instanceof ConcurrentSet) { map = new CaseInsensitiveMap<>(new ConcurrentSkipListMap<>()); } @@ -98,7 +98,7 @@ public boolean equals(Object other) if (other == this) return true; if (!(other instanceof Set)) return false; - Set that = (Set) other; + Set that = (Set) other; return that.size()==size() && containsAll(that); } @@ -176,7 +176,7 @@ public boolean retainAll(Collection c) other.put(o, null); } - Iterator i = map.keySet().iterator(); + Iterator i = map.keySet().iterator(); int size = map.size(); while (i.hasNext()) { @@ -204,7 +204,8 @@ public void clear() map.clear(); } - public Set minus(Iterable removeMe) + @Deprecated + public Set minus(Iterable removeMe) { for (Object me : removeMe) { @@ -213,22 +214,25 @@ public Set minus(Iterable removeMe) return this; } - public Set minus(Object removeMe) + @Deprecated + public Set minus(E removeMe) { remove(removeMe); return this; } - - public Set plus(Iterable right) + + @Deprecated + public Set plus(Iterable right) { - for (Object item : right) + for (E item : right) { - add((E)item); + add(item); } return this; } - public Set plus(Object right) + @Deprecated + public Set plus(Object right) { add((E)right); return this; diff --git a/src/main/java/com/cedarsoftware/util/ConcurrentHashSet.java b/src/main/java/com/cedarsoftware/util/ConcurrentHashSet.java deleted file mode 100644 index 2580ed83..00000000 --- a/src/main/java/com/cedarsoftware/util/ConcurrentHashSet.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.cedarsoftware.util; - -import java.util.Collection; -import java.util.Iterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * @author John DeRegnaucourt (jdereg@gmail.com) - *
- * Copyright (c) Cedar Software 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 - *

- * License - *

- * 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. - */ -public class ConcurrentHashSet implements Set { - private final Set set = ConcurrentHashMap.newKeySet(); - - public boolean add(T e) { - return set.add(e); - } - - public boolean remove(Object o) { - return set.remove(o); - } - - public boolean containsAll(Collection c) { - return set.containsAll(c); - } - - public boolean addAll(Collection c) { - return set.addAll(c); - } - - public boolean retainAll(Collection c) { - return set.retainAll(c); - } - - public boolean removeAll(Collection c) { - return set.removeAll(c); - } - - public void clear() { - set.clear(); - } - - public boolean contains(Object o) { - return set.contains(o); - } - - public boolean isEmpty() { - return set.isEmpty(); - } - - public Iterator iterator() { - return set.iterator(); - } - - public int size() { - return set.size(); - } - - public Object[] toArray() { - return set.toArray(); - } - - public T1[] toArray(T1[] a) { - return set.toArray(a); - } -} diff --git a/src/main/java/com/cedarsoftware/util/ConcurrentList.java b/src/main/java/com/cedarsoftware/util/ConcurrentList.java index cbf4ba11..fd4eb426 100644 --- a/src/main/java/com/cedarsoftware/util/ConcurrentList.java +++ b/src/main/java/com/cedarsoftware/util/ConcurrentList.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; @@ -9,10 +10,15 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; /** - * ConcurrentList provides a List or List wrapper that is thread-safe, usable in highly concurrent + * ConcurrentList provides a List and List wrapper that is thread-safe, usable in highly concurrent * environments. It provides a no-arg constructor that will directly return a ConcurrentList that is * thread-safe. It has a constructor that takes a List argument, which will wrap that List and make it - * thread-safe (no elements are duplicated). + * thread-safe (no elements are duplicated).
+ *
+ * The iterator(), listIterator() return read-only views copied from the list. The listIterator(index) + * is not implemented, as the inbound index could already be outside the lists position due to concurrent + * edits. Similarly, subList(from, to) is not implemented because the boundaries may exceed the lists + * size due to concurrent edits. *

* @author John DeRegnaucourt (jdereg@gmail.com) *
@@ -32,12 +38,13 @@ */ public class ConcurrentList implements List { private final List list; - private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final ReadWriteLock lock; /** * Use this no-arg constructor to create a ConcurrentList. */ public ConcurrentList() { + lock = new ReentrantReadWriteLock(); this.list = new ArrayList<>(); } @@ -50,6 +57,7 @@ public ConcurrentList(List list) { if (list == null) { throw new IllegalArgumentException("list cannot be null"); } + lock = new ReentrantReadWriteLock(); this.list = list; } @@ -89,6 +97,15 @@ public int hashCode() { } } + public String toString() { + lock.readLock().lock(); + try { + return list.toString(); + } finally { + lock.readLock().unlock(); + } + } + public boolean contains(Object o) { lock.readLock().lock(); try { @@ -101,7 +118,7 @@ public boolean contains(Object o) { public Iterator iterator() { lock.readLock().lock(); try { - return new ArrayList<>(list).iterator(); // Create a snapshot for iterator + return new ArrayList<>(list).iterator(); } finally { lock.readLock().unlock(); } @@ -146,7 +163,7 @@ public boolean remove(Object o) { public boolean containsAll(Collection c) { lock.readLock().lock(); try { - return list.containsAll(c); + return new HashSet<>(list).containsAll(c); } finally { lock.readLock().unlock(); } @@ -251,90 +268,20 @@ public int lastIndexOf(Object o) { } } - public List subList(int fromIndex, int toIndex) { return new ConcurrentList<>(list.subList(fromIndex, toIndex)); } + public List subList(int fromIndex, int toIndex) { + throw new UnsupportedOperationException("subList not implemented for ConcurrentList"); + } public ListIterator listIterator() { - return createLockHonoringListIterator(list.listIterator()); + lock.readLock().lock(); + try { + return new ArrayList(list).listIterator(); + } finally { + lock.readLock().unlock(); + } } public ListIterator listIterator(int index) { - return createLockHonoringListIterator(list.listIterator(index)); - } - - private ListIterator createLockHonoringListIterator(ListIterator iterator) { - return new ListIterator() { - public boolean hasNext() { - lock.readLock().lock(); - try { - return iterator.hasNext(); - } finally { - lock.readLock().unlock(); - } - } - public E next() { - lock.readLock().lock(); - try { - return iterator.next(); - } finally { - lock.readLock().unlock(); - } - } - public boolean hasPrevious() { - lock.readLock().lock(); - try { - return iterator.hasPrevious(); - } finally { - lock.readLock().unlock(); - } - } - public E previous() { - lock.readLock().lock(); - try { - return iterator.previous(); - } finally { - lock.readLock().unlock(); - } - } - public int nextIndex() { - lock.readLock().lock(); - try { - return iterator.nextIndex(); - } finally { - lock.readLock().unlock(); - } - } - public int previousIndex() { - lock.readLock().lock(); - try { - return iterator.previousIndex(); - } finally { - lock.readLock().unlock(); - } - } - public void remove() { - lock.writeLock().lock(); - try { - iterator.remove(); - } finally { - lock.writeLock().unlock(); - } - } - public void set(E e) { - lock.writeLock().lock(); - try { - iterator.set(e); - } finally { - lock.writeLock().unlock(); - } - } - public void add(E e) { - lock.writeLock().lock(); - try { - iterator.add(e); - } finally { - lock.writeLock().unlock(); - } - } - }; + throw new UnsupportedOperationException("listIterator(index) not implemented for ConcurrentList"); } -} +} \ No newline at end of file diff --git a/src/main/java/com/cedarsoftware/util/ConcurrentSet.java b/src/main/java/com/cedarsoftware/util/ConcurrentSet.java new file mode 100644 index 00000000..1975a4eb --- /dev/null +++ b/src/main/java/com/cedarsoftware/util/ConcurrentSet.java @@ -0,0 +1,47 @@ +package com.cedarsoftware.util; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author John DeRegnaucourt (jdereg@gmail.com) + *
+ * Copyright (c) Cedar Software 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 + *

+ * License + *

+ * 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. + */ +public class ConcurrentSet implements Set { + private final Set set = ConcurrentHashMap.newKeySet(); + + // Immutable APIs + public boolean equals(Object other) { return set.equals(other); } + public int hashCode() { return set.hashCode(); } + public String toString() { return set.toString(); } + public boolean isEmpty() { return set.isEmpty(); } + public int size() { return set.size(); } + public boolean contains(Object o) { return set.contains(o); } + public boolean containsAll(Collection c) { return set.containsAll(c); } + public Iterator iterator() { return set.iterator(); } + public Object[] toArray() { return set.toArray(); } + public T1[] toArray(T1[] a) { return set.toArray(a); } + + // Mutable APIs + public boolean add(T e) {return set.add(e);} + public boolean addAll(Collection c) { return set.addAll(c); } + public boolean remove(Object o) { return set.remove(o); } + public boolean removeAll(Collection c) { return set.removeAll(c); } + public boolean retainAll(Collection c) { return set.retainAll(c); } + public void clear() { set.clear(); } +} diff --git a/src/main/java/com/cedarsoftware/util/SealableList.java b/src/main/java/com/cedarsoftware/util/SealableList.java index 473a9c20..aee98161 100644 --- a/src/main/java/com/cedarsoftware/util/SealableList.java +++ b/src/main/java/com/cedarsoftware/util/SealableList.java @@ -80,6 +80,7 @@ private void throwIfSealed() { // Immutable APIs public boolean equals(Object other) { return list.equals(other); } public int hashCode() { return list.hashCode(); } + public String toString() { return list.toString(); } public int size() { return list.size(); } public boolean isEmpty() { return list.isEmpty(); } public boolean contains(Object o) { return list.contains(o); } diff --git a/src/main/java/com/cedarsoftware/util/SealableMap.java b/src/main/java/com/cedarsoftware/util/SealableMap.java index c874babc..b0a62fef 100644 --- a/src/main/java/com/cedarsoftware/util/SealableMap.java +++ b/src/main/java/com/cedarsoftware/util/SealableMap.java @@ -66,6 +66,7 @@ private void throwIfSealed() { // Immutable public boolean equals(Object obj) { return map.equals(obj); } public int hashCode() { return map.hashCode(); } + public String toString() { return map.toString(); } public int size() { return map.size(); } public boolean isEmpty() { return map.isEmpty(); } public boolean containsKey(Object key) { return map.containsKey(key); } diff --git a/src/main/java/com/cedarsoftware/util/SealableNavigableMap.java b/src/main/java/com/cedarsoftware/util/SealableNavigableMap.java index 71641925..cc7b8eb3 100644 --- a/src/main/java/com/cedarsoftware/util/SealableNavigableMap.java +++ b/src/main/java/com/cedarsoftware/util/SealableNavigableMap.java @@ -36,7 +36,7 @@ * limitations under the License. */ public class SealableNavigableMap implements NavigableMap { - private final NavigableMap map; + private final NavigableMap navMap; private final Supplier sealedSupplier; /** @@ -47,7 +47,7 @@ public class SealableNavigableMap implements NavigableMap { */ public SealableNavigableMap(Supplier sealedSupplier) { this.sealedSupplier = sealedSupplier; - this.map = new ConcurrentSkipListMap<>(); + this.navMap = new ConcurrentSkipListMap<>(); } /** @@ -59,7 +59,7 @@ public SealableNavigableMap(Supplier sealedSupplier) { */ public SealableNavigableMap(SortedMap map, Supplier sealedSupplier) { this.sealedSupplier = sealedSupplier; - this.map = new ConcurrentSkipListMap<>(map); + this.navMap = new ConcurrentSkipListMap<>(map); } /** @@ -71,7 +71,7 @@ public SealableNavigableMap(SortedMap map, Supplier sealedSupplie */ public SealableNavigableMap(NavigableMap map, Supplier sealedSupplier) { this.sealedSupplier = sealedSupplier; - this.map = map; + this.navMap = map; } private void throwIfSealed() { @@ -81,50 +81,51 @@ private void throwIfSealed() { } // Immutable APIs - public boolean equals(Object o) { return map.equals(o); } - public int hashCode() { return map.hashCode(); } - public boolean isEmpty() { return map.isEmpty(); } - public boolean containsKey(Object key) { return map.containsKey(key); } - public boolean containsValue(Object value) { return map.containsValue(value); } - public int size() { return map.size(); } - public V get(Object key) { return map.get(key); } - public Comparator comparator() { return map.comparator(); } - public K firstKey() { return map.firstKey(); } - public K lastKey() { return map.lastKey(); } - public Set keySet() { return new SealableSet<>(map.keySet(), sealedSupplier); } - public Collection values() { return new SealableList<>(new ArrayList<>(map.values()), sealedSupplier); } - public Set> entrySet() { return new SealableSet<>(map.entrySet(), sealedSupplier); } - public Map.Entry lowerEntry(K key) { return map.lowerEntry(key); } - public K lowerKey(K key) { return map.lowerKey(key); } - public Map.Entry floorEntry(K key) { return map.floorEntry(key); } - public K floorKey(K key) { return map.floorKey(key); } - public Map.Entry ceilingEntry(K key) { return map.ceilingEntry(key); } - public K ceilingKey(K key) { return map.ceilingKey(key); } - public Map.Entry higherEntry(K key) { return map.higherEntry(key); } - public K higherKey(K key) { return map.higherKey(key); } - public Map.Entry firstEntry() { return map.firstEntry(); } - public Map.Entry lastEntry() { return map.lastEntry(); } - public NavigableMap descendingMap() { return new SealableNavigableMap<>(map.descendingMap(), sealedSupplier); } - public NavigableSet navigableKeySet() { return new SealableNavigableSet<>(map.navigableKeySet(), sealedSupplier); } - public NavigableSet descendingKeySet() { return new SealableNavigableSet<>(map.descendingKeySet(), sealedSupplier); } + public boolean equals(Object o) { return navMap.equals(o); } + public int hashCode() { return navMap.hashCode(); } + public String toString() { return navMap.toString(); } + public boolean isEmpty() { return navMap.isEmpty(); } + public boolean containsKey(Object key) { return navMap.containsKey(key); } + public boolean containsValue(Object value) { return navMap.containsValue(value); } + public int size() { return navMap.size(); } + public V get(Object key) { return navMap.get(key); } + public Comparator comparator() { return navMap.comparator(); } + public K firstKey() { return navMap.firstKey(); } + public K lastKey() { return navMap.lastKey(); } + public Set keySet() { return new SealableSet<>(navMap.keySet(), sealedSupplier); } + public Collection values() { return new SealableList<>(new ArrayList<>(navMap.values()), sealedSupplier); } + public Set> entrySet() { return new SealableSet<>(navMap.entrySet(), sealedSupplier); } + public Map.Entry lowerEntry(K key) { return navMap.lowerEntry(key); } + public K lowerKey(K key) { return navMap.lowerKey(key); } + public Map.Entry floorEntry(K key) { return navMap.floorEntry(key); } + public K floorKey(K key) { return navMap.floorKey(key); } + public Map.Entry ceilingEntry(K key) { return navMap.ceilingEntry(key); } + public K ceilingKey(K key) { return navMap.ceilingKey(key); } + public Map.Entry higherEntry(K key) { return navMap.higherEntry(key); } + public K higherKey(K key) { return navMap.higherKey(key); } + public Map.Entry firstEntry() { return navMap.firstEntry(); } + public Map.Entry lastEntry() { return navMap.lastEntry(); } + public NavigableMap descendingMap() { return new SealableNavigableMap<>(navMap.descendingMap(), sealedSupplier); } + public NavigableSet navigableKeySet() { return new SealableNavigableSet<>(navMap.navigableKeySet(), sealedSupplier); } + public NavigableSet descendingKeySet() { return new SealableNavigableSet<>(navMap.descendingKeySet(), sealedSupplier); } public SortedMap subMap(K fromKey, K toKey) { return subMap(fromKey, true, toKey, false); } public NavigableMap subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { - return new SealableNavigableMap<>(map.subMap(fromKey, fromInclusive, toKey, toInclusive), sealedSupplier); + return new SealableNavigableMap<>(navMap.subMap(fromKey, fromInclusive, toKey, toInclusive), sealedSupplier); } public SortedMap headMap(K toKey) { return headMap(toKey, false); } public NavigableMap headMap(K toKey, boolean inclusive) { - return new SealableNavigableMap<>(map.headMap(toKey, inclusive), sealedSupplier); + return new SealableNavigableMap<>(navMap.headMap(toKey, inclusive), sealedSupplier); } public SortedMap tailMap(K fromKey) { return tailMap(fromKey, true); } public NavigableMap tailMap(K fromKey, boolean inclusive) { - return new SealableNavigableMap<>(map.tailMap(fromKey, inclusive), sealedSupplier); + return new SealableNavigableMap<>(navMap.tailMap(fromKey, inclusive), sealedSupplier); } // Mutable APIs - public Map.Entry pollFirstEntry() { throwIfSealed(); return map.pollFirstEntry(); } - public Map.Entry pollLastEntry() { throwIfSealed(); return map.pollLastEntry(); } - public V put(K key, V value) { throwIfSealed(); return map.put(key, value); } - public V remove(Object key) { throwIfSealed(); return map.remove(key); } - public void putAll(Map m) { throwIfSealed(); map.putAll(m); } - public void clear() { throwIfSealed(); map.clear(); } + public Map.Entry pollFirstEntry() { throwIfSealed(); return navMap.pollFirstEntry(); } + public Map.Entry pollLastEntry() { throwIfSealed(); return navMap.pollLastEntry(); } + public V put(K key, V value) { throwIfSealed(); return navMap.put(key, value); } + public V remove(Object key) { throwIfSealed(); return navMap.remove(key); } + public void putAll(Map m) { throwIfSealed(); navMap.putAll(m); } + public void clear() { throwIfSealed(); navMap.clear(); } } \ No newline at end of file diff --git a/src/main/java/com/cedarsoftware/util/SealableNavigableSet.java b/src/main/java/com/cedarsoftware/util/SealableNavigableSet.java index 16219d26..13af86c0 100644 --- a/src/main/java/com/cedarsoftware/util/SealableNavigableSet.java +++ b/src/main/java/com/cedarsoftware/util/SealableNavigableSet.java @@ -35,7 +35,7 @@ * limitations under the License. */ public class SealableNavigableSet implements NavigableSet { - private final NavigableSet navigableSet; + private final NavigableSet navSet; private final Supplier sealedSupplier; /** @@ -46,7 +46,7 @@ public class SealableNavigableSet implements NavigableSet { */ public SealableNavigableSet(Supplier sealedSupplier) { this.sealedSupplier = sealedSupplier; - navigableSet = new ConcurrentSkipListSet<>(); + navSet = new ConcurrentSkipListSet<>(); } /** @@ -59,7 +59,7 @@ public SealableNavigableSet(Supplier sealedSupplier) { */ public SealableNavigableSet(Comparator comparator, Supplier sealedSupplier) { this.sealedSupplier = sealedSupplier; - navigableSet = new ConcurrentSkipListSet<>(comparator); + navSet = new ConcurrentSkipListSet<>(comparator); } /** @@ -83,7 +83,7 @@ public SealableNavigableSet(Collection col, Supplier seale */ public SealableNavigableSet(SortedSet set, Supplier sealedSupplier) { this.sealedSupplier = sealedSupplier; - navigableSet = new ConcurrentSkipListSet<>(set); + navSet = new ConcurrentSkipListSet<>(set); } /** @@ -95,7 +95,7 @@ public SealableNavigableSet(SortedSet set, Supplier sealedSupplier) */ public SealableNavigableSet(NavigableSet set, Supplier sealedSupplier) { this.sealedSupplier = sealedSupplier; - navigableSet = set; + navSet = set; } private void throwIfSealed() { @@ -105,58 +105,59 @@ private void throwIfSealed() { } // Immutable APIs - public boolean equals(Object o) { return o == this || navigableSet.equals(o); } - public int hashCode() { return navigableSet.hashCode(); } - public int size() { return navigableSet.size(); } - public boolean isEmpty() { return navigableSet.isEmpty(); } - public boolean contains(Object o) { return navigableSet.contains(o); } - public boolean containsAll(Collection col) { return navigableSet.containsAll(col);} - public Comparator comparator() { return navigableSet.comparator(); } - public T first() { return navigableSet.first(); } - public T last() { return navigableSet.last(); } - public Object[] toArray() { return navigableSet.toArray(); } - public T[] toArray(T[] a) { return navigableSet.toArray(a); } - public T lower(T e) { return navigableSet.lower(e); } - public T floor(T e) { return navigableSet.floor(e); } - public T ceiling(T e) { return navigableSet.ceiling(e); } - public T higher(T e) { return navigableSet.higher(e); } + public boolean equals(Object o) { return o == this || navSet.equals(o); } + public int hashCode() { return navSet.hashCode(); } + public String toString() { return navSet.toString(); } + public int size() { return navSet.size(); } + public boolean isEmpty() { return navSet.isEmpty(); } + public boolean contains(Object o) { return navSet.contains(o); } + public boolean containsAll(Collection col) { return navSet.containsAll(col);} + public Comparator comparator() { return navSet.comparator(); } + public T first() { return navSet.first(); } + public T last() { return navSet.last(); } + public Object[] toArray() { return navSet.toArray(); } + public T[] toArray(T[] a) { return navSet.toArray(a); } + public T lower(T e) { return navSet.lower(e); } + public T floor(T e) { return navSet.floor(e); } + public T ceiling(T e) { return navSet.ceiling(e); } + public T higher(T e) { return navSet.higher(e); } public Iterator iterator() { - return createSealHonoringIterator(navigableSet.iterator()); + return createSealHonoringIterator(navSet.iterator()); } public Iterator descendingIterator() { - return createSealHonoringIterator(navigableSet.descendingIterator()); + return createSealHonoringIterator(navSet.descendingIterator()); } public NavigableSet descendingSet() { - return new SealableNavigableSet<>(navigableSet.descendingSet(), sealedSupplier); + return new SealableNavigableSet<>(navSet.descendingSet(), sealedSupplier); } public SortedSet subSet(T fromElement, T toElement) { return subSet(fromElement, true, toElement, false); } public NavigableSet subSet(T fromElement, boolean fromInclusive, T toElement, boolean toInclusive) { - return new SealableNavigableSet<>(navigableSet.subSet(fromElement, fromInclusive, toElement, toInclusive), sealedSupplier); + return new SealableNavigableSet<>(navSet.subSet(fromElement, fromInclusive, toElement, toInclusive), sealedSupplier); } public SortedSet headSet(T toElement) { return headSet(toElement, false); } public NavigableSet headSet(T toElement, boolean inclusive) { - return new SealableNavigableSet<>(navigableSet.headSet(toElement, inclusive), sealedSupplier); + return new SealableNavigableSet<>(navSet.headSet(toElement, inclusive), sealedSupplier); } public SortedSet tailSet(T fromElement) { return tailSet(fromElement, false); } public NavigableSet tailSet(T fromElement, boolean inclusive) { - return new SealableNavigableSet<>(navigableSet.tailSet(fromElement, inclusive), sealedSupplier); + return new SealableNavigableSet<>(navSet.tailSet(fromElement, inclusive), sealedSupplier); } // Mutable APIs - public boolean add(T e) { throwIfSealed(); return navigableSet.add(e); } - public boolean addAll(Collection col) { throwIfSealed(); return navigableSet.addAll(col); } - public void clear() { throwIfSealed(); navigableSet.clear(); } - public boolean remove(Object o) { throwIfSealed(); return navigableSet.remove(o); } - public boolean removeAll(Collection col) { throwIfSealed(); return navigableSet.removeAll(col); } - public boolean retainAll(Collection col) { throwIfSealed(); return navigableSet.retainAll(col); } - public T pollFirst() { throwIfSealed(); return navigableSet.pollFirst(); } - public T pollLast() { throwIfSealed(); return navigableSet.pollLast(); } + public boolean add(T e) { throwIfSealed(); return navSet.add(e); } + public boolean addAll(Collection col) { throwIfSealed(); return navSet.addAll(col); } + public void clear() { throwIfSealed(); navSet.clear(); } + public boolean remove(Object o) { throwIfSealed(); return navSet.remove(o); } + public boolean removeAll(Collection col) { throwIfSealed(); return navSet.removeAll(col); } + public boolean retainAll(Collection col) { throwIfSealed(); return navSet.retainAll(col); } + public T pollFirst() { throwIfSealed(); return navSet.pollFirst(); } + public T pollLast() { throwIfSealed(); return navSet.pollLast(); } private Iterator createSealHonoringIterator(Iterator iterator) { return new Iterator() { diff --git a/src/main/java/com/cedarsoftware/util/SealableSet.java b/src/main/java/com/cedarsoftware/util/SealableSet.java index bff1f550..2030b9c8 100644 --- a/src/main/java/com/cedarsoftware/util/SealableSet.java +++ b/src/main/java/com/cedarsoftware/util/SealableSet.java @@ -76,14 +76,15 @@ private void throwIfSealed() { } // Immutable APIs + public boolean equals(Object o) { return set.equals(o); } + public int hashCode() { return set.hashCode(); } + public String toString() { return set.toString(); } public int size() { return set.size(); } public boolean isEmpty() { return set.isEmpty(); } public boolean contains(Object o) { return set.contains(o); } public Object[] toArray() { return set.toArray(); } public T1[] toArray(T1[] a) { return set.toArray(a); } public boolean containsAll(Collection col) { return set.containsAll(col); } - public boolean equals(Object o) { return set.equals(o); } - public int hashCode() { return set.hashCode(); } // Mutable APIs public boolean add(T t) { throwIfSealed(); return set.add(t); } diff --git a/src/test/java/com/cedarsoftware/util/ConcurrentList2Test.java b/src/test/java/com/cedarsoftware/util/ConcurrentList2Test.java new file mode 100644 index 00000000..75d16e78 --- /dev/null +++ b/src/test/java/com/cedarsoftware/util/ConcurrentList2Test.java @@ -0,0 +1,127 @@ +package com.cedarsoftware.util; + +import java.security.SecureRandom; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +/** + * @author John DeRegnaucourt (jdereg@gmail.com) + *
+ * Copyright (c) Cedar Software 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 + *

+ * License + *

+ * 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. + */ +class ConcurrentList2Test { + + @Test + void testConcurrentOperations() throws InterruptedException { + final int numberOfThreads = 6; + final int numberOfElements = 100; + ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads); + CountDownLatch latch = new CountDownLatch(numberOfThreads); + ConcurrentList list = new ConcurrentList<>(); + + // Initialize the list with 100 elements (1-100) + for (int i = 1; i <= numberOfElements; i++) { + list.add(i); + } + + // Define random operations on the list + Runnable modifierRunnable = () -> { + Random random = new SecureRandom(); + while (true) { + try { + int operation = random.nextInt(3); + int value = random.nextInt(1000) + 1000; + int index = random.nextInt(list.size()); + + switch (operation) { + case 0: + list.add(index, value); + break; + case 1: + list.remove(index); + break; + case 2: + list.set(index, value); + break; + } + } catch (IndexOutOfBoundsException | IllegalArgumentException e) { + } + } + }; + + Runnable iteratorRunnable = () -> { + Random random = new SecureRandom(); + while (true) { + try { + int start = random.nextInt(random.nextInt(list.size())); + Iterator it = list.iterator(); + while (it.hasNext()) { it.next(); } + } catch (UnsupportedOperationException | IllegalArgumentException e) { + } + } + }; + + Runnable listIteratorRunnable = () -> { + Random random = new SecureRandom(); + while (true) { + try { + int start = random.nextInt(random.nextInt(list.size())); + ListIterator it = list.listIterator(); + while (it.hasNext()) { it.next(); } + } catch (UnsupportedOperationException | IllegalArgumentException e) { + } + } + }; + + Runnable subListRunnable = () -> { + Random random = new SecureRandom(); + while (true) { + try { + int x = random.nextInt(99); + int y = random.nextInt(99); + if (x > y) { + int temp = x; + x = y; + y = temp; + } + List list2 = list.subList(x, y); + Iterator i = list2.iterator(); + while (i.hasNext()) { i.next(); } + } catch (IndexOutOfBoundsException e) { + } + } + }; + + // Execute the threads + executor.execute(modifierRunnable); + executor.execute(modifierRunnable); + executor.execute(iteratorRunnable); + executor.execute(iteratorRunnable); + executor.execute(listIteratorRunnable); + executor.execute(listIteratorRunnable); + + // Wait for threads to complete (except the continuous validator) + latch.await(250, TimeUnit.MILLISECONDS); + executor.shutdownNow(); + } +} diff --git a/src/test/java/com/cedarsoftware/util/ConcurrentListTest.java b/src/test/java/com/cedarsoftware/util/ConcurrentListTest.java index 1010569a..7ace33bc 100644 --- a/src/test/java/com/cedarsoftware/util/ConcurrentListTest.java +++ b/src/test/java/com/cedarsoftware/util/ConcurrentListTest.java @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; class ConcurrentListTest { @@ -90,8 +91,8 @@ void testSubList() { List list = new ConcurrentList<>(); list.addAll(Arrays.asList(1, 2, 3, 4, 5)); - List subList = list.subList(1, 4); - assertEquals(Arrays.asList(2, 3, 4), subList, "SubList should return the correct portion of the list"); + List subList = null; + assertThrows(UnsupportedOperationException.class, () -> list.subList(1, 4)); } @Test diff --git a/src/test/java/com/cedarsoftware/util/ConcurrentHashSetTest.java b/src/test/java/com/cedarsoftware/util/ConcurrentSetTest.java similarity index 89% rename from src/test/java/com/cedarsoftware/util/ConcurrentHashSetTest.java rename to src/test/java/com/cedarsoftware/util/ConcurrentSetTest.java index 6c56ba85..312540f5 100644 --- a/src/test/java/com/cedarsoftware/util/ConcurrentHashSetTest.java +++ b/src/test/java/com/cedarsoftware/util/ConcurrentSetTest.java @@ -33,11 +33,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -class ConcurrentHashSetTest { +class ConcurrentSetTest { @Test void testAddAndRemove() { - ConcurrentHashSet set = new ConcurrentHashSet<>(); + ConcurrentSet set = new ConcurrentSet<>(); assertTrue(set.add(1), "Should return true when adding a new element"); assertTrue(set.contains(1), "Set should contain the element 1 after addition"); assertEquals(1, set.size(), "Set size should be 1"); @@ -50,7 +50,7 @@ void testAddAndRemove() { @Test void testAddAllAndRemoveAll() { - ConcurrentHashSet set = new ConcurrentHashSet<>(); + ConcurrentSet set = new ConcurrentSet<>(); set.addAll(Arrays.asList(1, 2, 3)); assertEquals(3, set.size(), "Set should have 3 elements after addAll"); @@ -62,7 +62,7 @@ void testAddAllAndRemoveAll() { @Test void testRetainAll() { - ConcurrentHashSet set = new ConcurrentHashSet<>(); + ConcurrentSet set = new ConcurrentSet<>(); set.addAll(Arrays.asList(1, 2, 3, 4, 5)); set.retainAll(Arrays.asList(2, 3, 5)); @@ -72,7 +72,7 @@ void testRetainAll() { @Test void testClear() { - ConcurrentHashSet set = new ConcurrentHashSet<>(); + ConcurrentSet set = new ConcurrentSet<>(); set.addAll(Arrays.asList(1, 2, 3)); set.clear(); @@ -82,7 +82,7 @@ void testClear() { @Test void testIterator() { - ConcurrentHashSet set = new ConcurrentHashSet<>(); + ConcurrentSet set = new ConcurrentSet<>(); set.addAll(Arrays.asList(1, 2, 3)); int sum = 0; @@ -94,7 +94,7 @@ void testIterator() { @Test void testToArray() { - ConcurrentHashSet set = new ConcurrentHashSet<>(); + ConcurrentSet set = new ConcurrentSet<>(); set.addAll(Arrays.asList(1, 2, 3)); Object[] array = set.toArray(); @@ -109,7 +109,7 @@ void testToArray() { @Test void testIsEmptyAndSize() { - ConcurrentHashSet set = new ConcurrentHashSet<>(); + ConcurrentSet set = new ConcurrentSet<>(); assertTrue(set.isEmpty(), "New set should be empty"); set.add(1); diff --git a/src/test/java/com/cedarsoftware/util/SealableListTest.java b/src/test/java/com/cedarsoftware/util/SealableListTest.java index 641c7b23..780038ff 100644 --- a/src/test/java/com/cedarsoftware/util/SealableListTest.java +++ b/src/test/java/com/cedarsoftware/util/SealableListTest.java @@ -1,5 +1,6 @@ package com.cedarsoftware.util; +import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -42,7 +43,7 @@ class SealableListTest { @BeforeEach void setUp() { sealedState = false; - list = new SealableList<>(sealedSupplier); + list = new SealableList<>(new ArrayList<>(), sealedSupplier); list.add(10); list.add(20); list.add(30);