Skip to content

Commit ce2ba7b

Browse files
LANG-1504 - Adding labels to split StopWatch feature (#1473)
* Adding labels to split StopWatch feature * Adding labels to split StopWatch feature - refactor based on PR review * Update Javadoc @SInCE tags from 3.19.0 to 3.20.0 * Add testGetSplits to show NullPointerException * Adding labels to split StopWatch feature - refactor based on PR review * Assert that we can only unsplit once --------- Co-authored-by: edelgadoh <edelgadoh> Co-authored-by: Gary Gregory <[email protected]>
1 parent 1ac4de0 commit ce2ba7b

File tree

2 files changed

+144
-2
lines changed

2 files changed

+144
-2
lines changed

src/main/java/org/apache/commons/lang3/time/StopWatch.java

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919

2020
import java.time.Duration;
2121
import java.time.Instant;
22+
import java.util.ArrayList;
23+
import java.util.Collections;
24+
import java.util.List;
2225
import java.util.Objects;
2326
import java.util.concurrent.TimeUnit;
2427
import java.util.function.Supplier;
@@ -27,6 +30,7 @@
2730
import org.apache.commons.lang3.function.FailableConsumer;
2831
import org.apache.commons.lang3.function.FailableRunnable;
2932
import org.apache.commons.lang3.function.FailableSupplier;
33+
import org.apache.commons.lang3.tuple.ImmutablePair;
3034

3135
/**
3236
* {@link StopWatch} provides a convenient API for timings.
@@ -248,6 +252,11 @@ public static StopWatch createStarted() {
248252
*/
249253
private long stopTimeNanos;
250254

255+
/**
256+
* The split list.
257+
*/
258+
private final List<Split> splits = new ArrayList<>();
259+
251260
/**
252261
* Constructs a new instance.
253262
*/
@@ -326,6 +335,16 @@ public String getMessage() {
326335
return message;
327336
}
328337

338+
/**
339+
* Gets the split list.
340+
*
341+
* @return the list of splits.
342+
* @since 3.20.0
343+
*/
344+
public List<Split> getSplits() {
345+
return Collections.unmodifiableList(splits);
346+
}
347+
329348
/**
330349
* Gets the <em>elapsed</em> time in nanoseconds.
331350
*
@@ -382,7 +401,7 @@ public long getSplitNanoTime() {
382401
if (splitState != SplitState.SPLIT) {
383402
throw new IllegalStateException("Stopwatch must be split to get the split time.");
384403
}
385-
return stopTimeNanos - startTimeNanos;
404+
return splits.get(splits.size() - 1).getRight().toNanos();
386405
}
387406

388407
/**
@@ -557,6 +576,7 @@ private long nanosToMillis(final long nanos) {
557576
public void reset() {
558577
runningState = State.UNSTARTED;
559578
splitState = SplitState.UNSPLIT;
579+
splits.clear();
560580
}
561581

562582
/**
@@ -624,6 +644,28 @@ public void split() {
624644
}
625645
stopTimeNanos = System.nanoTime();
626646
splitState = SplitState.SPLIT;
647+
splits.add(new Split(String.valueOf(splits.size()), Duration.ofNanos(stopTimeNanos - startTimeNanos)));
648+
}
649+
650+
/**
651+
* Splits the time with a label.
652+
*
653+
* <p>
654+
* This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected, enabling {@link #unsplit()} to continue the
655+
* timing from the original start point.
656+
* </p>
657+
*
658+
* @param label A message for string presentation.
659+
* @throws IllegalStateException if the StopWatch is not running.
660+
* @since 3.20.0
661+
*/
662+
public void split(final String label) {
663+
if (runningState != State.RUNNING) {
664+
throw new IllegalStateException("Stopwatch is not running.");
665+
}
666+
stopTimeNanos = System.nanoTime();
667+
splitState = SplitState.SPLIT;
668+
splits.add(new Split(label, Duration.ofNanos(stopTimeNanos - startTimeNanos)));
627669
}
628670

629671
/**
@@ -645,6 +687,7 @@ public void start() {
645687
startTimeNanos = System.nanoTime();
646688
startInstant = Instant.now();
647689
runningState = State.RUNNING;
690+
splits.clear();
648691
}
649692

650693
/**
@@ -697,7 +740,7 @@ public void suspend() {
697740
}
698741

699742
/**
700-
* Gets a summary of the split time that this StopWatch recorded as a string.
743+
* Gets a summary of the last split time that this StopWatch recorded as a string.
701744
*
702745
* <p>
703746
* The format used is ISO 8601-like, [<em>message</em> ]<em>hours</em>:<em>minutes</em>:<em>seconds</em>.<em>milliseconds</em>.
@@ -744,6 +787,53 @@ public void unsplit() {
744787
throw new IllegalStateException("Stopwatch has not been split.");
745788
}
746789
splitState = SplitState.UNSPLIT;
790+
splits.remove(splits.size() - 1);
791+
}
792+
793+
/**
794+
* Stores a split as a label and duration.
795+
*
796+
* @since 3.20.0
797+
*/
798+
public static final class Split extends ImmutablePair<String, Duration> {
799+
800+
/**
801+
* Constructs a Split object with label and duration.
802+
*
803+
* @param label Label for this split.
804+
* @param duration Duration for this split.
805+
*/
806+
public Split(String label, Duration duration) {
807+
super(label, duration);
808+
}
809+
810+
/**
811+
* Gets the label of this split.
812+
*
813+
* @return The label of this split.
814+
*/
815+
public String getLabel() {
816+
return getLeft();
817+
}
818+
819+
/**
820+
* Gets the duration of this split.
821+
*
822+
* @return The duration of this split..
823+
*/
824+
public Duration getDuration() {
825+
return getRight();
826+
}
827+
828+
/**
829+
* Converts this instance to a string.
830+
*
831+
* @return this instance to a string.
832+
*/
833+
@Override
834+
public String toString() {
835+
return String.format("Split [%s, %s])", getLabel(), getDuration());
836+
}
747837
}
748838

749839
}

