class TimeBasedCache {

    public V get(K key)
    {
        // optimistically try to get the key without locking
        V value = _cache.get(key);
        if (value == null && mayReload())
        {
            reloadCache();
            value = _cache.get(key);
        }

        return value;
    }

}

class TimeBasedCacheTest {

    @Test
    public void doesNotReloadCacheWithinTimeLimit()
            throws Exception
    {

        // fake clock returns current time, then the same thing again,
        // then 4 seconds later, then 8 seconds later.
        Clock fakeClock = mock(Clock.class);
        Instant now = Instant.now();
        when(fakeClock.millis()).thenCallRealMethod();
        when(fakeClock.instant()).thenReturn(
                now, // get("0") - first map loading
                now, // cache asks when the latest loading happened
                now.plus(Duration.ofSeconds(4)), // get("1") - no reloading
                // get("0") - should not even ask about the time as entry was found
                now.plus(Duration.ofSeconds(4)), // get("1") - no reloading
                now.plus(Duration.ofSeconds(8))); // get("1") - reload

        @SuppressWarnings("unchecked")
        Supplier<ImmutableMap> fakeSupplier = mock(Supplier.class);
        when(fakeSupplier.get()).thenReturn(
                ImmutableMap.of("0", 0),
                ImmutableMap.of("1", 1));

        // the map will always contain a single entry with the number of reloads as in
        // ("reloads" -> reloads)
        AtomicInteger reloads = new AtomicInteger(0);

        TimeBasedCache<String, Integer> cache = new TimeBasedCache<>(Duration.ofSeconds(5),
                () -> ImmutableMap.copyOf(ImmutableMap.of(Integer.toString(reloads.get()),
                        reloads.getAndIncrement())),
                ImmutableMap.of(), fakeClock);

        // should have only ("0" -> 0) in the map in the beginning
        assertNotNull(cache.get("0"));
        assertNull(cache.get("1"));

        // second time we try, the map should not reload
        assertNotNull(cache.get("0"));
        assertNull(cache.get("1"));

        // when the first reload happens, the cache contains ("1" -> 1)
        assertNotNull(cache.get("1"));
        assertNull(cache.get("0"));
    }

}