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.cedarsoftwarejava-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.cedarsoftwarejava-utilbundle
- 2.6.0
+ 2.7.0Java Utilitieshttps://github.com/jdereg/java-util
@@ -37,12 +37,12 @@
5.10.24.11.03.25.3
- 4.19.13
+ 4.21.01.21.1
- 3.3.0
- 3.2.2
+ 3.4.1
+ 3.2.43.13.03.6.33.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 extends E> 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
+ *
+ * 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 extends T> 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
+ *
+ * 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 extends E> c) {
+ lock.writeLock().lock();
+ try {
+ return list.addAll(c);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ public boolean addAll(int index, Collection extends E> 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
+ *
+ * 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