Last active
May 12, 2020 11:39
-
-
Save tingstad/d21326254fd6e7f67d5fafa70b3a2dfc to your computer and use it in GitHub Desktop.
Cached Supplier - similar to Google Guava's Suppliers.memoizeWithExpiration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.concurrent.TimeUnit; | |
import java.util.function.Supplier; | |
import static java.util.Objects.requireNonNull; | |
public class CachedSupplier<T> implements Supplier<T> { | |
private final Supplier<T> delegate; | |
private final long expiration; | |
private final Supplier<Long> currentTimeSupplier; | |
private T value; | |
private volatile long time; | |
public CachedSupplier(Supplier<T> delegate, long expiration, TimeUnit timeUnit) { | |
this(delegate, expiration, timeUnit, System::currentTimeMillis); | |
} | |
public CachedSupplier(Supplier<T> delegate, long expiration, TimeUnit timeUnit, Supplier<Long> currentTimeMillisSupplier) { | |
if (expiration <= 0) throw new IllegalArgumentException("expiration must be > 0"); | |
this.delegate = requireNonNull(delegate); | |
this.expiration = timeUnit.toMillis(expiration); | |
this.currentTimeSupplier = requireNonNull(currentTimeMillisSupplier); | |
this.time = 0; | |
} | |
@Override | |
public T get() { | |
long currentTime = currentTimeSupplier.get(); | |
boolean expired = currentTime - time > expiration; | |
if (expired) { | |
synchronized (this) { | |
if (time < currentTime) { | |
value = delegate.get(); | |
time = currentTime; | |
} | |
} | |
} | |
return value; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.jupiter.api.Test; | |
import java.util.concurrent.TimeUnit; | |
import java.util.concurrent.atomic.AtomicInteger; | |
import java.util.concurrent.atomic.AtomicLong; | |
import java.util.function.Supplier; | |
import static org.junit.jupiter.api.Assertions.assertEquals; | |
import static org.junit.jupiter.api.Assertions.assertThrows; | |
public class CachedSupplierTest { | |
@Test | |
void shouldCallDelegate() { | |
Supplier<Long> delegate = () -> 7L; | |
long value = new CachedSupplier<>(delegate, 1, TimeUnit.HOURS, () -> Long.MAX_VALUE).get(); | |
assertEquals(7L, value); | |
} | |
@Test | |
void twoRapidCallsMakesOneBackendRequest() { | |
AtomicInteger i = new AtomicInteger(0); | |
Supplier<Integer> delegate = i::getAndIncrement; | |
CachedSupplier cachedSupplier = new CachedSupplier(delegate, 1, TimeUnit.SECONDS, () -> Long.MAX_VALUE); | |
cachedSupplier.get(); | |
cachedSupplier.get(); | |
assertEquals(1, i.get()); | |
} | |
@Test | |
void shouldCallDelegateAfterExpiration() { | |
Supplier<Integer> delegate = new AtomicInteger(0)::incrementAndGet; | |
AtomicLong timeInMillis = new AtomicLong(1001); | |
CachedSupplier cachedSupplier = new CachedSupplier(delegate, 1, TimeUnit.SECONDS, timeInMillis::get); | |
assertEquals(1, cachedSupplier.get()); | |
assertEquals(1, cachedSupplier.get()); | |
timeInMillis.set(2002); | |
assertEquals(2, cachedSupplier.get()); | |
assertEquals(2, cachedSupplier.get()); | |
assertEquals(2, cachedSupplier.get()); | |
timeInMillis.set(9000); | |
assertEquals(3, cachedSupplier.get()); | |
assertEquals(3, cachedSupplier.get()); | |
timeInMillis.set(9999); | |
assertEquals(3, cachedSupplier.get()); | |
assertEquals(3, cachedSupplier.get()); | |
} | |
@Test | |
void zeroOrNegativeExpirationIsNotAllowed() { | |
Supplier<Long> delegate = () -> 1L; | |
assertThrows(IllegalArgumentException.class, () -> new CachedSupplier(delegate, 0, TimeUnit.SECONDS), | |
"Expiration must be positive"); | |
assertThrows(IllegalArgumentException.class, () -> new CachedSupplier(delegate, -10, TimeUnit.SECONDS), | |
"Expiration must be positive"); | |
assertThrows(NullPointerException.class, () -> new CachedSupplier(null, 1, TimeUnit.SECONDS), | |
"Supplier argument must not be null"); | |
assertThrows(NullPointerException.class, () -> new CachedSupplier(delegate, 1, null), | |
"TimeUnit argument must not be null"); | |
assertThrows(NullPointerException.class, () -> new CachedSupplier(delegate, 1, TimeUnit.SECONDS, null), | |
"TimeSupplier argument must not be null"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment