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 df3e2715f Add TimedSemaphore.builder() and Builder
df3e2715f is described below

commit df3e2715f41263d8998b7350301d106967748c0e
Author: Gary D. Gregory <[email protected]>
AuthorDate: Tue Oct 14 16:15:35 2025 -0400

    Add TimedSemaphore.builder() and Builder
---
 src/changes/changes.xml                            |   1 +
 .../commons/lang3/concurrent/TimedSemaphore.java   | 114 ++++++++++++++++++---
 .../lang3/concurrent/TimedSemaphoreTest.java       |  84 ++++++---------
 3 files changed, 130 insertions(+), 69 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 95a239b84..fdd80bc2d 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -59,6 +59,7 @@ The <action> type attribute can be add,update,fix,remove.
     <action                   type="add" dev="ggregory" due-to="Gary 
Gregory">Add JavaVersion.JAVA_25.</action>
     <action                   type="add" dev="ggregory" due-to="Gary 
Gregory">Add SystemUtils.IS_JAVA_25.</action>
     <action                   type="add" dev="ggregory" due-to="jack5505, Gary 
Gregory">Add MutablePair.ofNonNull(Map.Entry).</action>
+    <action                   type="add" dev="ggregory" due-to="Gary 
Gregory">Add TimedSemaphore.builder() and Builder.</action>
     <!-- UPDATE -->
     <action                   type="update" dev="ggregory" due-to="Gary 
Gregory">Bump org.apache.commons:commons-parent from 88 to 89.</action>
   </release>
diff --git 
a/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java 
b/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java
index 10ffef62c..3960c64ba 100644
--- a/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java
+++ b/src/main/java/org/apache/commons/lang3/concurrent/TimedSemaphore.java
@@ -21,6 +21,7 @@
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
 
 import org.apache.commons.lang3.Validate;
 
