This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push:
new b6f72e73e Add builders for LockingVisitors implementations
b6f72e73e is described below
commit b6f72e73ee96feb63e17096933fd5b7f313fb77b
Author: Gary D. Gregory <[email protected]>
AuthorDate: Fri Jun 20 10:28:32 2025 -0400
Add builders for LockingVisitors implementations
---
src/changes/changes.xml | 1 +
.../lang3/concurrent/locks/LockingVisitors.java | 258 +++++++++++++++++++++
.../concurrent/locks/LockingVisitorsTest.java | 133 +++++++++--
3 files changed, 370 insertions(+), 22 deletions(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index d6c6a7812..f5a435a89 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -136,6 +136,7 @@ The <action> type attribute can be add,update,fix,remove.
<action type="add" dev="ggregory" due-to="Gary
Gregory">Add
org.apache.commons.lang3.concurrent.locks.LockingVisitors.reentrantLockVisitor(Object).</action>
<action type="add" dev="ggregory" due-to="Gary
Gregory">Add
org.apache.commons.lang3.concurrent.locks.LockingVisitors.create(Object,
ReentrantLock).</action>
<action type="add" dev="ggregory" due-to="Gary
Gregory">Add
org.apache.commons.lang3.concurrent.locks.LockingVisitors.ReentrantLockVisitor.</action>
+ <action type="add" dev="ggregory" due-to="Gary
Gregory">Add builders for LockingVisitors implementations.</action>
<!-- UPDATE -->
<action type="update" dev="ggregory" due-to="Gary
Gregory, Dependabot">Bump org.apache.commons:commons-parent from 73 to 85
#1267, #1277, #1283, #1288, #1302, #1377.</action>
<action type="update" dev="ggregory" due-to="Gary
Gregory, Dependabot">[site] Bump org.codehaus.mojo:taglist-maven-plugin from
3.1.0 to 3.2.1 #1300.</action>
diff --git
a/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java
b/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java
index d613cc440..0e165de3a 100644
---
a/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java
+++
b/src/main/java/org/apache/commons/lang3/concurrent/locks/LockingVisitors.java
@@ -24,6 +24,7 @@
import java.util.concurrent.locks.StampedLock;
import java.util.function.Supplier;
+import org.apache.commons.lang3.builder.AbstractSupplier;
import org.apache.commons.lang3.function.Failable;
import org.apache.commons.lang3.function.FailableConsumer;
import org.apache.commons.lang3.function.FailableFunction;
@@ -94,6 +95,103 @@ public class LockingVisitors {
*/
public static class LockVisitor<O, L> {
+ /**
+ * Builds {@link LockVisitor} instances.
+ *
+ * @param <O> the wrapped object type.
+ * @param <L> the wrapped lock type.
+ * @param <B> the builder type.
+ * @since 3.18.0
+ */
+ public static class LVBuilder<O, L, B extends LVBuilder<O, L, B>>
extends AbstractSupplier<LockVisitor<O, L>, B, RuntimeException> {
+
+ /**
+ * The lock object, untyped, since, for example {@link
StampedLock} does not implement a locking interface in
+ * Java 8.
+ */
+ L lock;
+
+ /**
+ * The guarded object.
+ */
+ O object;
+
+ /**
+ * Supplies the read lock, usually from the lock object.
+ */
+ private Supplier<Lock> readLockSupplier;
+
+ /**
+ * Supplies the write lock, usually from the lock object.
+ */
+ private Supplier<Lock> writeLockSupplier;
+
+ /**
+ * Constructs a new instance.
+ */
+ public LVBuilder() {
+ // empty
+ }
+
+ @Override
+ public LockVisitor<O, L> get() {
+ return new LockVisitor<>(this);
+ }
+
+ Supplier<Lock> getReadLockSupplier() {
+ return readLockSupplier;
+ }
+
+
+ Supplier<Lock> getWriteLockSupplier() {
+ return writeLockSupplier;
+ }
+
+ /**
+ * Set the lock used from accept methods.
+ *
+ * @param lock the lock.
+ * @return {@code this} instance.
+ */
+ public B setLock(final L lock) {
+ this.lock = lock;
+ return asThis();
+ }
+
+ /**
+ * Set the resource.
+ *
+ * @param object the resource.
+ * @return {@code this} instance.
+ */
+ public B setObject(final O object) {
+ this.object = object;
+ return asThis();
+ }
+
+ /**
+ * Supplies the read lock.
+ *
+ * @param readLockSupplier Supplies the read lock.
+ * @return {@code this} instance.
+ */
+ public B setReadLockSupplier(final Supplier<Lock>
readLockSupplier) {
+ this.readLockSupplier = readLockSupplier;
+ return asThis();
+ }
+
+ /**
+ * Supplies the write lock.
+ *
+ * @param writeLockSupplier Supplies the write lock.
+ * @return {@code this} instance.
+ */
+ public B setWriteLockSupplier(final Supplier<Lock>
writeLockSupplier) {
+ this.writeLockSupplier = writeLockSupplier;
+ return asThis();
+ }
+ }
+
/**
* The lock object, untyped, since, for example {@link StampedLock}
does not implement a locking interface in
* Java 8.
@@ -115,6 +213,18 @@ public static class LockVisitor<O, L> {
*/
private final Supplier<Lock> writeLockSupplier;
+ /**
+ * Constructs an instance from a builder.
+ *
+ * @param builder The builder.
+ */
+ private LockVisitor(final LVBuilder<O, L, ?> builder) {
+ this.object = Objects.requireNonNull(builder.object, "object");
+ this.lock = Objects.requireNonNull(builder.lock, "lock");
+ this.readLockSupplier =
Objects.requireNonNull(builder.readLockSupplier, "readLockSupplier");
+ this.writeLockSupplier =
Objects.requireNonNull(builder.writeLockSupplier, "writeLockSupplier");
+ }
+
/**
* Constructs an instance.
*
@@ -316,6 +426,54 @@ protected <T> T lockApplyUnlock(final Supplier<Lock>
lockSupplier, final Failabl
*/
public static class ReadWriteLockVisitor<O> extends LockVisitor<O,
ReadWriteLock> {
+ /**
+ * Builds {@link LockVisitor} instances.
+ *
+ * @param <O> the wrapped object type.
+ * @since 3.18.0
+ */
+ public static class Builder<O> extends LVBuilder<O, ReadWriteLock,
Builder<O>> {
+
+ /**
+ * Constructs a new instance.
+ */
+ public Builder() {
+ // empty
+ }
+
+ @Override
+ public ReadWriteLockVisitor<O> get() {
+ return new ReadWriteLockVisitor<>(this);
+ }
+
+ @Override
+ public Builder<O> setLock(final ReadWriteLock readWriteLock) {
+ setReadLockSupplier(readWriteLock::readLock);
+ setWriteLockSupplier(readWriteLock::writeLock);
+ return super.setLock(readWriteLock);
+ }
+ }
+
+ /**
+ * Creates a new builder.
+ *
+ * @param <O> the wrapped object type.
+ * @return a new builder.
+ * @since 3.18.0
+ */
+ public static <O> Builder<O> builder() {
+ return new Builder<>();
+ }
+
+ /**
+ * Constructs a new instance from a builder.
+ *
+ * @param builder a builder.
+ */
+ private ReadWriteLockVisitor(final Builder<O> builder) {
+ super(builder);
+ }
+
/**
* Creates a new instance with the given locked object. This
constructor is supposed to be used for subclassing
* only. In general, it is suggested to use {@link
LockingVisitors#stampedLockVisitor(Object)} instead.
@@ -326,6 +484,7 @@ public static class ReadWriteLockVisitor<O> extends
LockVisitor<O, ReadWriteLock
protected ReadWriteLockVisitor(final O object, final ReadWriteLock
readWriteLock) {
super(object, readWriteLock, readWriteLock::readLock,
readWriteLock::writeLock);
}
+
}
/**
@@ -340,6 +499,56 @@ protected ReadWriteLockVisitor(final O object, final
ReadWriteLock readWriteLock
*/
public static class ReentrantLockVisitor<O> extends LockVisitor<O,
ReentrantLock> {
+ /**
+ * Builds {@link LockVisitor} instances.
+ *
+ * @param <O> the wrapped object type.
+ * @since 3.18.0
+ */
+ public static class Builder<O> extends LVBuilder<O, ReentrantLock,
Builder<O>> {
+
+ /**
+ * Constructs a new instance.
+ */
+ public Builder() {
+ // empty
+ }
+
+ @Override
+ public ReentrantLockVisitor<O> get() {
+ return new ReentrantLockVisitor<>(this);
+ }
+
+
+ @Override
+ public Builder<O> setLock(final ReentrantLock reentrantLock) {
+ setReadLockSupplier(() -> reentrantLock);
+ setWriteLockSupplier(() -> reentrantLock);
+ return super.setLock(reentrantLock);
+ }
+ }
+
+ /**
+ * Creates a new builder.
+ *
+ * @param <O> the wrapped object type.
+ * @return a new builder.
+ * @since 3.18.0
+ */
+ public static <O> Builder<O> builder() {
+ return new Builder<>();
+ }
+
+ /**
+ * Constructs a new instance from a builder.
+ *
+ * @param builder a builder.
+ */
+ private ReentrantLockVisitor(final Builder<O> builder) {
+ super(builder);
+ }
+
+
/**
* Creates a new instance with the given locked object. This
constructor is supposed to be used for subclassing
* only. In general, it is suggested to use {@link
LockingVisitors#reentrantLockVisitor(Object)} instead.
@@ -363,6 +572,55 @@ protected ReentrantLockVisitor(final O object, final
ReentrantLock reentrantLock
*/
public static class StampedLockVisitor<O> extends LockVisitor<O,
StampedLock> {
+ /**
+ * Builds {@link LockVisitor} instances.
+ *
+ * @param <O> the wrapped object type.
+ * @since 3.18.0
+ */
+ public static class Builder<O> extends LVBuilder<O, StampedLock,
Builder<O>> {
+
+ /**
+ * Constructs a new instance.
+ */
+ public Builder() {
+ // empty
+ }
+
+ @Override
+ public StampedLockVisitor<O> get() {
+ return new StampedLockVisitor<>(this);
+ }
+
+
+ @Override
+ public Builder<O> setLock(final StampedLock stampedLock) {
+ setReadLockSupplier(stampedLock::asReadLock);
+ setWriteLockSupplier(stampedLock::asWriteLock);
+ return super.setLock(stampedLock);
+ }
+ }
+
+ /**
+ * Creates a new builder.
+ *
+ * @param <O> the wrapped object type.
+ * @return a new builder.
+ * @since 3.18.0
+ */
+ public static <O> Builder<O> builder() {
+ return new Builder<>();
+ }
+
+ /**
+ * Constructs a new instance from a builder.
+ *
+ * @param builder a builder.
+ */
+ private StampedLockVisitor(final Builder<O> builder) {
+ super(builder);
+ }
+
/**
* Creates a new instance with the given locked object. This
constructor is supposed to be used for subclassing
* only. In general, it is suggested to use {@link
LockingVisitors#stampedLockVisitor(Object)} instead.
diff --git
a/src/test/java/org/apache/commons/lang3/concurrent/locks/LockingVisitorsTest.java
b/src/test/java/org/apache/commons/lang3/concurrent/locks/LockingVisitorsTest.java
index 3ce1a38d5..9797644af 100644
---
a/src/test/java/org/apache/commons/lang3/concurrent/locks/LockingVisitorsTest.java
+++
b/src/test/java/org/apache/commons/lang3/concurrent/locks/LockingVisitorsTest.java
@@ -26,12 +26,15 @@
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.locks.StampedLock;
import java.util.function.LongConsumer;
import org.apache.commons.lang3.AbstractLangTest;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ThreadUtils;
import org.apache.commons.lang3.concurrent.locks.LockingVisitors.LockVisitor;
+import
org.apache.commons.lang3.concurrent.locks.LockingVisitors.ReadWriteLockVisitor;
+import
org.apache.commons.lang3.concurrent.locks.LockingVisitors.ReentrantLockVisitor;
import
org.apache.commons.lang3.concurrent.locks.LockingVisitors.StampedLockVisitor;
import org.apache.commons.lang3.function.FailableConsumer;
import org.junit.jupiter.api.Test;
@@ -95,16 +98,102 @@ protected void set(final boolean[] booleanArray, final int
offset, final boolean
}
}
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ void testBuilderLockVisitor(final boolean fair) {
+ final AtomicInteger obj = new AtomicInteger();
+ final ReadWriteLock lock = new ReentrantReadWriteLock(fair);
+ // @formatter:off
+ final LockVisitor<AtomicInteger, ReadWriteLock> lockVisitor = new
LockVisitor.LVBuilder()
+ .setObject(obj)
+ .setLock(lock)
+ .setReadLockSupplier(lock::readLock)
+ .setWriteLockSupplier(lock::writeLock)
+ .get();
+ // @formatter:on
+ lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
+ assertEquals(1, obj.get());
+ lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
+ assertEquals(2, obj.get());
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ void testBuilderReadWriteLockVisitor(final boolean fair) {
+ final AtomicInteger obj = new AtomicInteger();
+ final ReadWriteLock lock = new ReentrantReadWriteLock(fair);
+ // @formatter:off
+ final LockingVisitors.ReadWriteLockVisitor<AtomicInteger> lockVisitor
= ReadWriteLockVisitor.<AtomicInteger>builder()
+ .setObject(obj)
+ .setLock(lock)
+ .get();
+ // @formatter:on
+ lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
+ assertEquals(1, obj.get());
+ lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
+ assertEquals(2, obj.get());
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ void testBuilderReentrantLockVisitor(final boolean fair) {
+ final AtomicInteger obj = new AtomicInteger();
+ final ReentrantLock lock = new ReentrantLock(fair);
+ // @formatter:off
+ final LockingVisitors.ReentrantLockVisitor<AtomicInteger> lockVisitor
= ReentrantLockVisitor.<AtomicInteger>builder()
+ .setObject(obj)
+ .setLock(lock)
+ .get();
+ // @formatter:on
+ lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
+ assertEquals(1, obj.get());
+ lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
+ assertEquals(2, obj.get());
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ void testBuilderReentrantReadWriteLockVisitor(final boolean fair) {
+ final AtomicInteger obj = new AtomicInteger();
+ final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair);
+ // @formatter:off
+ final LockingVisitors.ReadWriteLockVisitor<AtomicInteger> lockVisitor
= ReadWriteLockVisitor.<AtomicInteger>builder()
+ .setObject(obj)
+ .setLock(lock)
+ .get();
+ // @formatter:on
+ lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
+ assertEquals(1, obj.get());
+ lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
+ assertEquals(2, obj.get());
+ }
+
+ @Test
+ void testBuilderReentrantStampedLockVisitor() {
+ final AtomicInteger obj = new AtomicInteger();
+ final StampedLock lock = new StampedLock();
+ // @formatter:off
+ final LockingVisitors.StampedLockVisitor<AtomicInteger> lockVisitor =
StampedLockVisitor.<AtomicInteger>builder()
+ .setObject(obj)
+ .setLock(lock)
+ .get();
+ // @formatter:on
+ lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
+ assertEquals(1, obj.get());
+ lockVisitor.acceptReadLocked(AtomicInteger::incrementAndGet);
+ assertEquals(2, obj.get());
+ }
+
@Test
void testCreate() {
- final AtomicInteger res = new AtomicInteger();
- final ReadWriteLock rwLock = new ReentrantReadWriteLock();
- LockingVisitors.create(res,
rwLock).acceptReadLocked(AtomicInteger::incrementAndGet);
- LockingVisitors.create(res, rwLock).acceptReadLocked(null);
- assertEquals(1, res.get());
- LockingVisitors.create(res,
rwLock).acceptWriteLocked(AtomicInteger::incrementAndGet);
- LockingVisitors.create(res, rwLock).acceptWriteLocked(null);
- assertEquals(2, res.get());
+ final AtomicInteger obj = new AtomicInteger();
+ final ReadWriteLock lock = new ReentrantReadWriteLock();
+ LockingVisitors.create(obj,
lock).acceptReadLocked(AtomicInteger::incrementAndGet);
+ LockingVisitors.create(obj, lock).acceptReadLocked(null);
+ assertEquals(1, obj.get());
+ LockingVisitors.create(obj,
lock).acceptWriteLocked(AtomicInteger::incrementAndGet);
+ LockingVisitors.create(obj, lock).acceptWriteLocked(null);
+ assertEquals(2, obj.get());
}
@SuppressWarnings("deprecation")
@@ -114,35 +203,35 @@ void testDeprecatedConstructor() {
}
@Test
- void testReentrantReadWriteLockExclusive() throws Exception {
- // If our threads are running concurrently, then we expect to be no
faster than running one after the other.
+ void testReentrantLock() throws Exception {
+ // If our threads are running concurrently, then we expect to be
faster than running one after the other.
final boolean[] booleanValues = new boolean[10];
- runTest(DELAY, true, millis -> assertTrue(millis >=
TOTAL_DELAY.toMillis()), booleanValues,
- LockingVisitors.reentrantReadWriteLockVisitor(booleanValues));
+ runTest(DELAY, false, millis -> assertTrue(millis <
TOTAL_DELAY.toMillis()), booleanValues,
LockingVisitors.reentrantLockVisitor(booleanValues));
}
- @Test
- void testReentrantReadWriteLockNotExclusive() throws Exception {
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ void testReentrantLockFairness(final boolean fairness) throws Exception {
// If our threads are running concurrently, then we expect to be
faster than running one after the other.
final boolean[] booleanValues = new boolean[10];
runTest(DELAY, false, millis -> assertTrue(millis <
TOTAL_DELAY.toMillis()), booleanValues,
- LockingVisitors.reentrantReadWriteLockVisitor(booleanValues));
+ LockingVisitors.create(booleanValues, new
ReentrantLock(fairness)));
}
@Test
- void testReentrantLock() throws Exception {
- // If our threads are running concurrently, then we expect to be
faster than running one after the other.
+ void testReentrantReadWriteLockExclusive() throws Exception {
+ // If our threads are running concurrently, then we expect to be no
faster than running one after the other.
final boolean[] booleanValues = new boolean[10];
- runTest(DELAY, false, millis -> assertTrue(millis <
TOTAL_DELAY.toMillis()), booleanValues,
LockingVisitors.reentrantLockVisitor(booleanValues));
+ runTest(DELAY, true, millis -> assertTrue(millis >=
TOTAL_DELAY.toMillis()), booleanValues,
+ LockingVisitors.reentrantReadWriteLockVisitor(booleanValues));
}
- @ParameterizedTest
- @ValueSource(booleans = { true, false })
- void testReentrantLockFairness(final boolean fairness) throws Exception {
+ @Test
+ void testReentrantReadWriteLockNotExclusive() throws Exception {
// If our threads are running concurrently, then we expect to be
faster than running one after the other.
final boolean[] booleanValues = new boolean[10];
runTest(DELAY, false, millis -> assertTrue(millis <
TOTAL_DELAY.toMillis()), booleanValues,
- LockingVisitors.create(booleanValues, new
ReentrantLock(fairness)));
+ LockingVisitors.reentrantReadWriteLockVisitor(booleanValues));
}
@Test