src/test/java/org/apache/commons/lang3/time/StopWatchTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import java.io.IOException;
2727
import java.time.Duration;
2828
import java.time.Instant;
29+
import java.util.ArrayList;
30+
import java.util.List;
2931
import java.util.concurrent.TimeUnit;
3032
import java.util.concurrent.atomic.AtomicInteger;
3133

@@ -69,8 +71,11 @@ private StopWatch createMockStopWatch(final long nanos) {
6971
private StopWatch set(final StopWatch watch, final long nanos) {
7072
try {
7173
final long currentNanos = System.nanoTime();
74+
final List<StopWatch.Split> splits = new ArrayList<>();
75+
splits.add(new StopWatch.Split(String.valueOf(0), Duration.ofNanos(nanos)));
7276
FieldUtils.writeField(watch, "startTimeNanos", currentNanos - nanos, true);
7377
FieldUtils.writeField(watch, "stopTimeNanos", currentNanos, true);
78+
FieldUtils.writeField(watch, "splits", splits, true);
7479
} catch (final IllegalAccessException e) {
7580
return null;
7681
}
@@ -215,6 +220,23 @@ void testGetSplitDuration() {
215220
assertEquals(Duration.ofNanos(123456), watch.getSplitDuration());
216221
}
217222

223+
@Test
224+
void testGetSplits() {
225+
final StopWatch stopWatch = StopWatch.create();
226+
assertTrue(stopWatch.getSplits().isEmpty());
227+
stopWatch.start();
228+
testGetSplits(stopWatch);
229+
testGetSplits(StopWatch.createStarted());
230+
}
231+
232+
private void testGetSplits(final StopWatch watch) {
233+
assertTrue(watch.getSplits().isEmpty());
234+
watch.split();
235+
assertEquals(1, watch.getSplits().size());
236+
watch.unsplit();
237+
assertTrue(watch.getSplits().isEmpty());
238+
}
239+
218240
@Test
219241
void testGetStartInstant() {
220242
final long beforeStopWatchMillis = System.currentTimeMillis();
@@ -500,6 +522,36 @@ void testToStringWithMessage() throws InterruptedException {
500522
assertEquals(SPLIT_CLOCK_STR_LEN + MESSAGE.length() + 1, splitStr.length(), "Formatted split string not the correct length");
501523
}
502524

525+
@Test
526+
void testSplitsWithStringLabels() {
527+
final StopWatch watch = new StopWatch();
528+
final String firstLabel = "one";
529+
final String secondLabel = "two";
530+
final String thirdLabel = "three";
531+
watch.start();
532+
// starting splits
533+
watch.split(firstLabel);
534+
watch.split(secondLabel);
535+
watch.split(thirdLabel);
536+
watch.stop();
537+
// getting splits
538+
final List<StopWatch.Split> splits = watch.getSplits();
539+
// check size
540+
assertEquals(3, splits.size());
541+
// check labels
542+
assertEquals(firstLabel, splits.get(0).getLabel());
543+
assertEquals(secondLabel, splits.get(1).getLabel());
544+
assertEquals(thirdLabel, splits.get(2).getLabel());
545+
// check time in nanos
546+
assertTrue(splits.get(0).getDuration().toNanos() > 0);
547+
assertTrue(splits.get(1).getDuration().toNanos() > 0);
548+
assertTrue(splits.get(2).getDuration().toNanos() > 0);
549+
// We can only unsplit once
550+
watch.unsplit();
551+
assertEquals(2, watch.getSplits().size());
552+
assertThrows(IllegalStateException.class, watch::unsplit);
553+
}
554+
503555
private int throwIOException() throws IOException {
504556
throw new IOException("A");
505557
}

0 commit comments

Comments
 (0)