Skip to content

Commit 9e58d81

Browse files
authored
🎉 feat!: improve Cooldowns tests, docs, add getDifference (#30)
- add missing class documentation - implement #24 by renaming `getRemaining` into `getDifference` and adding `getRemaining` which returns time until expiration, minimally zero - rename `Cooldowns.cleanup` to `Cooldowns.clearExpired` for better clarification - add `getDifference` to the tests - improve some parts of the tests - make the cooldown tests concurrent again because some of them are using `Thread.sleep`
1 parent 3d5e14c commit 9e58d81

5 files changed

Lines changed: 71 additions & 22 deletions

File tree

src/main/java/com/cosimo/utilities/timed/Cooldown.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
import java.util.concurrent.TimeUnit;
66

7+
/**
8+
* Mutable implementation where the cooldown can be extended by a duration.
9+
*/
710
public class Cooldown implements ICooldown {
811

912
/**
@@ -17,7 +20,6 @@ public class Cooldown implements ICooldown {
1720
*
1821
* @param duration For how long this cooldown will last
1922
* @param unit {@link TimeUnit} of the given cooldown duration parameter
20-
* @see Cooldown - duration unit conversion to milliseconds
2123
*/
2224
public Cooldown(long duration, @NonNull TimeUnit unit) {
2325
this.end = this.getCurrentTime() + this.toThisTime(duration, unit);

src/main/java/com/cosimo/utilities/timed/Cooldowns.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
import java.util.Map;
77

88
/**
9-
* A {@link Map} of {@link ICooldown} updated lazily, on access, with the {@link #cleanup()} method for purging expired
10-
* entries when needed.
9+
* A {@link Map} of {@link ICooldown} cleared lazily with the {@link #clearExpired()} method for purging expired entries
10+
* when needed.
1111
*
1212
* @param <K> Key type of this class's {@link Map}, {@link String} is suggested as it provides many variations for
1313
* unique keys
1414
* @param <V> Expected {@link ICooldown} implementation
1515
*/
16+
@SuppressWarnings("unused")
1617
public class Cooldowns<K, V extends ICooldown> extends HashMap<K, V> {
1718
public Cooldowns(int initialCapacity, float loadFactor) {
1819
super(initialCapacity, loadFactor);
@@ -26,18 +27,16 @@ public Cooldowns() {
2627
super();
2728
}
2829

29-
public Cooldowns(@NonNull Map<? extends K, ? extends V> map) {
30+
public Cooldowns(@NonNull Map<K, ? extends V> map) {
3031
super(map);
3132
}
3233

3334
/**
3435
* Removes all {@link ICooldown} that have expired.
3536
*
36-
* @return This instance, useful for chaining
37+
* @return Whether any {@link ICooldown} has expired and was removed
3738
*/
38-
@NonNull
39-
public Cooldowns<K, V> cleanup() {
40-
this.entrySet().removeIf(entry -> entry.getValue().isExpired());
41-
return this;
39+
public boolean clearExpired() {
40+
return this.entrySet().removeIf(entry -> entry.getValue().isExpired());
4241
}
4342
}

src/main/java/com/cosimo/utilities/timed/ICooldown.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,43 @@ default boolean isExpired() {
1616
}
1717

1818
/**
19-
* Returns how much time is left until the end of this cooldown, expressed in a given {@link TimeUnit}.
19+
* Returns how much time is left until the end of this cooldown, expressed in a given {@link TimeUnit}, minimally
20+
* zero.
2021
*
2122
* @param unit {@link TimeUnit} in which the remaining milliseconds should be converted to
22-
* @return The remaining cooldown time, expressed as a double in the given {@link TimeUnit}
23+
* @return The remaining cooldown time that can't be negative, expressed as a long in the given {@link TimeUnit}
2324
*/
2425
default long getRemaining(@NonNull TimeUnit unit) {
25-
return this.fromThisTime(this.getRemaining(), unit);
26+
return Math.max(0, this.getDifference(unit));
2627
}
2728

2829
/**
29-
* Returns how much time is left until the end of this cooldown, even negative if it has already expired.
30+
* Returns the timestamp difference between current one and the end of this cooldown, minimally zero, which would
31+
* indicate it has already expired.
3032
*
31-
* @return Remaining time of any sign
33+
* @return Remaining time that can't be negative
3234
*/
3335
default long getRemaining() {
36+
return Math.max(0, this.getDifference());
37+
}
38+
39+
/**
40+
* Returns the timestamp difference between current one and the end of this cooldown, expressed in a given
41+
* {@link TimeUnit}.
42+
*
43+
* @param unit {@link TimeUnit} in which the remaining milliseconds should be converted to
44+
* @return The remaining cooldown time, expressed as a long in the given {@link TimeUnit}
45+
*/
46+
default long getDifference(@NonNull TimeUnit unit) {
47+
return this.fromThisTime(this.getDifference(), unit);
48+
}
49+
50+
/**
51+
* Returns how much time is left until the end of this cooldown, even negative if it has already expired.
52+
*
53+
* @return Remaining time of any mathematical sign
54+
*/
55+
default long getDifference() {
3456
return this.getExpiration() - this.getCurrentTime();
3557
}
3658

src/main/java/com/cosimo/utilities/timed/ImmutableCooldown.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
import java.util.concurrent.TimeUnit;
66

7+
/**
8+
* Immutable implementation for tracking cooldown expiration only.
9+
*/
10+
@SuppressWarnings("unused")
711
public class ImmutableCooldown implements ICooldown {
812

913
/**

src/test/java/com/cosimo/utilities/timed/CooldownTests.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
package com.cosimo.utilities.timed;
22

33
import org.junit.jupiter.api.Test;
4+
import org.junit.jupiter.api.parallel.Execution;
5+
import org.junit.jupiter.api.parallel.ExecutionMode;
46

57
import java.util.Map;
68
import java.util.concurrent.TimeUnit;
79

810
import static org.junit.jupiter.api.Assertions.*;
911

12+
@Execution(ExecutionMode.CONCURRENT)
1013
public class CooldownTests {
1114

1215
/**
1316
* Permitted time difference from setting a time value to testing it.
1417
*/
15-
public static final int TOLERANCE = 50;
18+
public static final int MILLISECOND_TOLERANCE = 50;
1619

1720
@Test
1821
public void testToThisTime() {
@@ -33,26 +36,43 @@ public void testFromThisTime() {
3336
@Test
3437
public void testGetCurrentTime() {
3538
final TimeStandard timeStandard = new TimeStandard() {
39+
@Override
40+
public long getCurrentTime() {
41+
return System.nanoTime();
42+
}
3643
};
37-
final long currentTime = System.currentTimeMillis();
44+
final long currentTime = System.nanoTime();
45+
final long diffInMs = Math.abs(timeStandard.getCurrentTime() - currentTime) / 1000;
3846

39-
assertTrue(Math.abs(timeStandard.getCurrentTime() - currentTime) < TOLERANCE);
47+
assertTrue(diffInMs < MILLISECOND_TOLERANCE);
4048
}
4149

4250
@Test
4351
public void testIsExpired() throws InterruptedException {
4452
final ICooldown cooldown = new Cooldown(50);
53+
4554
assertFalse(cooldown.isExpired());
55+
4656
Thread.sleep(60);
57+
4758
assertTrue(cooldown.isExpired());
59+
60+
assertEquals(0, cooldown.getRemaining());
61+
assertEquals(0, cooldown.getRemaining(TimeUnit.SECONDS));
62+
63+
assertTrue(cooldown.getDifference() < 0);
64+
assertTrue(cooldown.getDifference(TimeUnit.NANOSECONDS) < 0);
4865
}
4966

5067
@Test
51-
public void testGetRemaining() {
68+
public void testGetRemainingAndDifference() {
5269
final ICooldown cooldown = new Cooldown(5_000);
70+
final long difference = cooldown.getDifference();
5371
final long remaining = cooldown.getRemaining();
5472

55-
assertTrue(remaining <= 5_000 && remaining > 0);
73+
assertTrue(difference <= 5_000 && difference > 0);
74+
assertEquals(difference, remaining);
75+
assertTrue(cooldown.getDifference(TimeUnit.SECONDS) <= 5);
5676
assertTrue(cooldown.getRemaining(TimeUnit.SECONDS) <= 5);
5777
}
5878

@@ -93,18 +113,20 @@ public void testCooldownsCleanup() throws InterruptedException {
93113
cooldowns.put("test1", new Cooldown(50));
94114
cooldowns.put("test2", new Cooldown(5_000));
95115

96-
Thread.sleep(50 + TOLERANCE);
116+
Thread.sleep(50 + MILLISECOND_TOLERANCE);
97117

98-
cooldowns.cleanup();
118+
assertTrue(cooldowns.clearExpired());
99119

100120
assertFalse(cooldowns.containsKey("test1"));
101121
assertTrue(cooldowns.containsKey("test2"));
122+
123+
assertFalse(cooldowns.clearExpired());
102124
}
103125

104126
@Test
105127
public void testCooldownsConstructorWithMap() {
106128
final var initialMap = Map.of("key1", new Cooldown(5000));
107-
final Cooldowns<String, ICooldown> cooldowns = new Cooldowns<>(initialMap);
129+
final var cooldowns = new Cooldowns<>(initialMap);
108130

109131
assertTrue(cooldowns.containsKey("key1"));
110132
}

0 commit comments

Comments
 (0)