Skip to content

Commit 0b843c4

Browse files
authored
feat(api): add forEach method to Cache interface (#171)
1 parent a5547c5 commit 0b843c4

File tree

10 files changed

+103
-0
lines changed

10 files changed

+103
-0
lines changed

api/src/main/java/io/github/xanthic/cache/api/Cache.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package io.github.xanthic.cache.api;
22

3+
import org.jetbrains.annotations.ApiStatus;
34
import org.jetbrains.annotations.NotNull;
45
import org.jetbrains.annotations.Nullable;
56

67
import java.util.Map;
8+
import java.util.function.BiConsumer;
79
import java.util.function.BiFunction;
810
import java.util.function.Function;
911

@@ -174,4 +176,22 @@ default void putAll(@NotNull Map<? extends K, ? extends V> map) {
174176
map.forEach(this::put);
175177
}
176178

179+
/**
180+
* Performs the specified action upon all entries within the cache.
181+
*
182+
* @param action the action to perform upon each entry
183+
* @apiNote While this method is technically optional, all of the canonical
184+
* implementations provided by Xanthic support this operation.
185+
* @implSpec The iteration order of entries is not consistent (across cache different implementations),
186+
* and should not be relied upon. Iteration may terminate early if the action yields an exception.
187+
* @implNote This can be an inefficient operation that ought to be avoided;
188+
* perhaps your data can be modeled differently to avoid this operation.
189+
* @throws NullPointerException if the specified action is null
190+
* @throws UnsupportedOperationException if the underlying cache provider does not support iteration over entries
191+
*/
192+
@ApiStatus.Experimental
193+
default void forEach(@NotNull BiConsumer<? super K, ? super V> action) {
194+
throw new UnsupportedOperationException();
195+
}
196+
177197
}

core/src/main/java/io/github/xanthic/cache/core/delegate/EmptyCache.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.jetbrains.annotations.Nullable;
77

88
import java.util.Map;
9+
import java.util.function.BiConsumer;
910
import java.util.function.BiFunction;
1011
import java.util.function.Function;
1112

@@ -94,4 +95,8 @@ public void putAll(@NotNull Map<? extends K, ? extends V> map) {
9495
// no-op
9596
}
9697

98+
@Override
99+
public void forEach(@NotNull BiConsumer<? super K, ? super V> action) {
100+
// no-op
101+
}
97102
}

core/src/main/java/io/github/xanthic/cache/core/delegate/GenericMapCacheDelegate.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.jetbrains.annotations.Nullable;
99

1010
import java.util.Map;
11+
import java.util.function.BiConsumer;
1112
import java.util.function.BiFunction;
1213
import java.util.function.Function;
1314

@@ -88,4 +89,9 @@ public boolean replace(@NotNull K key, @NotNull V oldValue, @NotNull V newValue)
8889
public void putAll(@NotNull Map<? extends K, ? extends V> map) {
8990
this.map.putAll(map);
9091
}
92+
93+
@Override
94+
public void forEach(@NotNull BiConsumer<? super K, ? super V> action) {
95+
this.map.forEach(action);
96+
}
9197
}

core/src/testFixtures/java/io/github/xanthic/cache/core/provider/ProviderTestBase.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,30 @@ public void replaceTest() {
135135
Assertions.assertNull(cache.get("9"));
136136
}
137137