@@ -111,6 +112,75 @@
  */
 public class TimedSemaphore {
 
+    /**
+     * Builds new {@link TimedSemaphore}.
+     *
+     * @since 3.20.0
+     */
+    public static class Builder implements Supplier<TimedSemaphore> {
+
+        private ScheduledExecutorService service;
+        private long period;
+        private TimeUnit timeUnit;
+        private int limit;
+
+        /**
+         * Constructs a new Builder.
+         */
+        public Builder() {
+            // empty
+        }
+
+        @Override
+        public TimedSemaphore get() {
+            return new TimedSemaphore(this);
+        }
+
+        /**
+         * Sets the limit.
+         *
+         * @param limit The limit.
+         * @return {@code this} instance.
+         */
+        public Builder setLimit(final int limit) {
+            this.limit = limit;
+            return this;
+        }
+
+        /**
+         * Sets the time period.
+         *
+         * @param period The time period.
+         * @return {@code this} instance.
+         */
+        public Builder setPeriod(final long period) {
+            this.period = period;
+            return this;
+        }
+
+        /**
+         * Sets the executor service.
+         *
+         * @param service The executor service.
+         * @return {@code this} instance.
+         */
+        public Builder setService(final ScheduledExecutorService service) {
+            this.service = service;
+            return this;
+        }
+
+        /**
+         * Sets the time unit for the period.
+         *
+         * @param timeUnit The time unit for the period.
+         * @return {@code this} instance.
+         */
+        public Builder setTimeUnit(final TimeUnit timeUnit) {
+            this.timeUnit = timeUnit;
+            return this;
+        }
+    }
+
     /**
      * Constant for a value representing no limit. If the limit is set to a 
value less or equal this constant, the {@link TimedSemaphore} will be 
effectively
      * switched off.
@@ -120,10 +190,20 @@ public class TimedSemaphore {
     /** Constant for the thread pool size for the executor. */
     private static final int THREAD_POOL_SIZE = 1;
 
+    /**
+     * Constructs a new Builder.
+     *
+     * @return a new Builder.
+     * @since 3.20.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     /** The executor service for managing the timer thread. */
     private final ScheduledExecutorService executorService;
 
-    /** Stores the period for this timed semaphore. */
+    /** The period for this timed semaphore. */
     private final long period;
 
     /** The time unit for the period. */
@@ -155,6 +235,23 @@ public class TimedSemaphore {
     /** A flag whether shutdown() was called. */
     private boolean shutdown; // @GuardedBy("this")
 
+    private TimedSemaphore(final Builder builder) {
+        Validate.inclusiveBetween(1, Long.MAX_VALUE, builder.period, "Time 
period must be greater than 0.");
+        period = builder.period;
+        unit = builder.timeUnit;
+        if (builder.service != null) {
+            executorService = builder.service;
+            ownExecutor = false;
+        } else {
+            final ScheduledThreadPoolExecutor stpe = new 
ScheduledThreadPoolExecutor(THREAD_POOL_SIZE);
+            stpe.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
+            stpe.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+            executorService = stpe;
+            ownExecutor = true;
+        }
+        setLimit(builder.limit);
+    }
+
     /**
      * Constructs a new instance of {@link TimedSemaphore} and initializes it 
with the given time period and the limit.
      *
@@ -178,20 +275,7 @@ public TimedSemaphore(final long timePeriod, final 
TimeUnit timeUnit, final int
      * @throws IllegalArgumentException if the period is less or equals 0.
      */
     public TimedSemaphore(final ScheduledExecutorService service, final long 
timePeriod, final TimeUnit timeUnit, final int limit) {
-        Validate.inclusiveBetween(1, Long.MAX_VALUE, timePeriod, "Time period 
must be greater than 0!");
-        period = timePeriod;
-        unit = timeUnit;
-        if (service != null) {
-            executorService = service;
-            ownExecutor = false;
-        } else {
-            final ScheduledThreadPoolExecutor s = new 
ScheduledThreadPoolExecutor(THREAD_POOL_SIZE);
-            s.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
-            s.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
-            executorService = s;
-            ownExecutor = true;
-        }
-        setLimit(limit);
+        
this(builder().setService(service).setPeriod(timePeriod).setTimeUnit(timeUnit).setLimit(limit));
     }
 
     /**
diff --git 
a/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java 
b/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java
index f6b4e2062..6cd284426 100644
--- a/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java
+++ b/src/test/java/org/apache/commons/lang3/concurrent/TimedSemaphoreTest.java
@@ -36,15 +36,17 @@
 import org.junit.jupiter.api.Test;
 
 /**
- * Test class for TimedSemaphore.
+ * Tests {@link TimedSemaphore}.
  */
 class TimedSemaphoreTest extends AbstractLangTest {
+
     /**
      * A test thread class that will be used by tests for triggering the
      * semaphore. The thread calls the semaphore a configurable number of 
times.
      * When this is done, it cannotify the main thread.
      */
     private static final class SemaphoreThread extends Thread {
+
         /** The semaphore. */
         private final TimedSemaphore semaphore;
 
@@ -90,6 +92,7 @@ public void run() {
      * test.
      */
     private static final class TimedSemaphoreTestImpl extends TimedSemaphore {
+
         /** A mock scheduled future. */
         ScheduledFuture<?> schedFuture;
 
@@ -99,13 +102,11 @@ private static final class TimedSemaphoreTestImpl extends 
TimedSemaphore {
         /** Counter for the endOfPeriod() invocations. */
         private int periodEnds;
 
-        TimedSemaphoreTestImpl(final long timePeriod, final TimeUnit timeUnit,
-                final int limit) {
+        TimedSemaphoreTestImpl(final long timePeriod, final TimeUnit timeUnit, 
final int limit) {
             super(timePeriod, timeUnit, limit);
         }
 
-        TimedSemaphoreTestImpl(final ScheduledExecutorService service,
-                final long timePeriod, final TimeUnit timeUnit, final int 
limit) {
+        TimedSemaphoreTestImpl(final ScheduledExecutorService service, final 
long timePeriod, final TimeUnit timeUnit, final int limit) {
             super(service, timePeriod, timeUnit, limit);
         }
 
@@ -156,6 +157,7 @@ protected ScheduledFuture<?> startTimer() {
      * records the return value.
      */
     private static final class TryAcquireThread extends Thread {
+
         /** The semaphore. */
         private final TimedSemaphore semaphore;
 
@@ -201,8 +203,7 @@ public void run() {
      */
     private void prepareStartTimer(final ScheduledExecutorService service,
             final ScheduledFuture<?> future) {
-        service.scheduleAtFixedRate((Runnable) EasyMock.anyObject(), EasyMock
-                .eq(PERIOD_MILLIS), EasyMock.eq(PERIOD_MILLIS), 
EasyMock.eq(UNIT));
+        service.scheduleAtFixedRate((Runnable) EasyMock.anyObject(), 
EasyMock.eq(PERIOD_MILLIS), EasyMock.eq(PERIOD_MILLIS), EasyMock.eq(UNIT));
         EasyMock.expectLastCall().andReturn(future);
     }
 
@@ -213,24 +214,20 @@ private void prepareStartTimer(final 
ScheduledExecutorService service,
      */
     @Test
     void testAcquireLimit() throws InterruptedException {
-        final ScheduledExecutorService service = EasyMock
-                .createMock(ScheduledExecutorService.class);
+        final ScheduledExecutorService service = 
EasyMock.createMock(ScheduledExecutorService.class);
         final ScheduledFuture<?> future = 
EasyMock.createMock(ScheduledFuture.class);
         prepareStartTimer(service, future);
         EasyMock.replay(service, future);
         final int count = 10;
         final CountDownLatch latch = new CountDownLatch(count - 1);
         final TimedSemaphore semaphore = new TimedSemaphore(service, 
PERIOD_MILLIS, UNIT, 1);
-        final SemaphoreThread t = new SemaphoreThread(semaphore, latch, count,
-                count - 1);
+        final SemaphoreThread t = new SemaphoreThread(semaphore, latch, count, 
count - 1);
         semaphore.setLimit(count - 1);
-
         // start a thread that calls the semaphore count times
         t.start();
         latch.await();
         // now the semaphore's limit should be reached and the thread blocked
         assertEquals(count - 1, semaphore.getAcquireCount(), "Wrong semaphore 
count");
-
         // this wakes up the thread, it should call the semaphore once more
         semaphore.endOfPeriod();
         t.join();
@@ -250,8 +247,7 @@ void testAcquireLimit() throws InterruptedException {
     @Test
     void testAcquireMultiplePeriods() throws InterruptedException {
         final int count = 1000;
-        final TimedSemaphoreTestImpl semaphore = new TimedSemaphoreTestImpl(
-                PERIOD_MILLIS / 10, TimeUnit.MILLISECONDS, 1);
+        final TimedSemaphoreTestImpl semaphore = new 
TimedSemaphoreTestImpl(PERIOD_MILLIS / 10, TimeUnit.MILLISECONDS, 1);
         semaphore.setLimit(count / 4);
         final CountDownLatch latch = new CountDownLatch(count);
         final SemaphoreThread t = new SemaphoreThread(semaphore, latch, count, 
count);
@@ -271,13 +267,11 @@ void testAcquireMultiplePeriods() throws 
InterruptedException {
      */
     @Test
     void testAcquireMultipleThreads() throws InterruptedException {
-        final ScheduledExecutorService service = EasyMock
-                .createMock(ScheduledExecutorService.class);
+        final ScheduledExecutorService service = 
EasyMock.createMock(ScheduledExecutorService.class);
         final ScheduledFuture<?> future = 
EasyMock.createMock(ScheduledFuture.class);
         prepareStartTimer(service, future);
         EasyMock.replay(service, future);
-        final TimedSemaphoreTestImpl semaphore = new 
TimedSemaphoreTestImpl(service,
-                PERIOD_MILLIS, UNIT, 1);
+        final TimedSemaphoreTestImpl semaphore = new 
TimedSemaphoreTestImpl(service, PERIOD_MILLIS, UNIT, 1);
         semaphore.latch = new CountDownLatch(1);
         final int count = 10;
         final SemaphoreThread[] threads = new SemaphoreThread[count];
@@ -307,13 +301,11 @@ void testAcquireMultipleThreads() throws 
InterruptedException {
      */
     @Test
     void testAcquireNoLimit() throws InterruptedException {
-        final ScheduledExecutorService service = EasyMock
-                .createMock(ScheduledExecutorService.class);
+        final ScheduledExecutorService service = 
EasyMock.createMock(ScheduledExecutorService.class);
         final ScheduledFuture<?> future = 
EasyMock.createMock(ScheduledFuture.class);
         prepareStartTimer(service, future);
         EasyMock.replay(service, future);
-        final TimedSemaphoreTestImpl semaphore = new 
TimedSemaphoreTestImpl(service,
-                PERIOD_MILLIS, UNIT, TimedSemaphore.NO_LIMIT);
+        final TimedSemaphoreTestImpl semaphore = new 
TimedSemaphoreTestImpl(service, PERIOD_MILLIS, UNIT, TimedSemaphore.NO_LIMIT);
         final int count = 1000;
         final CountDownLatch latch = new CountDownLatch(count);
         final SemaphoreThread t = new SemaphoreThread(semaphore, latch, count, 
count);
@@ -329,13 +321,11 @@ void testAcquireNoLimit() throws InterruptedException {
      */
     @Test
     void testGetAvailablePermits() throws InterruptedException {
-        final ScheduledExecutorService service = EasyMock
-                .createMock(ScheduledExecutorService.class);
+        final ScheduledExecutorService service = 
EasyMock.createMock(ScheduledExecutorService.class);
         final ScheduledFuture<?> future = 
EasyMock.createMock(ScheduledFuture.class);
         prepareStartTimer(service, future);
         EasyMock.replay(service, future);
-        final TimedSemaphore semaphore = new TimedSemaphore(service, 
PERIOD_MILLIS, UNIT,
-                LIMIT);
+        final TimedSemaphore semaphore = new TimedSemaphore(service, 
PERIOD_MILLIS, UNIT, LIMIT);
         for (int i = 0; i < LIMIT; i++) {
             assertEquals(LIMIT - i, semaphore.getAvailablePermits(), "Wrong 
available count at " + i);
             semaphore.acquire();
@@ -352,13 +342,11 @@ void testGetAvailablePermits() throws 
InterruptedException {
      */
     @Test
     void testGetAverageCallsPerPeriod() throws InterruptedException {
-        final ScheduledExecutorService service = EasyMock
-                .createMock(ScheduledExecutorService.class);
+        final ScheduledExecutorService service = 
EasyMock.createMock(ScheduledExecutorService.class);
         final ScheduledFuture<?> future = 
EasyMock.createMock(ScheduledFuture.class);
         prepareStartTimer(service, future);
         EasyMock.replay(service, future);
-        final TimedSemaphore semaphore = new TimedSemaphore(service, 
PERIOD_MILLIS, UNIT,
-                LIMIT);
+        final TimedSemaphore semaphore = new TimedSemaphore(service, 
PERIOD_MILLIS, UNIT, LIMIT);
         semaphore.acquire();
         semaphore.endOfPeriod();
         assertEquals(1.0, semaphore.getAverageCallsPerPeriod(), .005, "Wrong 
average (1)");
@@ -374,11 +362,9 @@ void testGetAverageCallsPerPeriod() throws 
InterruptedException {
      */
     @Test
     void testInit() {
-        final ScheduledExecutorService service = EasyMock
-                .createMock(ScheduledExecutorService.class);
+        final ScheduledExecutorService service = 
EasyMock.createMock(ScheduledExecutorService.class);
         EasyMock.replay(service);
-        final TimedSemaphore semaphore = new TimedSemaphore(service, 
PERIOD_MILLIS, UNIT,
-                LIMIT);
+        final TimedSemaphore semaphore = new TimedSemaphore(service, 
PERIOD_MILLIS, UNIT, LIMIT);
         EasyMock.verify(service);
         assertEquals(service, semaphore.getExecutorService(), "Wrong service");
         assertEquals(PERIOD_MILLIS, semaphore.getPeriod(), "Wrong period");
@@ -396,8 +382,7 @@ void testInit() {
     @Test
     void testInitDefaultService() {
         final TimedSemaphore semaphore = new TimedSemaphore(PERIOD_MILLIS, 
UNIT, LIMIT);
-        final ScheduledThreadPoolExecutor exec = (ScheduledThreadPoolExecutor) 
semaphore
-                .getExecutorService();
+        final ScheduledThreadPoolExecutor exec = (ScheduledThreadPoolExecutor) 
semaphore.getExecutorService();
         
assertFalse(exec.getContinueExistingPeriodicTasksAfterShutdownPolicy(), "Wrong 
periodic task policy");
         assertFalse(exec.getExecuteExistingDelayedTasksAfterShutdownPolicy(), 
"Wrong delayed task policy");
         assertFalse(exec.isShutdown(), "Already shutdown");
@@ -430,14 +415,12 @@ void testPassAfterShutdown() {
      */
     @Test
     void testShutdownMultipleTimes() throws InterruptedException {
-        final ScheduledExecutorService service = EasyMock
-                .createMock(ScheduledExecutorService.class);
+        final ScheduledExecutorService service = 
EasyMock.createMock(ScheduledExecutorService.class);
         final ScheduledFuture<?> future = 
EasyMock.createMock(ScheduledFuture.class);
         prepareStartTimer(service, future);
         
EasyMock.expect(Boolean.valueOf(future.cancel(false))).andReturn(Boolean.TRUE);
         EasyMock.replay(service, future);
-        final TimedSemaphoreTestImpl semaphore = new 
TimedSemaphoreTestImpl(service,
-                PERIOD_MILLIS, UNIT, LIMIT);
+        final TimedSemaphoreTestImpl semaphore = new 
TimedSemaphoreTestImpl(service, PERIOD_MILLIS, UNIT, LIMIT);
         semaphore.acquire();
         for (int i = 0; i < 10; i++) {
             semaphore.shutdown();
@@ -463,11 +446,9 @@ void testShutdownOwnExecutor() {
      */
     @Test
     void testShutdownSharedExecutorNoTask() {
-        final ScheduledExecutorService service = EasyMock
-                .createMock(ScheduledExecutorService.class);
+        final ScheduledExecutorService service = 
EasyMock.createMock(ScheduledExecutorService.class);
         EasyMock.replay(service);
-        final TimedSemaphore semaphore = new TimedSemaphore(service, 
PERIOD_MILLIS, UNIT,
-                LIMIT);
+        final TimedSemaphore semaphore = new TimedSemaphore(service, 
PERIOD_MILLIS, UNIT, LIMIT);
         semaphore.shutdown();
         assertTrue(semaphore.isShutdown(), "Not shutdown");
         EasyMock.verify(service);
@@ -481,14 +462,12 @@ void testShutdownSharedExecutorNoTask() {
      */
     @Test
     void testShutdownSharedExecutorTask() throws InterruptedException {
-        final ScheduledExecutorService service = EasyMock
-                .createMock(ScheduledExecutorService.class);
+        final ScheduledExecutorService service = 
EasyMock.createMock(ScheduledExecutorService.class);
         final ScheduledFuture<?> future = 
EasyMock.createMock(ScheduledFuture.class);
         prepareStartTimer(service, future);
         
EasyMock.expect(Boolean.valueOf(future.cancel(false))).andReturn(Boolean.TRUE);
         EasyMock.replay(service, future);
-        final TimedSemaphoreTestImpl semaphore = new 
TimedSemaphoreTestImpl(service,
-                PERIOD_MILLIS, UNIT, LIMIT);
+        final TimedSemaphoreTestImpl semaphore = new 
TimedSemaphoreTestImpl(service, PERIOD_MILLIS, UNIT, LIMIT);
         semaphore.acquire();
         semaphore.shutdown();
         assertTrue(semaphore.isShutdown(), "Not shutdown");
@@ -502,8 +481,7 @@ void testShutdownSharedExecutorTask() throws 
InterruptedException {
      */
     @Test
     void testStartTimer() throws InterruptedException {
-        final TimedSemaphoreTestImpl semaphore = new 
TimedSemaphoreTestImpl(PERIOD_MILLIS,
-                UNIT, LIMIT);
+        final TimedSemaphoreTestImpl semaphore = new 
TimedSemaphoreTestImpl(PERIOD_MILLIS, UNIT, LIMIT);
         final ScheduledFuture<?> future = semaphore.startTimer();
         assertNotNull(future, "No future returned");
         ThreadUtils.sleepQuietly(DURATION);
@@ -522,15 +500,13 @@ void testStartTimer() throws InterruptedException {
      */
     @Test
     void testTryAcquire() throws InterruptedException {
-        final TimedSemaphore semaphore = new TimedSemaphore(PERIOD_MILLIS, 
TimeUnit.SECONDS,
-                LIMIT);
+        final TimedSemaphore semaphore = new TimedSemaphore(PERIOD_MILLIS, 
TimeUnit.SECONDS, LIMIT);
         final TryAcquireThread[] threads = new TryAcquireThread[3 * LIMIT];
         final CountDownLatch latch = new CountDownLatch(1);
         for (int i = 0; i < threads.length; i++) {
             threads[i] = new TryAcquireThread(semaphore, latch);
             threads[i].start();
         }
-
         latch.countDown();
         int permits = 0;
         for (final TryAcquireThread t : threads) {

Reply via email to