diff --git a/README.md b/README.md index 53a911c1..c62d1d39 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,29 @@ java-util [![Maven Central](https://badgen.net/maven/v/maven-central/com.cedarsoftware/java-util)](https://central.sonatype.com/search?q=java-util&namespace=com.cedarsoftware) [![Javadoc](https://javadoc.io/badge/com.cedarsoftware/java-util.svg)](http://www.javadoc.io/doc/com.cedarsoftware/java-util) -Helpful utilities that are thoroughly tested (> 98% code coverage via JUnit tests). +Helpful utilities that are thoroughly tested. Available on [Maven Central](https://central.sonatype.com/search?q=java-util&namespace=com.cedarsoftware). This library has no dependencies on other libraries for runtime. -The`.jar`file is `250K.` -Works with`JDK 1.8`through`JDK 21`. +The`.jar`file is `260K` and works with`JDK 1.8`through`JDK 21`. The '.jar' file classes are version 52 (`JDK 1.8`). +## Compatibility + +### JPMS (Java Platform Module System) + +This library is fully compatible with JPMS, commonly known as Java Modules. It includes a `module-info.class` file that +specifies module dependencies and exports. + +### OSGi + +This library also supports OSGi environments. It comes with pre-configured OSGi metadata in the `MANIFEST.MF` file, ensuring easy integration into any OSGi-based application. + +Both of these features ensure that our library can be seamlessly integrated into modular Java applications, providing robust dependency management and encapsulation. --- To include in your project: ##### Gradle ``` -implementation 'com.cedarsoftware:java-util:2.6.0' +implementation 'com.cedarsoftware:java-util:2.7.0' ``` ##### Maven @@ -23,27 +34,11 @@ implementation 'com.cedarsoftware:java-util:2.6.0' com.cedarsoftware java-util - 2.6.0 + 2.7.0 ``` --- -Since Java 1.5, you can statically import classes. Using this technique with many of the classes below, it makes their methods directly accessible in your source code, keeping your source code smaller and easier to read. For example: - -``` -import static com.cedarsoftware.util.Converter.*; -``` -will permit you to write: -``` -... -Calendar cal = convertToCalendar("2019/11/17"); -Date date = convertToDate("November 17th, 2019 4:45pm"); -TimeStamp stamp = convertToTimeStamp(cal); -AtomicLong atomicLong = convertToAtomicLong("123128300") -String s = convertToString(atomicLong) -... -``` - Included in java-util: * **ArrayUtilities** - Useful utilities for working with Java's arrays `[]` * **ByteUtilities** - Useful routines for converting `byte[]` to HEX character `[]` and visa-versa. @@ -55,7 +50,8 @@ same class. * **CompactLinkedSet** - Small memory footprint `Set` that expands to a `LinkedHashSet` when `size() > compactSize()`. * **CompactCILinkedSet** - Small memory footprint `Set` that expands to a case-insensitive `LinkedHashSet` when `size() > compactSize()`. * **CompactCIHashSet** - Small memory footprint `Set` that expands to a case-insensitive `HashSet` when `size() > compactSize()`. - * **CaseInsensitiveSet** - `Set` that ignores case for `Strings` contained within. + * **CaseInsensitiveSet** - `Set` that ignores case for `Strings` contained within. + * **ConcurrentHashSet** - A thread-safe `Set` which does not require each element to be `Comparable` like `ConcurrentSkipListSet` which is a `NavigableSet,` which is a `SortedSet.` * **Maps** * **CompactMap** - Small memory footprint `Map` that expands to a `HashMap` when `size() > compactSize()` entries. * **CompactLinkedMap** - Small memory footprint `Map` that expands to a `LinkedHashMap` when `size() > compactSize()` entries. @@ -64,6 +60,8 @@ same class. * **CaseInsensitiveMap** - `Map` that ignores case when `Strings` are used as keys. * **LRUCache** - Thread safe LRUCache that implements the full Map API and supports a maximum capacity. Once max capacity is reached, placing another item in the cache will cause the eviction of the item that was the least recently used (LRU). * **TrackingMap** - `Map` class that tracks when the keys are accessed via `.get()` or `.containsKey()`. Provided by @seankellner +* **Lists** + * **ConcurrentList** - Provides a thread-safe `List` with all API support except for `listIterator(),` however, it implements `iterator()` which returns an iterator to a snapshot copy of the `List.` * **Converter** - Convert from one instance to another. For example, `convert("45.3", BigDecimal.class)` will convert the `String` to a `BigDecimal`. Works for all primitives, primitive wrappers, `Date`, `java.sql.Date`, `String`, `BigDecimal`, `BigInteger`, `AtomicBoolean`, `AtomicLong`, etc. The method is very generous on what it allows to be converted. For example, a `Calendar` instance can be input for a `Date` or `Long`. Call the method `Converter.getSupportedConversions()` or `Converter.allSupportedConversions()` to get a list of all source/target conversions. Currently, there are 680+ conversions. * **DateUtilities** - Robust date String parser that handles date/time, date, time, time/date, string name months or numeric months, skips comma, etc. English month names only (plus common month name abbreviations), time with/without seconds or milliseconds, `y/m/d` and `m/d/y` ordering as well. * **DeepEquals** - Compare two object graphs and return 'true' if they are equivalent, 'false' otherwise. This will handle cycles in the graph, and will call an `equals()` method on an object if it has one, otherwise it will do a field-by-field equivalency check for non-transient fields. Has options to turn on/off using `.equals()` methods that may exist on classes. diff --git a/changelog.md b/changelog.md index e8d9a14b..cf2eb749 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,8 @@ ### Revision History +* 2.7.0 + * Added `ConcurrentHashList,` which implements a thread-safe `List.` Provides all API support except for `listIterator(),` however, it implements `iterator()` which returns an iterator to a snapshot copy of the `List.` + * Added `ConcurrentHashSet,` a true `Set` which is a bit easier to use than `ConcurrentSkipListSet,` which as a `NaviableSet` and `SortedSet,` requires each element to be `Comparable.` + * Performance improvement: On `LRUCache,` removed unnecessary `Collections.SynchronizedMap` surrounding the internal `LinkedHashMap` as the concurrent protection offered by `ReentrantReadWriteLock` is all that is needed. * 2.6.0 * Performance improvement: `Converter` instance creation is faster due to the code no longer copying the static default table. Overrides are kept in separate variable. * New capability added: `MathUtilities.parseToMinimalNumericType()` which will parse a String number into a Long, BigInteger, Double, or BigDecimal, choosing the "smallest" datatype to represent the number without loss of precision. diff --git a/pom.xml b/pom.xml index 87e7bd74..71b2acbc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.cedarsoftware java-util bundle - 2.6.0 + 2.7.0 Java Utilities https://github.com/jdereg/java-util @@ -37,12 +37,12 @@ 5.10.2 4.11.0 3.25.3 - 4.19.13 + 4.21.0 1.21.1 - 3.3.0 - 3.2.2 + 3.4.1 + 3.2.4 3.13.0 3.6.3 3.2.5 diff --git a/src/main/java/com/cedarsoftware/util/CaseInsensitiveSet.java b/src/main/java/com/cedarsoftware/util/CaseInsensitiveSet.java index 28540a3e..23361212 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) + if (collection instanceof ConcurrentSkipListSet || collection instanceof ConcurrentHashSet) { map = new CaseInsensitiveMap<>(new ConcurrentSkipListMap<>()); } diff --git a/src/main/java/com/cedarsoftware/util/ConcurrentHashSet.java b/src/main/java/com/cedarsoftware/util/ConcurrentHashSet.java new file mode 100644 index 00000000..2580ed83 --- /dev/null +++ b/src/main/java/com/cedarsoftware/util/ConcurrentHashSet.java @@ -0,0 +1,79 @@ +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 new file mode 100644 index 00000000..4dfb3740 --- /dev/null +++ b/src/main/java/com/cedarsoftware/util/ConcurrentList.java @@ -0,0 +1,228 @@ +package com.cedarsoftware.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * @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 ConcurrentList implements List { + private final List list = new ArrayList<>(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + public int size() { + lock.readLock().lock(); + try { + return list.size(); + } finally { + lock.readLock().unlock(); + } + } + + public boolean isEmpty() { + lock.readLock().lock(); + try { + return list.isEmpty(); + } finally { + lock.readLock().unlock(); + } + } + + public boolean contains(Object o) { + lock.readLock().lock(); + try { + return list.contains(o); + } finally { + lock.readLock().unlock(); + } + } + + public Iterator iterator() { + lock.readLock().lock(); + try { + return new ArrayList<>(list).iterator(); // Create a snapshot for iterator + } finally { + lock.readLock().unlock(); + } + } + + public Object[] toArray() { + lock.readLock().lock(); + try { + return list.toArray(); + } finally { + lock.readLock().unlock(); + } + } + + public T[] toArray(T[] a) { + lock.readLock().lock(); + try { + return list.toArray(a); + } finally { + lock.readLock().unlock(); + } + } + + public boolean add(E e) { + lock.writeLock().lock(); + try { + return list.add(e); + } finally { + lock.writeLock().unlock(); + } + } + + public boolean remove(Object o) { + lock.writeLock().lock(); + try { + return list.remove(o); + } finally { + lock.writeLock().unlock(); + } + } + + public boolean containsAll(Collection c) { + lock.readLock().lock(); + try { + return list.containsAll(c); + } finally { + lock.readLock().unlock(); + } + } + + public boolean addAll(Collection c) { + lock.writeLock().lock(); + try { + return list.addAll(c); + } finally { + lock.writeLock().unlock(); + } + } + + public boolean addAll(int index, Collection c) { + lock.writeLock().lock(); + try { + return list.addAll(index, c); + } finally { + lock.writeLock().unlock(); + } + } + + public boolean removeAll(Collection c) { + lock.writeLock().lock(); + try { + return list.removeAll(c); + } finally { + lock.writeLock().unlock(); + } + } + + public boolean retainAll(Collection c) { + lock.writeLock().lock(); + try { + return list.retainAll(c); + } finally { + lock.writeLock().unlock(); + } + } + + public void clear() { + lock.writeLock().lock(); + try { + list.clear(); + } finally { + lock.writeLock().unlock(); + } + } + + public E get(int index) { + lock.readLock().lock(); + try { + return list.get(index); + } finally { + lock.readLock().unlock(); + } + } + + public E set(int index, E element) { + lock.writeLock().lock(); + try { + return list.set(index, element); + } finally { + lock.writeLock().unlock(); + } + } + + public void add(int index, E element) { + lock.writeLock().lock(); + try { + list.add(index, element); + } finally { + lock.writeLock().unlock(); + } + } + + public E remove(int index) { + lock.writeLock().lock(); + try { + return list.remove(index); + } finally { + lock.writeLock().unlock(); + } + } + + public int indexOf(Object o) { + lock.readLock().lock(); + try { + return list.indexOf(o); + } finally { + lock.readLock().unlock(); + } + } + + public int lastIndexOf(Object o) { + lock.readLock().lock(); + try { + return list.lastIndexOf(o); + } finally { + lock.readLock().unlock(); + } + } + + public ListIterator listIterator() { + throw new UnsupportedOperationException("ListIterator is not supported with thread-safe iteration."); + } + + public ListIterator listIterator(int index) { + throw new UnsupportedOperationException("ListIterator is not supported with thread-safe iteration."); + } + + public List subList(int fromIndex, int toIndex) { + lock.readLock().lock(); + try { + return new ArrayList<>(list.subList(fromIndex, toIndex)); // Return a snapshot of the sublist + } finally { + lock.readLock().unlock(); + } + } +} diff --git a/src/main/java/com/cedarsoftware/util/LRUCache.java b/src/main/java/com/cedarsoftware/util/LRUCache.java index 62dc531e..c64cd15d 100644 --- a/src/main/java/com/cedarsoftware/util/LRUCache.java +++ b/src/main/java/com/cedarsoftware/util/LRUCache.java @@ -1,7 +1,6 @@ package com.cedarsoftware.util; import java.util.Collection; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -33,11 +32,11 @@ public class LRUCache implements Map { private final ReadWriteLock lock = new ReentrantReadWriteLock(); public LRUCache(int capacity) { - this.cache = Collections.synchronizedMap(new LinkedHashMap(capacity, 0.75f, true) { + cache = new LinkedHashMap(capacity, 0.75f, true) { protected boolean removeEldestEntry(Map.Entry eldest) { return size() > capacity; } - }); + }; } // Implement Map interface diff --git a/src/test/java/com/cedarsoftware/util/ConcurrentHashSetTest.java b/src/test/java/com/cedarsoftware/util/ConcurrentHashSetTest.java new file mode 100644 index 00000000..6c56ba85 --- /dev/null +++ b/src/test/java/com/cedarsoftware/util/ConcurrentHashSetTest.java @@ -0,0 +1,119 @@ +package com.cedarsoftware.util; + +import java.util.Arrays; +import java.util.HashSet; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * This class is used in conjunction with the Executor class. Example + * usage:
+ * Executor exec = new Executor()
+ * exec.execute("ls -l")
+ * String result = exec.getOut()
+ * 
+ * + * @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 ConcurrentHashSetTest { + + @Test + void testAddAndRemove() { + ConcurrentHashSet set = new ConcurrentHashSet<>(); + 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"); + + assertFalse(set.add(1), "Should return false when adding a duplicate element"); + assertTrue(set.remove(1), "Should return true when removing an existing element"); + assertFalse(set.contains(1), "Set should not contain the element 1 after removal"); + assertTrue(set.isEmpty(), "Set should be empty after removing elements"); + } + + @Test + void testAddAllAndRemoveAll() { + ConcurrentHashSet set = new ConcurrentHashSet<>(); + set.addAll(Arrays.asList(1, 2, 3)); + + assertEquals(3, set.size(), "Set should have 3 elements after addAll"); + assertTrue(set.containsAll(Arrays.asList(1, 2, 3)), "Set should contain all added elements"); + + set.removeAll(Arrays.asList(1, 3)); + assertTrue(set.contains(2) && !set.contains(1) && !set.contains(3), "Set should only contain the element 2 after removeAll"); + } + + @Test + void testRetainAll() { + ConcurrentHashSet set = new ConcurrentHashSet<>(); + set.addAll(Arrays.asList(1, 2, 3, 4, 5)); + set.retainAll(Arrays.asList(2, 3, 5)); + + assertTrue(set.containsAll(Arrays.asList(2, 3, 5)), "Set should contain elements 2, 3, and 5"); + assertFalse(set.contains(1) || set.contains(4), "Set should not contain elements 1 and 4"); + } + + @Test + void testClear() { + ConcurrentHashSet set = new ConcurrentHashSet<>(); + set.addAll(Arrays.asList(1, 2, 3)); + set.clear(); + + assertTrue(set.isEmpty(), "Set should be empty after clear"); + assertEquals(0, set.size(), "Set size should be 0 after clear"); + } + + @Test + void testIterator() { + ConcurrentHashSet set = new ConcurrentHashSet<>(); + set.addAll(Arrays.asList(1, 2, 3)); + + int sum = 0; + for (Integer i : set) { + sum += i; + } + assertEquals(6, sum, "Sum of elements should be 6"); + } + + @Test + void testToArray() { + ConcurrentHashSet set = new ConcurrentHashSet<>(); + set.addAll(Arrays.asList(1, 2, 3)); + + Object[] array = set.toArray(); + HashSet arrayContent = new HashSet<>(Arrays.asList(array)); + assertTrue(arrayContent.containsAll(Arrays.asList(1, 2, 3)), "Array should contain all the set elements"); + + Integer[] intArray = new Integer[3]; + intArray = set.toArray(intArray); + HashSet intArrayContent = new HashSet<>(Arrays.asList(intArray)); + assertTrue(intArrayContent.containsAll(Arrays.asList(1, 2, 3)), "Integer array should contain all the set elements"); + } + + @Test + void testIsEmptyAndSize() { + ConcurrentHashSet set = new ConcurrentHashSet<>(); + assertTrue(set.isEmpty(), "New set should be empty"); + + set.add(1); + assertFalse(set.isEmpty(), "Set should not be empty after adding an element"); + assertEquals(1, set.size(), "Size of set should be 1 after adding one element"); + } +} diff --git a/src/test/java/com/cedarsoftware/util/ConcurrentListTest.java b/src/test/java/com/cedarsoftware/util/ConcurrentListTest.java new file mode 100644 index 00000000..1cf607b0 --- /dev/null +++ b/src/test/java/com/cedarsoftware/util/ConcurrentListTest.java @@ -0,0 +1,264 @@ +package com.cedarsoftware.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +import static com.cedarsoftware.util.DeepEquals.deepEquals; +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 { + + @Test + void testAddAndSize() { + List list = new ConcurrentList<>(); + assertTrue(list.isEmpty(), "List should be initially empty"); + + list.add(1); + assertFalse(list.isEmpty(), "List should not be empty after add"); + assertEquals(1, list.size(), "List size should be 1 after adding one element"); + + list.add(2); + assertEquals(2, list.size(), "List size should be 2 after adding another element"); + } + + @Test + void testSetAndGet() { + List list = new ConcurrentList<>(); + list.add(1); + list.add(2); + + list.set(1, 3); + assertEquals(3, list.get(1), "Element at index 1 should be updated to 3"); + } + + @Test + void testAddAll() { + List list = new ConcurrentList<>(); + List toAdd = new ArrayList<>(Arrays.asList(1, 2, 3)); + + list.addAll(toAdd); + assertEquals(3, list.size(), "List should contain all added elements"); + } + + @Test + void testRemove() { + List list = new ConcurrentList<>(); + list.add(1); + list.add(2); + + assertTrue(list.remove(Integer.valueOf(1)), "Element should be removed successfully"); + assertEquals(1, list.size(), "List size should decrease after removal"); + assertFalse(list.contains(1), "List should not contain removed element"); + } + + @Test + void testConcurrency() throws InterruptedException { + List list = new ConcurrentList<>(); + ExecutorService executor = Executors.newFixedThreadPool(10); + int numberOfAdds = 1000; + + // Add elements in parallel + for (int i = 0; i < numberOfAdds; i++) { + int finalI = i; + executor.submit(() -> list.add(finalI)); + } + + // Shutdown executor and wait for all tasks to complete + executor.shutdown(); + assertTrue(executor.awaitTermination(1, TimeUnit.MINUTES), "Tasks did not complete in time"); + + // Check the list size after all additions + assertEquals(numberOfAdds, list.size(), "List size should match the number of added elements"); + + // Check if all elements were added + for (int i = 0; i < numberOfAdds; i++) { + assertTrue(list.contains(i), "List should contain the element added by the thread"); + } + } + + @Test + 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"); + } + + @Test + void testClearAndIsEmpty() { + List list = new ConcurrentList<>(); + list.add(1); + list.clear(); + assertTrue(list.isEmpty(), "List should be empty after clear operation"); + } + + @Test + void testIterator() { + List list = new ConcurrentList<>(); + list.add(1); + list.add(2); + list.add(3); + + int sum = 0; + for (Integer value : list) { + sum += value; + } + assertEquals(6, sum, "Sum of elements should be equal to the sum of 1, 2, and 3"); + } + + @Test + void testIndexOf() { + List list = new ConcurrentList<>(); + list.add(1); + list.add(2); + list.add(3); + list.add(2); + + assertEquals(1, list.indexOf(2), "Index of the first occurrence of 2 should be 1"); + assertEquals(3, list.lastIndexOf(2), "Index of the last occurrence of 2 should be 3"); + } + + @Test + void testAddRemoveAndSize() { + List list = new ConcurrentList<>(); + assertTrue(list.isEmpty(), "List should be initially empty"); + + list.add(1); + list.add(2); + assertFalse(list.isEmpty(), "List should not be empty after additions"); + assertEquals(2, list.size(), "List size should be 2 after adding two elements"); + + list.remove(Integer.valueOf(1)); + assertTrue(list.contains(2) && !list.contains(1), "List should contain 2 but not 1 after removal"); + assertEquals(1, list.size(), "List size should be 1 after removing one element"); + + list.add(3); + list.add(3); + assertTrue(list.remove(Integer.valueOf(3)), "First occurrence of 3 should be removed"); + assertEquals(2, list.size(), "List should be 2 after removing one occurrence of 3"); + } + + @Test + void testRetainAll() { + List list = new ConcurrentList<>(); + list.addAll(Arrays.asList(1, 2, 3, 4, 5)); + + list.retainAll(Arrays.asList(1, 2, 3)); + assertEquals(3, list.size(), "List should only retain elements 1, 2, and 3"); + assertTrue(list.containsAll(Arrays.asList(1, 2, 3)), "List should contain 1, 2, and 3"); + assertFalse(list.contains(4) || list.contains(5), "List should not contain 4 or 5"); + } + + @Test + void testRemoveAll() { + List list = new ConcurrentList<>(); + list.addAll(Arrays.asList(1, 2, 3, 4, 5)); + + list.removeAll(Arrays.asList(4, 5)); + assertEquals(3, list.size(), "List should have size 3 after removing 4 and 5"); + assertFalse(list.contains(4) || list.contains(5), "List should not contain 4 or 5"); + } + + @Test + void testContainsAll() { + List list = new ConcurrentList<>(); + list.addAll(Arrays.asList(1, 2, 3, 4, 5)); + + assertTrue(list.containsAll(Arrays.asList(1, 2, 3)), "List should contain 1, 2, and 3"); + assertFalse(list.containsAll(Arrays.asList(6, 7)), "List should not contain 6 or 7"); + } + + @Test + void testToArray() { + List list = new ConcurrentList<>(); + list.addAll(Arrays.asList(1, 2, 3, 4, 5)); + + Object[] array = list.toArray(); + assertArrayEquals(new Object[]{1, 2, 3, 4, 5}, array, "toArray should return correct elements"); + + Integer[] integerArray = new Integer[5]; + integerArray = list.toArray(integerArray); + assertArrayEquals(new Integer[]{1, 2, 3, 4, 5}, integerArray, "toArray(T[] a) should return correct elements"); + } + + @Test + void testAddAtIndex() { + List list = new ConcurrentList<>(); + list.addAll(Arrays.asList(1, 3, 4)); + + // Test adding at start + list.add(0, 0); + assert deepEquals(Arrays.asList(0, 1, 3, 4), list); + + // Test adding at middle + list.add(2, 2); + assert deepEquals(Arrays.asList(0, 1, 2, 3, 4), list); + + // Test adding at end + list.add(5, 5); + assert deepEquals(Arrays.asList(0, 1, 2, 3, 4, 5), list); + } + + @Test + void testRemoveAtIndex() { + List list = new ConcurrentList<>(); + list.addAll(Arrays.asList(0, 1, 2, 3, 4)); + + // Remove element at index 2 (which is '2') + assertEquals(2, list.remove(2), "Element 2 should be removed from index 2"); + assert deepEquals(Arrays.asList(0, 1, 3, 4), list); + + // Remove element at index 0 (which is '0') + assertEquals(0, list.remove(0), "Element 0 should be removed from index 0"); + assert deepEquals(Arrays.asList(1, 3, 4), list); + + // Remove element at last index (which is '4') + assertEquals(4, list.remove(2), "Element 4 should be removed from the last index"); + assert deepEquals(Arrays.asList(1, 3), list); + } + + @Test + void testAddAllAtIndex() { + List list = new ConcurrentList<>(); + list.addAll(Arrays.asList(1, 5)); + + // Add multiple elements at start + list.addAll(0, Arrays.asList(-1, 0)); + assert deepEquals(Arrays.asList(-1, 0, 1, 5), list); + + // Add multiple elements at middle + list.addAll(2, Arrays.asList(2, 3, 4)); + assert deepEquals(Arrays.asList(-1, 0, 2, 3, 4, 1, 5), list); + + // Add multiple elements at end + list.addAll(7, Arrays.asList(6, 7)); + assert deepEquals(Arrays.asList(-1, 0, 2, 3, 4, 1, 5, 6, 7), list); + } + + @Test + void testListIeratorBlows() { + List list = new ConcurrentList<>(); + list.addAll(Arrays.asList(1, 5)); + + assertThrows(UnsupportedOperationException.class, () -> list.listIterator()); + } + + @Test + void testListIerator2Blows() { + List list = new ConcurrentList<>(); + list.addAll(Arrays.asList(1, 5)); + + assertThrows(UnsupportedOperationException.class, () -> list.listIterator(1)); + } +}