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

commit da246244aebf91571b0689e3dee97fce0ad52b23
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Sun Sep 22 08:11:40 2024 -0400

    Add StopWatch.run([Failable]Runnable) and get([Failable]Supplier)
---
 pom.xml                                            |   6 ++
 src/changes/changes.xml                            |   1 +
 .../org/apache/commons/lang3/time/StopWatch.java   | 102 +++++++++++++++++--
 .../apache/commons/lang3/time/StopWatchTest.java   | 109 +++++++++++++++------
 4 files changed, 179 insertions(+), 39 deletions(-)

diff --git a/pom.xml b/pom.xml
index cd18b14ca..496eff197 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,6 +82,12 @@
       <version>5.4.0</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>4.11.0</version>
+      <scope>test</scope>
+    </dependency>
     <!-- For Javadoc links -->
     <dependency>
       <groupId>org.apache.commons</groupId>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index b9952c966..17afee8a0 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -54,6 +54,7 @@ The <action> type attribute can be add,update,fix,remove.
     <action                   type="fix" dev="ggregory" due-to="OSS-Fuzz, Gary 
Gregory">Rewrite ClassUtils.getClass(...) without recursion to avoid 
StackOverflowError on very long inputs. OSS-Fuzz Issue 42522972: 
apache-commons-text:StringSubstitutorInterpolatorFuzzer: Security exception in 
org.apache.commons.lang3.ClassUtils.getClass.</action>
     <!-- ADD -->
     <action                   type="add" dev="ggregory" due-to="Gary 
Gregory">Add Strings and refactor StringUtils.</action>
+    <action                   type="add" dev="ggregory" due-to="Gary 
Gregory">Add StopWatch.run([Failable]Runnable) and 
get([Failable]Supplier).</action>
     <!-- UPDATE -->
     <action                   type="update" dev="ggregory" due-to="Gary 
Gregory, Dependabot">Bump org.apache.commons:commons-parent from 73 to 75 
#1267, #1277.</action>
   </release>
diff --git a/src/main/java/org/apache/commons/lang3/time/StopWatch.java 
b/src/main/java/org/apache/commons/lang3/time/StopWatch.java
index 6164c9932..5b81fa94c 100644
--- a/src/main/java/org/apache/commons/lang3/time/StopWatch.java
+++ b/src/main/java/org/apache/commons/lang3/time/StopWatch.java
@@ -21,10 +21,12 @@ import java.time.Duration;
 import java.time.Instant;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.function.FailableConsumer;
 import org.apache.commons.lang3.function.FailableRunnable;
