Skip to content

Commit

Permalink
- New Sealable Collections added.
Browse files Browse the repository at this point in the history
- ConcurrentList updated to support being the List and wrapping a List.
  • Loading branch information
jdereg committed Apr 28, 2024
1 parent d446ddb commit f3ec068
Show file tree
Hide file tree
Showing 17 changed files with 1,839 additions and 31 deletions.
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ Both of these features ensure that our library can be seamlessly integrated into
To include in your project:
##### Gradle
```
implementation 'com.cedarsoftware:java-util:2.8.0'
implementation 'com.cedarsoftware:java-util:2.9.0'
```

##### Maven
```
<dependency>
<groupId>com.cedarsoftware</groupId>
<artifactId>java-util</artifactId>
<version>2.8.0</version>
<version>2.9.0</version>
</dependency>
```
---
Expand All @@ -51,6 +51,8 @@ same class.
* **CompactCIHashSet** - Small memory footprint `Set` that expands to a case-insensitive `HashSet` when `size() > compactSize()`.
* **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.`
* **SealableSet** - Provides a `Set` (or `Set` wrapper) that will make it read-only (sealed) or read-write (unsealed), controllable via a `Supplier<Boolean>.` This moves the immutability control outside the `Set` and ensures that all views on the `Set` respect the sealed-ness. One master supplier can control the immutability of many collections.
* **SealableNavigableSet** - Provides a `NavigableSet` (or `NavigableSet` wrapper) that will make it read-only (sealed) or read-write (unsealed), controllable via a `Supplier<Boolean>.` This moves the immutability control outside the `NavigableSet` and ensures that all views on the `NavigableSet` respect the sealed-ness. One master supplier can control the immutability of many collections.
* **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.
Expand All @@ -59,8 +61,12 @@ 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
* **SealableMap** - Provides a `Map` (or `Map` wrapper) that will make it read-only (sealed) or read-write (unsealed), controllable via a `Supplier<Boolean>.` This moves the immutability control outside the `Map` and ensures that all views on the `Map` respect the sealed-ness. One master supplier can control the immutability of many collections.
* **SealableNavigbableMap** - Provides a `NavigableMap` (or `NavigableMap` wrapper) that will make it read-only (sealed) or read-write (unsealed), controllable via a `Supplier<Boolean>.` This moves the immutability control outside the `NavigableMap` and ensures that all views on the `NavigableMap` respect the sealed-ness. One master supplier can control the immutability of many collections.
* **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.`
* **ConcurrentList** - Provides a thread-safe `List` (or `List` wrapper). Use the no-arg constructor for a thread-safe `List,` use the constructor that takes a `List` to wrap another `List` instance and make it thread-safe (no elements are copied).
* **SealableList** - Provides a `List` (or `List` wrapper) that will make it read-only (sealed) or read-write (unsealed), controllable via a `Supplier<Boolean>.` This moves the immutability control outside the `List` and ensures that all views on the `List` respect the sealed-ness. One master supplier can control the immutability of many collections.

* **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.
Expand Down
7 changes: 7 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
### Revision History
* 2.9.0
* Added `SealableList` which provides a `List` (or `List` wrapper) that will make it read-only (sealed) or read-write (unsealed), controllable via a `Supplier<Boolean>.` This moves the immutability control outside the list and ensures that all views on the `List` respect the sealed-ness. One master supplier can control the immutability of many collections.
* Added `SealableSet` similar to SealableList but with `Set` nature.
* Added `SealableMap` similar to SealableList but with `Map` nature.
* Added `SealableNavigableSet` similar to SealableList but with `NavigableSet` nature.
* Added `SealableNavigableMap` similar to SealableList but with `NavigableMap` nature.
* Updated `ConcurrentList` to support wrapping any `List` and making it thread-safe, including all view APIs: `iterator(),` `listIterator(),` `listIterator(index).` The no-arg constructor creates a `ConcurrentList` ready-to-go. The constructor that takes a `List` parameter constructor wraps the passed in list and makes it thread-safe.
* 2.8.0
* Added `ClassUtilities.doesOneWrapTheOther()` API so that it is easy to test if one class is wrapping the other.
* Added `StringBuilder` and `StringBuffer` to `Strings` to the `Converter.` Eliminates special cases for `.toString()` calls where generalized `convert(src, type)` is being used.
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<groupId>com.cedarsoftware</groupId>
<artifactId>java-util</artifactId>
<packaging>bundle</packaging>
<version>2.8.0</version>
<version>2.9.0</version>
<description>Java Utilities</description>
<url>https://github.com/jdereg/java-util</url>