138+
@Test
139+
@DisplayName("Tests cache forEach")
140+
public void iterateTest() {
141+
// Build cache
142+
Cache<String, Integer> cache = build(null);
143+
144+
// Add entries
145+
for (int i = 0; i < 3; i++) {
146+
cache.put(String.valueOf(i), i);
147+
}
148+
149+
// Save output of forEach
150+
Map<String, Integer> observed = new HashMap<>();
151+
cache.forEach(observed::put);
152+
153+
// Test that observed contents match expected
154+
Map<String, Integer> expected = new HashMap<>();
155+
expected.put("0", 0);
156+
expected.put("1", 1);
157+
expected.put("2", 2);
158+
159+
Assertions.assertEquals(expected, observed);
160+
}
161+
138162
@Test
139163
@DisplayName("Test that caches with zero maximum size remain empty")
140164
public void zeroMaxSizeTest() {

provider-androidx/src/main/java/io/github/xanthic/cache/provider/androidx/ExpiringLruDelegate.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.concurrent.ScheduledExecutorService;
2020
import java.util.concurrent.TimeUnit;
2121
import java.util.concurrent.atomic.AtomicReference;
22+
import java.util.function.BiConsumer;
2223

2324
@Value
2425
@Getter(AccessLevel.PRIVATE)
@@ -101,6 +102,18 @@ public long size() {
101102
return cache.size();
102103
}
103104

105+
@Override
106+
public void forEach(@NotNull BiConsumer<? super K, ? super V> action) {
107+
if (type == ExpiryType.POST_ACCESS) {
108+
synchronized (getLock()) {
109+
BiConsumer<K, V> markAccessed = this::start;
110+
cache.snapshot().forEach(markAccessed.andThen(action));
111+
}
112+
} else {
113+
cache.snapshot().forEach(action);
114+
}
115+
}
116+
104117
@NotNull
105118
@Override
106119
protected Object getLock() {

provider-androidx/src/main/java/io/github/xanthic/cache/provider/androidx/LruDelegate.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import lombok.Value;
99
import org.jetbrains.annotations.NotNull;
1010

11+
import java.util.function.BiConsumer;
12+
1113
@Value
1214
@Getter(AccessLevel.PRIVATE)
1315
@EqualsAndHashCode(callSuper = false)
@@ -40,6 +42,11 @@ public long size() {
4042
return cache.size();
4143
}
4244

45+
@Override
46+
public void forEach(@NotNull BiConsumer<? super K, ? super V> action) {
47+
cache.snapshot().forEach(action);
48+
}
49+
4350
@NotNull
4451
@Override
4552
protected Object getLock() {

provider-cache2k/src/main/java/io/github/xanthic/cache/provider/cache2k/Cache2kDelegate.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.jetbrains.annotations.NotNull;
88

99
import java.util.Map;
10+
import java.util.function.BiConsumer;
1011
import java.util.function.Function;
1112

1213
@Value
@@ -59,4 +60,8 @@ public void putAll(@NotNull Map<? extends K, ? extends V> map) {
5960
cache.putAll(map);
6061
}
6162

63+
@Override
64+
public void forEach(@NotNull BiConsumer<? super K, ? super V> action) {
65+
cache.entries().forEach(e -> action.accept(e.getKey(), e.getValue()));
66+
}
6267
}

provider-ehcache/src/main/java/io/github/xanthic/cache/provider/ehcache/EhcacheDelegate.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.jetbrains.annotations.NotNull;
77

88
import java.util.Map;
9+
import java.util.function.BiConsumer;
910

1011
@Value
1112
@EqualsAndHashCode(callSuper = false)
@@ -64,4 +65,14 @@ public boolean replace(@NotNull K key, @NotNull V value) {
6465
public boolean replace(@NotNull K key, @NotNull V oldValue, @NotNull V newValue) {
6566
return read(() -> cache.replace(key, oldValue, newValue));
6667
}
68+
69+
@Override
70+
public void forEach(@NotNull BiConsumer<? super K, ? super V> action) {
71+
// We can't guarantee that users won't attempt to mutate the cache from within the action
72+
// So, we incur a performance penalty to acquire a write (rather than read) lock in order to avoid deadlocking
73+
write(() -> {
74+
cache.forEach(e -> action.accept((K) e.getKey(), (V) e.getValue()));
75+
return Void.TYPE;
76+
});
77+
}
6778
}

provider-infinispan-java11/src/main/java/io/github/xanthic/cache/provider/infinispanjdk11/InfinispanDelegate.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.jetbrains.annotations.Nullable;
77

88
import java.util.Map;
9+
import java.util.function.BiConsumer;
910
import java.util.function.BiFunction;
1011
import java.util.function.Function;
1112

@@ -78,4 +79,9 @@ public boolean replace(@NotNull K key, @NotNull V oldValue, @NotNull V newValue)
7879
public void putAll(@NotNull Map<? extends K, ? extends V> map) {
7980
cache.putAll(map);
8081
}
82+
83+
@Override
84+
public void forEach(@NotNull BiConsumer<? super K, ? super V> action) {
85+
cache.forEach(action);
86+
}
8187
}

provider-infinispan/src/main/java/io/github/xanthic/cache/provider/infinispan/InfinispanDelegate.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.jetbrains.annotations.Nullable;
77

88
import java.util.Map;
9+
import java.util.function.BiConsumer;
910
import java.util.function.BiFunction;
1011
import java.util.function.Function;
1112

@@ -78,4 +79,9 @@ public boolean replace(@NotNull K key, @NotNull V oldValue, @NotNull V newValue)
7879
public void putAll(@NotNull Map<? extends K, ? extends V> map) {
7980
cache.putAll(map);
8081
}
82+
83+
@Override
84+
public void forEach(@NotNull BiConsumer<? super K, ? super V> action) {
85+
cache.forEach(action);
86+
}
8187
}

0 commit comments

Comments
 (0)