diff --git a/src/main/java/ic/doc/CachedWeatherForecaster.java b/src/main/java/ic/doc/CachedWeatherForecaster.java index dc79bc9..83d9a70 100644 --- a/src/main/java/ic/doc/CachedWeatherForecaster.java +++ b/src/main/java/ic/doc/CachedWeatherForecaster.java @@ -1,14 +1,19 @@ package ic.doc; +import java.util.ArrayDeque; import java.util.HashMap; +import java.util.Queue; // This class is a decorator for WeatherForecaster that caches all results. public class CachedWeatherForecaster implements WeatherForecaster { private final WeatherForecaster forecaster; + private final Integer maxCacheSize; private final HashMap cache = new HashMap<>(); + private final Queue evictionQueue = new ArrayDeque<>(); - CachedWeatherForecaster(WeatherForecaster forecaster) { + CachedWeatherForecaster(WeatherForecaster forecaster, Integer maxCacheSize) { this.forecaster = forecaster; + this.maxCacheSize = maxCacheSize; } @Override @@ -17,11 +22,19 @@ public class CachedWeatherForecaster implements WeatherForecaster { WeatherForecast forecast = cache.get(key); if (forecast == null) { forecast = forecaster.forecastFor(region, day); - cache.put(key, forecast); + putCache(key, forecast); return forecast; } return forecast; } + private void putCache(CacheKey key, WeatherForecast forecast) { + if (maxCacheSize != null && cache.size() >= maxCacheSize) { + cache.remove(evictionQueue.poll()); + } + cache.put(key, forecast); + evictionQueue.add(key); + } + private record CacheKey(WeatherRegion region, Weekday day) {} } \ No newline at end of file diff --git a/src/main/java/ic/doc/CachedWeatherForecasterBuilder.java b/src/main/java/ic/doc/CachedWeatherForecasterBuilder.java index 4536950..be12711 100644 --- a/src/main/java/ic/doc/CachedWeatherForecasterBuilder.java +++ b/src/main/java/ic/doc/CachedWeatherForecasterBuilder.java @@ -2,6 +2,7 @@ package ic.doc; public class CachedWeatherForecasterBuilder { private WeatherForecaster forecaster; + private Integer maxCacheSize = null; private CachedWeatherForecasterBuilder() {} @@ -14,7 +15,12 @@ public class CachedWeatherForecasterBuilder { return this; } + public CachedWeatherForecasterBuilder withMaxCacheSize(int maxCacheSize) { + this.maxCacheSize = maxCacheSize; + return this; + } + public CachedWeatherForecaster build() { - return new CachedWeatherForecaster(forecaster); + return new CachedWeatherForecaster(forecaster, maxCacheSize); } } diff --git a/src/test/java/ic/doc/CachedWeatherForecasterTest.java b/src/test/java/ic/doc/CachedWeatherForecasterTest.java index 7ad733a..9a7cbdc 100644 --- a/src/test/java/ic/doc/CachedWeatherForecasterTest.java +++ b/src/test/java/ic/doc/CachedWeatherForecasterTest.java @@ -55,4 +55,34 @@ public class CachedWeatherForecasterTest { cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY); assertEquals(expForecast, actForecast); } + + @Test + public void forecastForCacheEvictedCall() { + WeatherForecaster cachedForecaster = cachedWeatherForecaster.withMaxCacheSize(2).build(); + WeatherForecast expForecast = new WeatherForecast("Sunny Spells", 30); + WeatherForecast badForecast = new WeatherForecast("Rainy", 10); + context.checking(new Expectations() {{ + // Two calls since cache gets evicted + exactly(2).of(forecaster).forecastFor(WeatherRegion.LONDON, Weekday.MONDAY); + will(returnValue(expForecast)); + // Also make some other requests in-between + oneOf(forecaster).forecastFor(WeatherRegion.LONDON, Weekday.TUESDAY); + will(returnValue(badForecast)); + oneOf(forecaster).forecastFor(WeatherRegion.WALES, Weekday.MONDAY); + will(returnValue(badForecast)); + }}); + // Cache put + cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY); + // Cache put + cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.TUESDAY); + // Size exceeded, (London, Monday) entry evicted + cachedForecaster.forecastFor(WeatherRegion.WALES, Weekday.MONDAY); + // Size exceeded, (London, Tuesday) entry evicted + WeatherForecast actForecast = + cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY); + assertEquals(expForecast, actForecast); + // Caching still works (3rd call not made) + actForecast = cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY); + assertEquals(expForecast, actForecast); + } } \ No newline at end of file