+import org.apache.commons.lang3.function.FailableSupplier;
 
 /**
  * {@link StopWatch} provides a convenient API for timings.
@@ -286,6 +288,23 @@ public class StopWatch {
         return DurationFormatUtils.formatDurationHMS(getTime());
     }
 
+    /**
+     * Delegates to {@link Supplier#get()} while recording the duration of the 
call.
+     *
+     * @param <T>      the type of results supplied by this supplier.
+     * @param supplier The supplier to {@link Supplier#get()}.
+     * @return a result from the given Supplier.
+     * @since 3.18.0
+     */
+    public <T> T get(final Supplier<T> supplier) {
+        startResume();
+        try {
+            return supplier.get();
+        } finally {
+            suspend();
+        }
+    }
+
     /**
      * Gets the Duration on the StopWatch.
      *
@@ -322,14 +341,16 @@ public class StopWatch {
      * @since 3.0
      */
     public long getNanoTime() {
-        if (runningState == State.STOPPED || runningState == State.SUSPENDED) {
+        switch (runningState) {
+        case STOPPED:
+        case SUSPENDED:
             return stopTimeNanos - startTimeNanos;
-        }
-        if (runningState == State.UNSTARTED) {
+        case UNSTARTED:
             return 0;
-        }
-        if (runningState == State.RUNNING) {
+        case RUNNING:
             return System.nanoTime() - startTimeNanos;
+        default:
+            break;
         }
         throw new IllegalStateException("Illegal running state has occurred.");
     }
@@ -443,6 +464,25 @@ public class StopWatch {
         return stopInstant.toEpochMilli();
     }
 
+    /**
+     * Delegates to {@link FailableSupplier#get()} while recording the 
duration of the call.
+     *
+     * @param <T>      the type of results supplied by this supplier.
+     * @param <E>      The kind of thrown exception or error.
+     * @param supplier The supplier to {@link Supplier#get()}.
+     * @return a result from the given Supplier.
+     * @throws Throwable if the supplier fails.
+     * @since 3.18.0
+     */
+    public <T, E extends Throwable> T getT(final FailableSupplier<T, E> 
supplier) throws Throwable {
+        startResume();
+        try {
+            return supplier.get();
+        } finally {
+            suspend();
+        }
+    }
+
     /**
      * Gets the time on the StopWatch.
      *
@@ -544,6 +584,38 @@ public class StopWatch {
         runningState = State.RUNNING;
     }
 
+    /**
+     * Delegates to {@link Runnable#run()} while recording the duration of the 
call.
+     *
+     * @param runnable The runnable to {@link Runnable#run()}.
+     * @since 3.18.0
+     */
+    public void run(final Runnable runnable) {
+        startResume();
+        try {
+            runnable.run();
+        } finally {
+            suspend();
+        }
+    }
+
+    /**
+     * Delegates to {@link FailableRunnable#run()} while recording the 
duration of the call.
+     *
+     * @param <E>      The kind of {@link Throwable}.
+     * @param runnable The runnable to {@link FailableRunnable#run()}.
+     * @throws Throwable Thrown by {@link FailableRunnable#run()}.
+     * @since 3.18.0
+     */
+    public <E extends Throwable> void runT(final FailableRunnable<E> runnable) 
throws Throwable {
+        startResume();
+        try {
+            runnable.run();
+        } finally {
+            suspend();
+        }
+    }
+
     /**
      * Splits the time.
      *
@@ -563,7 +635,7 @@ public class StopWatch {
     }
 
     /**
-     * Starts the StopWatch.
+     * Starts this StopWatch.
      *
      * <p>
      * This method starts a new timing session, clearing any previous values.
@@ -584,7 +656,18 @@ public class StopWatch {
     }
 
     /**
-     * Stops the StopWatch.
+     * Starts or resumes this StopWatch.
+     */
+    private void startResume() {
+        if (isStopped()) {
+            start();
+        } else if (isSuspended()) {
+            resume();
+        }
+    }
+
+    /**
+     * Stops this StopWatch.
      *
      * <p>
      * This method ends a new timing session, allowing the time to be 
retrieved.
@@ -604,7 +687,7 @@ public class StopWatch {
     }
 
     /**
-     * Suspends the StopWatch for later resumption.
+     * Suspends this StopWatch for later resumption.
      *
      * <p>
      * This method suspends the watch until it is resumed. The watch will not 
include time between the suspend and resume calls in the total time.
@@ -656,7 +739,7 @@ public class StopWatch {
     }
 
     /**
-     * Removes a split.
+     * Removes the split.
      *
      * <p>
      * This method clears the stop time. The start time is unaffected, 
enabling timing from the original start point to continue.
@@ -671,4 +754,5 @@ public class StopWatch {
         splitState = SplitState.UNSPLIT;
     }
 
+
 }
diff --git a/src/test/java/org/apache/commons/lang3/time/StopWatchTest.java 
b/src/test/java/org/apache/commons/lang3/time/StopWatchTest.java
index 04986fba3..48f06ed44 100644
--- a/src/test/java/org/apache/commons/lang3/time/StopWatchTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/StopWatchTest.java
@@ -27,9 +27,11 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.io.IOException;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.commons.lang3.AbstractLangTest;
 import org.apache.commons.lang3.ThreadUtils;
@@ -50,17 +52,13 @@ public class StopWatchTest extends AbstractLangTest {
 
     /**
      * <p>
-     * Creates a suspended StopWatch object which appears to have elapsed for 
the requested amount of time in
-     * nanoseconds.
+     * Creates a suspended StopWatch object which appears to have elapsed for 
the requested amount of time in nanoseconds.
      * <p>
      * <p>
      *
      * <pre>
      * // Create a mock StopWatch with a time of 2:59:01.999
-     * final long nanos = TimeUnit.HOURS.toNanos(2)
-     *         + TimeUnit.MINUTES.toNanos(59)
-     *         + TimeUnit.SECONDS.toNanos(1)
-     *         + TimeUnit.MILLISECONDS.toNanos(999);
+     * final long nanos = TimeUnit.HOURS.toNanos(2) + 
TimeUnit.MINUTES.toNanos(59) + TimeUnit.SECONDS.toNanos(1) + 
TimeUnit.MILLISECONDS.toNanos(999);
      * final StopWatch watch = createMockStopWatch(nanos);
      * </pre>
      *
@@ -100,42 +98,31 @@ public class StopWatchTest extends AbstractLangTest {
     @Test
     public void testBadStates() {
         final StopWatch watch = new StopWatch();
-        assertThrows(IllegalStateException.class, watch::stop,
-            "Calling stop on an unstarted StopWatch should throw an exception. 
");
+        assertThrows(IllegalStateException.class, watch::stop, "Calling stop 
on an unstarted StopWatch should throw an exception. ");
 
-        assertThrows(IllegalStateException.class, watch::suspend,
-            "Calling suspend on an unstarted StopWatch should throw an 
exception. ");
+        assertThrows(IllegalStateException.class, watch::suspend, "Calling 
suspend on an unstarted StopWatch should throw an exception. ");
 
-        assertThrows(IllegalStateException.class, watch::split,
-            "Calling split on a non-running StopWatch should throw an 
exception. ");
+        assertThrows(IllegalStateException.class, watch::split, "Calling split 
on a non-running StopWatch should throw an exception. ");
 
-        assertThrows(IllegalStateException.class, watch::unsplit,
-            "Calling unsplit on an unsplit StopWatch should throw an 
exception. ");
+        assertThrows(IllegalStateException.class, watch::unsplit, "Calling 
unsplit on an unsplit StopWatch should throw an exception. ");
 
-        assertThrows(IllegalStateException.class, watch::resume,
-            "Calling resume on an unsuspended StopWatch should throw an 
exception. ");
+        assertThrows(IllegalStateException.class, watch::resume, "Calling 
resume on an unsuspended StopWatch should throw an exception. ");
 
         watch.start();
 
-        assertThrows(IllegalStateException.class, watch::start,
-            "Calling start on a started StopWatch should throw an exception. 
");
+        assertThrows(IllegalStateException.class, watch::start, "Calling start 
on a started StopWatch should throw an exception. ");
 
-        assertThrows(IllegalStateException.class, watch::unsplit,
-            "Calling unsplit on an unsplit StopWatch should throw an 
exception. ");
+        assertThrows(IllegalStateException.class, watch::unsplit, "Calling 
unsplit on an unsplit StopWatch should throw an exception. ");
 
-        assertThrows(IllegalStateException.class, watch::getSplitTime,
-                "Calling getSplitTime on an unsplit StopWatch should throw an 
exception. ");
+        assertThrows(IllegalStateException.class, watch::getSplitTime, 
"Calling getSplitTime on an unsplit StopWatch should throw an exception. ");
 
-        assertThrows(IllegalStateException.class, watch::getSplitDuration,
-                "Calling getSplitTime on an unsplit StopWatch should throw an 
exception. ");
+        assertThrows(IllegalStateException.class, watch::getSplitDuration, 
"Calling getSplitTime on an unsplit StopWatch should throw an exception. ");
 
-        assertThrows(IllegalStateException.class, watch::resume,
-            "Calling resume on an unsuspended StopWatch should throw an 
exception. ");
+        assertThrows(IllegalStateException.class, watch::resume, "Calling 
resume on an unsuspended StopWatch should throw an exception. ");
 
         watch.stop();
 
-        assertThrows(IllegalStateException.class, watch::start,
-            "Calling start on a stopped StopWatch should throw an exception as 
it needs to be reset. ");
+        assertThrows(IllegalStateException.class, watch::start, "Calling start 
on a stopped StopWatch should throw an exception as it needs to be reset. ");
     }
 
     @Test
@@ -197,6 +184,33 @@ public class StopWatchTest extends AbstractLangTest {
         assertThat("formatTime", formatTime, not(startsWith(MESSAGE)));
     }
 
+    @Test
+    public void testGet() throws Throwable {
+        final StopWatch watch = new StopWatch();
+        final AtomicInteger i = new AtomicInteger();
+        assertEquals(1, watch.get(i::incrementAndGet));
+        assertEquals(2, watch.getT(i::incrementAndGet));
+        final IOException e = assertThrows(IOException.class, () -> 
watch.getT(this::throwIOException));
+        assertEquals("A", e.getMessage());
+        // test state
+        assertTrue(watch.isSuspended());
+        assertEquals(3, watch.get(() -> {
+            assertTrue(watch.isStarted());
+            return i.incrementAndGet();
+        }));
+        assertTrue(watch.isSuspended());
+        final long nanos1 = watch.getDuration().toNanos();
+        assertTrue(nanos1 >= 0);
+        // test state
+        assertTrue(watch.isSuspended());
+        assertEquals(4, watch.getT(() -> {
+            assertTrue(watch.isStarted());
+            return i.incrementAndGet();
+        }));
+        assertTrue(watch.isSuspended());
+        assertTrue(watch.getDuration().toNanos() >= nanos1);
+    }
+
     @Test
     public void testGetDuration() throws InterruptedException {
         final StopWatch watch = new StopWatch();
@@ -301,6 +315,37 @@ public class StopWatchTest extends AbstractLangTest {
         assertThat("stopWatch.toSplitString", stopWatch.toSplitString(), 
startsWith(MESSAGE));
     }
 
+    @Test
+    public void testRun() throws Throwable {
+        final StopWatch watch = new StopWatch();
+        final AtomicInteger i = new AtomicInteger();
+        watch.run(i::incrementAndGet);
+        assertEquals(1, i.get());
+        watch.runT(i::incrementAndGet);
+        assertEquals(2, i.get());
+        final IOException e = assertThrows(IOException.class, () -> 
watch.runT(this::throwIOException));
+        assertEquals("A", e.getMessage());
+        // test state
+        assertTrue(watch.isSuspended());
+        watch.run(() -> {
+            assertTrue(watch.isStarted());
+            i.incrementAndGet();
+        });
+        assertEquals(3, i.get());
+        assertTrue(watch.isSuspended());
+        final long nanos1 = watch.getDuration().toNanos();
+        assertTrue(nanos1 > 0);
+        // test state
+        assertTrue(watch.isSuspended());
+        watch.runT(() -> {
+            assertTrue(watch.isStarted());
+            i.incrementAndGet();
+        });
+        assertEquals(4, i.get());
+        assertTrue(watch.isSuspended());
+        assertTrue(watch.getDuration().toNanos() >= nanos1);
+    }
+
     @Test
     public void testSimple() throws InterruptedException {
         final StopWatch watch = StopWatch.createStarted();
@@ -421,14 +466,14 @@ public class StopWatchTest extends AbstractLangTest {
         assertTrue(totalTimeFromNanos >= sleepMillisX2, () -> 
String.format("totalTimeFromNanos %s >= sleepMillisX2 %s", totalTimeFromNanos, 
sleepMillisX2));
         assertTrue(totalDuration.compareTo(Duration.ofMillis(sleepMillisX2)) 
>= 0,
                 () -> String.format("totalDuration >= sleepMillisX2", 
totalDuration, sleepMillisX2));
-        ;
+
         // Be lenient for slow running builds
         final long testTooLongMillis = sleepMillis * 100;
         assertTrue(totalTimeFromNanos < testTooLongMillis,
                 () -> String.format("totalTimeFromNanos %s < testTooLongMillis 
%s", totalTimeFromNanos, testTooLongMillis));
         
assertTrue(totalDuration.compareTo(Duration.ofMillis(testTooLongMillis)) < 0,
                 () -> String.format("totalDuration %s < testTooLongMillis %s", 
totalDuration, testTooLongMillis));
-        ;
+
     }
 
     @Test
@@ -471,4 +516,8 @@ public class StopWatchTest extends AbstractLangTest {
         final String splitStr = watch.toString();
         assertEquals(SPLIT_CLOCK_STR_LEN + MESSAGE.length() + 1, 
splitStr.length(), "Formatted split string not the correct length");
     }
+
+    private int throwIOException() throws IOException {
+        throw new IOException("A");
+    }
 }

Reply via email to