Expand Down
132 changes: 122 additions & 10 deletions src/main/java/com/cedarsoftware/util/ConcurrentList.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* ConcurrentList provides a List or 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).
* <br><br>
* @author John DeRegnaucourt ([email protected])
* <br>
* Copyright (c) Cedar Software LLC
Expand All @@ -26,9 +31,28 @@
* limitations under the License.
*/
public class ConcurrentList<E> implements List<E> {
private final List<E> list = new ArrayList<>();
private final List<E> list;
private final ReadWriteLock lock = new ReentrantReadWriteLock();

/**
* Use this no-arg constructor to create a ConcurrentList.
*/
public ConcurrentList() {
this.list = new ArrayList<>();
}

/**
* Use this constructor to wrap a List (any kind of List) and make it a ConcurrentList.
* No duplicate of the List is created and the original list is operated on directly.
* @param list List instance to protect.
*/
public ConcurrentList(List<E> list) {
if (list == null) {
throw new IllegalArgumentException("list cannot be null");
}
this.list = list;
}

public int size() {
lock.readLock().lock();
try {
Expand All @@ -47,6 +71,24 @@ public boolean isEmpty() {
}
}

public boolean equals(Object obj) {
lock.readLock().lock();
try {
return list.equals(obj);
} finally {
lock.readLock().unlock();
}
}

public int hashCode() {
lock.readLock().lock();
try {
return list.hashCode();
} finally {
lock.readLock().unlock();
}
}

public boolean contains(Object o) {
lock.readLock().lock();
try {
Expand Down Expand Up @@ -209,20 +251,90 @@ public int lastIndexOf(Object o) {
}
}

public List<E> subList(int fromIndex, int toIndex) { return new ConcurrentList<>(list.subList(fromIndex, toIndex)); }

public ListIterator<E> listIterator() {
throw new UnsupportedOperationException("ListIterator is not supported with thread-safe iteration.");
return createLockHonoringListIterator(list.listIterator());
}

public ListIterator<E> listIterator(int index) {
throw new UnsupportedOperationException("ListIterator is not supported with thread-safe iteration.");
return createLockHonoringListIterator(list.listIterator(index));
}

public List<E> 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();
}
private ListIterator<E> createLockHonoringListIterator(ListIterator<E> iterator) {
return new ListIterator<E>() {
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();
}
}
};
}
}
130 changes: 130 additions & 0 deletions src/main/java/com/cedarsoftware/util/SealableList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.cedarsoftware.util;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Supplier;

/**
* SealableList provides a List or List wrapper that can be 'sealed' and 'unsealed.' When
* sealed, the List is mutable, when unsealed it is immutable (read-only). The iterator(),
* listIterator(), and subList() return views that honor the Supplier's sealed state.
* The sealed state can be changed as often as needed.
* <br><br>
* NOTE: Please do not reformat this code as the current format makes it easy to see the overall structure.
* <br><br>
* @author John DeRegnaucourt ([email protected])
* <br>
* Copyright (c) Cedar Software LLC
* <br><br>
* 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
* <br><br>
* <a href="http://www.apache.org/licenses/LICENSE-2.0">License</a>
* <br><br>
* 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 SealableList<T> implements List<T> {
private final List<T> list;
private final Supplier<Boolean> sealedSupplier;

/**
* Create a SealableList. Since no List is being supplied, this will use an ConcurrentList internally. If you
* want to use an ArrayList for example, use SealableList constructor that takes a List and pass it the instance
* you want it to wrap.
* @param sealedSupplier {@code Supplier<Boolean>} that returns 'true' to indicate sealed, 'false' for mutable.
*/
public SealableList(Supplier<Boolean> sealedSupplier) {
this.list = new ConcurrentList<>();
this.sealedSupplier = sealedSupplier;
}

/**
* Create a SealableList. Since a List is not supplied, the elements from the passed in Collection will be
* copied to an internal ConcurrentList. If you want to use an ArrayList for example, use SealableList
* constructor that takes a List and pass it the instance you want it to wrap.
* @param col Collection to supply initial elements. These are copied to an internal ConcurrentList.
* @param sealedSupplier {@code Supplier<Boolean>} that returns 'true' to indicate sealed, 'false' for mutable.
*/
public SealableList(Collection<T> col, Supplier<Boolean> sealedSupplier) {
this.list = new ConcurrentList<>();
this.list.addAll(col);
this.sealedSupplier = sealedSupplier;
}

/**
* Use this constructor to wrap a List (any kind of List) and make it a SealableList.
* No duplicate of the List is created and the original list is operated on directly if unsealed, or protected
* from changes if sealed.
* @param list List instance to protect.
* @param sealedSupplier {@code Supplier<Boolean>} that returns 'true' to indicate sealed, 'false' for mutable.
*/
public SealableList(List<T> list, Supplier<Boolean> sealedSupplier) {
this.list = list;
this.sealedSupplier = sealedSupplier;
}

private void throwIfSealed() {
if (sealedSupplier.get()) {
throw new UnsupportedOperationException("This list has been sealed and is now immutable");
}
}

// Immutable APIs
public boolean equals(Object other) { return list.equals(other); }
public int hashCode() { return list.hashCode(); }
public int size() { return list.size(); }
public boolean isEmpty() { return list.isEmpty(); }
public boolean contains(Object o) { return list.contains(o); }
public boolean containsAll(Collection<?> col) { return new HashSet<>(list).containsAll(col); }
public int indexOf(Object o) { return list.indexOf(o); }
public int lastIndexOf(Object o) { return list.lastIndexOf(o); }
public T get(int index) { return list.get(index); }
public Object[] toArray() { return list.toArray(); }
public <T1> T1[] toArray(T1[] a) { return list.toArray(a);}
public Iterator<T> iterator() { return createSealHonoringIterator(list.iterator()); }
public ListIterator<T> listIterator() { return createSealHonoringListIterator(list.listIterator()); }
public ListIterator<T> listIterator(final int index) { return createSealHonoringListIterator(list.listIterator(index)); }
public List<T> subList(int fromIndex, int toIndex) { return new SealableList<>(list.subList(fromIndex, toIndex), sealedSupplier); }

// Mutable APIs
public boolean add(T t) { throwIfSealed(); return list.add(t); }
public boolean remove(Object o) { throwIfSealed(); return list.remove(o); }
public boolean addAll(Collection<? extends T> col) { throwIfSealed(); return list.addAll(col); }
public boolean addAll(int index, Collection<? extends T> col) { throwIfSealed(); return list.addAll(index, col); }
public boolean removeAll(Collection<?> col) { throwIfSealed(); return list.removeAll(col); }
public boolean retainAll(Collection<?> col) { throwIfSealed(); return list.retainAll(col); }
public void clear() { throwIfSealed(); list.clear(); }
public T set(int index, T element) { throwIfSealed(); return list.set(index, element); }
public void add(int index, T element) { throwIfSealed(); list.add(index, element); }
public T remove(int index) { throwIfSealed(); return list.remove(index); }

private Iterator<T> createSealHonoringIterator(Iterator<T> iterator) {
return new Iterator<T>() {
public boolean hasNext() { return iterator.hasNext(); }
public T next() { return iterator.next(); }
public void remove() { throwIfSealed(); iterator.remove(); }
};
}

private ListIterator<T> createSealHonoringListIterator(ListIterator<T> iterator) {
return new ListIterator<T>() {
public boolean hasNext() { return iterator.hasNext();}
public T next() { return iterator.next(); }
public boolean hasPrevious() { return iterator.hasPrevious(); }
public T previous() { return iterator.previous(); }
public int nextIndex() { return iterator.nextIndex(); }
public int previousIndex() { return iterator.previousIndex(); }
public void remove() { throwIfSealed(); iterator.remove(); }
public void set(T e) { throwIfSealed(); iterator.set(e); }
public void add(T e) { throwIfSealed(); iterator.add(e);}
};
}
}
Loading

0 comments on commit f3ec068

Please sign in to comment.