/* * Copyright Terracotta, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ehcache.impl.internal.store.offheap; import org.ehcache.Cache; import org.ehcache.ValueSupplier; import org.ehcache.config.EvictionAdvisor; import org.ehcache.config.units.MemoryUnit; import org.ehcache.event.EventType; import org.ehcache.core.spi.store.StoreAccessException; import org.ehcache.expiry.Duration; import org.ehcache.expiry.Expirations; import org.ehcache.expiry.Expiry; import org.ehcache.core.spi.function.BiFunction; import org.ehcache.core.spi.function.Function; import org.ehcache.core.spi.function.NullaryFunction; import org.ehcache.core.spi.time.TimeSource; import org.ehcache.core.spi.store.AbstractValueHolder; import org.ehcache.core.spi.store.Store; import org.ehcache.core.spi.store.events.StoreEvent; import org.ehcache.core.spi.store.events.StoreEventListener; import org.ehcache.core.spi.store.tiering.CachingTier; import org.ehcache.core.statistics.LowerCachingTierOperationsOutcome; import org.ehcache.core.statistics.StoreOperationOutcomes; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.hamcrest.TypeSafeMatcher; import org.junit.After; import org.junit.Test; import org.terracotta.context.ContextElement; import org.terracotta.context.TreeNode; import org.terracotta.context.query.QueryBuilder; import org.terracotta.statistics.OperationStatistic; import org.terracotta.statistics.StatisticsManager; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import static org.ehcache.core.internal.util.ValueSuppliers.supplierOf; import static org.ehcache.impl.internal.util.Matchers.valueHeld; import static org.ehcache.impl.internal.util.StatisticsTestUtils.validateStats; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.lessThan; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; /** * * @author cdennis */ public abstract class AbstractOffHeapStoreTest { private TestTimeSource timeSource = new TestTimeSource(); private AbstractOffHeapStore<String, String> offHeapStore; @After public void after() { if(offHeapStore != null) { destroyStore(offHeapStore); } } @Test public void testGetAndRemoveNoValue() throws Exception { offHeapStore = createAndInitStore(timeSource, Expirations.noExpiration()); assertThat(offHeapStore.getAndRemove("1"), is(nullValue())); validateStats(offHeapStore, EnumSet.of(LowerCachingTierOperationsOutcome.GetAndRemoveOutcome.MISS)); } @Test public void testGetAndRemoveValue() throws Exception { offHeapStore = createAndInitStore(timeSource, Expirations.noExpiration()); offHeapStore.put("1", "one"); assertThat(offHeapStore.getAndRemove("1").value(), equalTo("one")); validateStats(offHeapStore, EnumSet.of(LowerCachingTierOperationsOutcome.GetAndRemoveOutcome.HIT_REMOVED)); assertThat(offHeapStore.get("1"), is(nullValue())); } @Test public void testGetAndRemoveExpiredElementReturnsNull() throws Exception { offHeapStore = createAndInitStore(timeSource, Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS))); assertThat(offHeapStore.getAndRemove("1"), is(nullValue())); offHeapStore.put("1", "one"); final AtomicReference<Store.ValueHolder<String>> invalidated = new AtomicReference<Store.ValueHolder<String>>(); offHeapStore.setInvalidationListener(new CachingTier.InvalidationListener<String, String>() { @Override public void onInvalidation(String key, Store.ValueHolder<String> valueHolder) { invalidated.set(valueHolder); } }); timeSource.advanceTime(20); assertThat(offHeapStore.getAndRemove("1"), is(nullValue())); assertThat(invalidated.get().value(), equalTo("one")); assertThat(invalidated.get().isExpired(timeSource.getTimeMillis(), TimeUnit.MILLISECONDS), is(true)); assertThat(getExpirationStatistic(offHeapStore).count(StoreOperationOutcomes.ExpirationOutcome.SUCCESS), is(1L)); } @Test public void testInstallMapping() throws Exception { offHeapStore = createAndInitStore(timeSource, Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS))); assertThat(offHeapStore.installMapping("1", new Function<String, Store.ValueHolder<String>>() { @Override public Store.ValueHolder<String> apply(String key) { return new SimpleValueHolder<String>("one", timeSource.getTimeMillis(), 15); } }).value(), equalTo("one")); validateStats(offHeapStore, EnumSet.of(LowerCachingTierOperationsOutcome.InstallMappingOutcome.PUT)); timeSource.advanceTime(20); try { offHeapStore.installMapping("1", new Function<String, Store.ValueHolder<String>>() { @Override public Store.ValueHolder<String> apply(String key) { return new SimpleValueHolder<String>("un", timeSource.getTimeMillis(), 15); } }); fail("expected AssertionError"); } catch (AssertionError ae) { // expected } } @Test public void testInvalidateKeyAbsent() throws Exception { offHeapStore = createAndInitStore(timeSource, Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS))); final AtomicReference<Store.ValueHolder<String>> invalidated = new AtomicReference<Store.ValueHolder<String>>(); offHeapStore.setInvalidationListener(new CachingTier.InvalidationListener<String, String>() { @Override public void onInvalidation(String key, Store.ValueHolder<String> valueHolder) { invalidated.set(valueHolder); } }); offHeapStore.invalidate("1"); assertThat(invalidated.get(), is(nullValue())); validateStats(offHeapStore, EnumSet.of(LowerCachingTierOperationsOutcome.InvalidateOutcome.MISS)); } @Test public void testInvalidateKeyPresent() throws Exception { offHeapStore = createAndInitStore(timeSource, Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS))); offHeapStore.put("1", "one"); final AtomicReference<Store.ValueHolder<String>> invalidated = new AtomicReference<Store.ValueHolder<String>>(); offHeapStore.setInvalidationListener(new CachingTier.InvalidationListener<String, String>() { @Override public void onInvalidation(String key, Store.ValueHolder<String> valueHolder) { invalidated.set(valueHolder); } }); offHeapStore.invalidate("1"); assertThat(invalidated.get().value(), equalTo("one")); validateStats(offHeapStore, EnumSet.of(LowerCachingTierOperationsOutcome.InvalidateOutcome.REMOVED)); assertThat(offHeapStore.get("1"), is(nullValue())); } @Test public void testClear() throws Exception { offHeapStore = createAndInitStore(timeSource, Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS))); offHeapStore.put("1", "one"); offHeapStore.put("2", "two"); offHeapStore.put("3", "three"); offHeapStore.clear(); assertThat(offHeapStore.get("1"), is(nullValue())); assertThat(offHeapStore.get("2"), is(nullValue())); assertThat(offHeapStore.get("3"), is(nullValue())); } @Test public void testWriteBackOfValueHolder() throws StoreAccessException { offHeapStore = createAndInitStore(timeSource, Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS))); offHeapStore.put("key1", "value1"); timeSource.advanceTime(10); OffHeapValueHolder<String> valueHolder = (OffHeapValueHolder<String>)offHeapStore.get("key1"); assertThat(valueHolder.lastAccessTime(TimeUnit.MILLISECONDS), is(10L)); timeSource.advanceTime(10); assertThat(offHeapStore.get("key1"), notNullValue()); timeSource.advanceTime(16); assertThat(offHeapStore.get("key1"), nullValue()); } @Test public void testEvictionAdvisor() throws StoreAccessException { Expiry<Object, Object> expiry = Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS)); EvictionAdvisor<String, byte[]> evictionAdvisor = new EvictionAdvisor<String, byte[]>() { @Override public boolean adviseAgainstEviction(String key, byte[] value) { return true; } }; performEvictionTest(timeSource, expiry, evictionAdvisor); } @Test public void testBrokenEvictionAdvisor() throws StoreAccessException { Expiry<Object, Object> expiry = Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS)); EvictionAdvisor<String, byte[]> evictionAdvisor = new EvictionAdvisor<String, byte[]>() { @Override public boolean adviseAgainstEviction(String key, byte[] value) { throw new UnsupportedOperationException("Broken advisor!"); } }; performEvictionTest(timeSource, expiry, evictionAdvisor); } @Test public void testFlushUpdatesAccessStats() throws StoreAccessException { Expiry<Object, Object> expiry = Expirations.timeToIdleExpiration(new Duration(15L, TimeUnit.MILLISECONDS)); offHeapStore = createAndInitStore(timeSource, expiry); try { final String key = "foo"; final String value = "bar"; offHeapStore.put(key, value); final Store.ValueHolder<String> firstValueHolder = offHeapStore.getAndFault(key); offHeapStore.put(key, value); final Store.ValueHolder<String> secondValueHolder = offHeapStore.getAndFault(key); timeSource.advanceTime(10); ((AbstractValueHolder) firstValueHolder).accessed(timeSource.getTimeMillis(), expiry.getExpiryForAccess(key, supplierOf(value))); timeSource.advanceTime(10); ((AbstractValueHolder) secondValueHolder).accessed(timeSource.getTimeMillis(), expiry.getExpiryForAccess(key, supplierOf(value))); assertThat(offHeapStore.flush(key, new DelegatingValueHolder<String>(firstValueHolder)), is(false)); assertThat(offHeapStore.flush(key, new DelegatingValueHolder<String>(secondValueHolder)), is(true)); timeSource.advanceTime(10); // this should NOT affect assertThat(offHeapStore.getAndFault(key).lastAccessTime(TimeUnit.MILLISECONDS), is(secondValueHolder.creationTime(TimeUnit.MILLISECONDS) + 20)); } finally { destroyStore(offHeapStore); } } @Test public void testFlushUpdatesHits() throws StoreAccessException { offHeapStore = createAndInitStore(timeSource, Expirations.noExpiration()); final String key = "foo1"; final String value = "bar1"; offHeapStore.put(key, value); for(int i = 0; i < 5; i++) { final Store.ValueHolder<String> valueHolder = offHeapStore.getAndFault(key); timeSource.advanceTime(1); ((AbstractValueHolder)valueHolder).accessed(timeSource.getTimeMillis(), new Duration(1L, TimeUnit.MILLISECONDS)); assertThat(offHeapStore.flush(key, new DelegatingValueHolder<String>(valueHolder)), is(true)); } assertThat(offHeapStore.getAndFault(key).hits(), is(5L)); } @Test public void testExpiryEventFiredOnExpiredCachedEntry() throws StoreAccessException { offHeapStore = createAndInitStore(timeSource, Expirations.timeToIdleExpiration(new Duration(10L, TimeUnit.MILLISECONDS))); final List<String> expiredKeys = new ArrayList<String>(); offHeapStore.getStoreEventSource().addEventListener(new StoreEventListener<String, String>() { @Override public void onEvent(StoreEvent<String, String> event) { if (event.getType() == EventType.EXPIRED) { expiredKeys.add(event.getKey()); } } }); offHeapStore.put("key1", "value1"); offHeapStore.put("key2", "value2"); offHeapStore.get("key1"); // Bring the entry to the caching tier timeSource.advanceTime(11); // Expire the elements offHeapStore.get("key1"); offHeapStore.get("key2"); assertThat(expiredKeys, containsInAnyOrder("key1", "key2")); assertThat(getExpirationStatistic(offHeapStore).count(StoreOperationOutcomes.ExpirationOutcome.SUCCESS), is(2L)); } @Test public void testGetWithExpiryOnAccess() throws Exception { offHeapStore = createAndInitStore(timeSource, Expirations.builder().setAccess(Duration.ZERO).build()); offHeapStore.put("key", "value"); final AtomicReference<String> expired = new AtomicReference<String>(); offHeapStore.getStoreEventSource().addEventListener(new StoreEventListener<String, String>() { @Override public void onEvent(StoreEvent<String, String> event) { if (event.getType() == EventType.EXPIRED) { expired.set(event.getKey()); } } }); assertThat(offHeapStore.get("key"), valueHeld("value")); assertThat(expired.get(), is("key")); } @Test public void testExpiryCreateException() throws Exception{ offHeapStore = createAndInitStore(timeSource, new Expiry<String, String>() { @Override public Duration getExpiryForCreation(String key, String value) { throw new RuntimeException(); } @Override public Duration getExpiryForAccess(String key, ValueSupplier<? extends String> value) { throw new AssertionError(); } @Override public Duration getExpiryForUpdate(String key, ValueSupplier<? extends String> oldValue, String newValue) { throw new AssertionError(); } }); offHeapStore.put("key", "value"); assertNull(offHeapStore.get("key")); } @Test public void testExpiryAccessException() throws Exception{ offHeapStore = createAndInitStore(timeSource, new Expiry<String, String>() { @Override public Duration getExpiryForCreation(String key, String value) { return Duration.INFINITE; } @Override public Duration getExpiryForAccess(String key, ValueSupplier<? extends String> value) { throw new RuntimeException(); } @Override public Duration getExpiryForUpdate(String key, ValueSupplier<? extends String> oldValue, String newValue) { return null; } }); offHeapStore.put("key", "value"); assertThat(offHeapStore.get("key"), valueHeld("value")); assertNull(offHeapStore.get("key")); } @Test public void testExpiryUpdateException() throws Exception{ offHeapStore = createAndInitStore(timeSource, new Expiry<String, String>() { @Override public Duration getExpiryForCreation(String key, String value) { return Duration.INFINITE; } @Override public Duration getExpiryForAccess(String key, ValueSupplier<? extends String> value) { return Duration.INFINITE; } @Override public Duration getExpiryForUpdate(String key, ValueSupplier<? extends String> oldValue, String newValue) { if (timeSource.getTimeMillis() > 0) { throw new RuntimeException(); } return Duration.INFINITE; } }); offHeapStore.put("key", "value"); assertThat(offHeapStore.get("key").value(), is("value")); timeSource.advanceTime(1000); offHeapStore.put("key", "newValue"); assertNull(offHeapStore.get("key")); } @Test public void testGetAndFaultOnExpiredEntry() throws StoreAccessException { offHeapStore = createAndInitStore(timeSource, Expirations.timeToIdleExpiration(new Duration(10L, TimeUnit.MILLISECONDS))); try { offHeapStore.put("key", "value"); timeSource.advanceTime(20L); Store.ValueHolder<String> valueHolder = offHeapStore.getAndFault("key"); assertThat(valueHolder, nullValue()); assertThat(getExpirationStatistic(offHeapStore).count(StoreOperationOutcomes.ExpirationOutcome.SUCCESS), is(1L)); } finally { destroyStore(offHeapStore); } } @Test public void testComputeExpiresOnAccess() throws StoreAccessException { timeSource.advanceTime(1000L); offHeapStore = createAndInitStore(timeSource, Expirations.builder().setAccess(Duration.ZERO).setUpdate(Duration.ZERO).build()); offHeapStore.put("key", "value"); Store.ValueHolder<String> result = offHeapStore.compute("key", new BiFunction<String, String, String>() { @Override public String apply(String s, String s2) { return s2; } }, new NullaryFunction<Boolean>() { @Override public Boolean apply() { return false; } }); assertThat(result, valueHeld("value")); } @Test public void testComputeExpiresOnUpdate() throws StoreAccessException { timeSource.advanceTime(1000L); offHeapStore = createAndInitStore(timeSource, Expirations.builder().setAccess(Duration.ZERO).setUpdate(Duration.ZERO).build()); offHeapStore.put("key", "value"); Store.ValueHolder<String> result = offHeapStore.compute("key", new BiFunction<String, String, String>() { @Override public String apply(String s, String s2) { return "newValue"; } }, new NullaryFunction<Boolean>() { @Override public Boolean apply() { return false; } }); assertThat(result, valueHeld("newValue")); } @Test public void testComputeOnExpiredEntry() throws StoreAccessException { offHeapStore = createAndInitStore(timeSource, Expirations.timeToIdleExpiration(new Duration(10L, TimeUnit.MILLISECONDS))); offHeapStore.put("key", "value"); timeSource.advanceTime(20L); offHeapStore.compute("key", new BiFunction<String, String, String>() { @Override public String apply(String mappedKey, String mappedValue) { assertThat(mappedKey, is("key")); assertThat(mappedValue, Matchers.nullValue()); return "value2"; } }); assertThat(getExpirationStatistic(offHeapStore).count(StoreOperationOutcomes.ExpirationOutcome.SUCCESS), is(1L)); } @Test public void testComputeIfAbsentOnExpiredEntry() throws StoreAccessException { offHeapStore = createAndInitStore(timeSource, Expirations.timeToIdleExpiration(new Duration(10L, TimeUnit.MILLISECONDS))); offHeapStore.put("key", "value"); timeSource.advanceTime(20L); offHeapStore.computeIfAbsent("key", new Function<String, String>() { @Override public String apply(String mappedKey) { assertThat(mappedKey, is("key")); return "value2"; } }); assertThat(getExpirationStatistic(offHeapStore).count(StoreOperationOutcomes.ExpirationOutcome.SUCCESS), is(1L)); } @Test public void testIteratorDoesNotSkipOrExpiresEntries() throws Exception { offHeapStore = createAndInitStore(timeSource, Expirations.timeToLiveExpiration(new Duration(10L, TimeUnit.MILLISECONDS))); offHeapStore.put("key1", "value1"); offHeapStore.put("key2", "value2"); timeSource.advanceTime(11L); offHeapStore.put("key3", "value3"); offHeapStore.put("key4", "value4"); final List<String> expiredKeys = new ArrayList<String>(); offHeapStore.getStoreEventSource().addEventListener(new StoreEventListener<String, String>() { @Override public void onEvent(StoreEvent<String, String> event) { if (event.getType() == EventType.EXPIRED) { expiredKeys.add(event.getKey()); } } }); List<String> iteratedKeys = new ArrayList<String>(); Store.Iterator<Cache.Entry<String, Store.ValueHolder<String>>> iterator = offHeapStore.iterator(); while(iterator.hasNext()) { iteratedKeys.add(iterator.next().getKey()); } assertThat(iteratedKeys, containsInAnyOrder("key1", "key2", "key3", "key4")); assertThat(expiredKeys.isEmpty(), is(true)); } @Test public void testIteratorWithSingleExpiredEntry() throws Exception { offHeapStore = createAndInitStore(timeSource, Expirations.timeToLiveExpiration(new Duration(10L, TimeUnit.MILLISECONDS))); offHeapStore.put("key1", "value1"); timeSource.advanceTime(11L); Store.Iterator<Cache.Entry<String, Store.ValueHolder<String>>> iterator = offHeapStore.iterator(); assertTrue(iterator.hasNext()); assertThat(iterator.next().getKey(), equalTo("key1")); assertFalse(iterator.hasNext()); } @Test public void testIteratorWithSingleNonExpiredEntry() throws Exception { offHeapStore = createAndInitStore(timeSource, Expirations.timeToLiveExpiration(new Duration(10L, TimeUnit.MILLISECONDS))); offHeapStore.put("key1", "value1"); timeSource.advanceTime(5L); Store.Iterator<Cache.Entry<String, Store.ValueHolder<String>>> iterator = offHeapStore.iterator(); assertTrue(iterator.hasNext()); assertThat(iterator.next().getKey(), is("key1")); } @Test public void testIteratorOnEmptyStore() throws Exception { offHeapStore = createAndInitStore(timeSource, Expirations.timeToLiveExpiration(new Duration(10L, TimeUnit.MILLISECONDS))); Store.Iterator<Cache.Entry<String, Store.ValueHolder<String>>> iterator = offHeapStore.iterator(); assertFalse(iterator.hasNext()); } protected abstract AbstractOffHeapStore<String, String> createAndInitStore(final TimeSource timeSource, final Expiry<? super String, ? super String> expiry); protected abstract AbstractOffHeapStore<String, byte[]> createAndInitStore(final TimeSource timeSource, final Expiry<? super String, ? super byte[]> expiry, EvictionAdvisor<? super String, ? super byte[]> evictionAdvisor); protected abstract void destroyStore(AbstractOffHeapStore<?, ?> store); private void performEvictionTest(TestTimeSource timeSource, Expiry<Object, Object> expiry, EvictionAdvisor<String, byte[]> evictionAdvisor) throws StoreAccessException { AbstractOffHeapStore<String, byte[]> offHeapStore = createAndInitStore(timeSource, expiry, evictionAdvisor); try { @SuppressWarnings("unchecked") StoreEventListener<String, byte[]> listener = mock(StoreEventListener.class); offHeapStore.getStoreEventSource().addEventListener(listener); byte[] value = getBytes(MemoryUnit.KB.toBytes(200)); offHeapStore.put("key1", value); offHeapStore.put("key2", value); offHeapStore.put("key3", value); offHeapStore.put("key4", value); offHeapStore.put("key5", value); offHeapStore.put("key6", value); Matcher<StoreEvent<String, byte[]>> matcher = eventType(EventType.EVICTED); verify(listener, atLeast(1)).onEvent(argThat(matcher)); } finally { destroyStore(offHeapStore); } } public static <K, V> Matcher<StoreEvent<K, V>> eventType(final EventType type) { return new TypeSafeMatcher<StoreEvent<K, V>>() { @Override protected boolean matchesSafely(StoreEvent<K, V> item) { return item.getType().equals(type); } @Override public void describeTo(Description description) { description.appendText("store event of type '").appendValue(type).appendText("'"); } }; } @SuppressWarnings("unchecked") private OperationStatistic<StoreOperationOutcomes.ExpirationOutcome> getExpirationStatistic(Store<?, ?> store) { StatisticsManager statisticsManager = new StatisticsManager(); statisticsManager.root(store); TreeNode treeNode = statisticsManager.queryForSingleton(QueryBuilder.queryBuilder() .descendants() .filter(org.terracotta.context.query.Matchers.context( org.terracotta.context.query.Matchers.<ContextElement>allOf(org.terracotta.context.query.Matchers.identifier(org.terracotta.context.query.Matchers .subclassOf(OperationStatistic.class)), org.terracotta.context.query.Matchers.attributes(org.terracotta.context.query.Matchers.hasAttribute("name", "expiration"))))) .build()); return (OperationStatistic<StoreOperationOutcomes.ExpirationOutcome>) treeNode.getContext().attributes().get("this"); } private byte[] getBytes(long valueLength) { assertThat(valueLength, lessThan((long) Integer.MAX_VALUE)); int valueLengthInt = (int) valueLength; byte[] value = new byte[valueLengthInt]; new Random().nextBytes(value); return value; } private static class TestTimeSource implements TimeSource { private long time = 0; @Override public long getTimeMillis() { return time; } public void advanceTime(long step) { time += step; } } public static class DelegatingValueHolder<T> implements Store.ValueHolder<T> { private final Store.ValueHolder<T> valueHolder; public DelegatingValueHolder(final Store.ValueHolder<T> valueHolder) { this.valueHolder = valueHolder; } @Override public T value() { return valueHolder.value(); } @Override public long creationTime(final TimeUnit unit) { return valueHolder.creationTime(unit); } @Override public long expirationTime(final TimeUnit unit) { return valueHolder.expirationTime(unit); } @Override public boolean isExpired(final long expirationTime, final TimeUnit unit) { return valueHolder.isExpired(expirationTime, unit); } @Override public long lastAccessTime(final TimeUnit unit) { return valueHolder.lastAccessTime(unit); } @Override public float hitRate(final long now, final TimeUnit unit) { return valueHolder.hitRate(now, unit); } @Override public long hits() { return valueHolder.hits(); } @Override public long getId() { return valueHolder.getId(); } } static class SimpleValueHolder<T> extends AbstractValueHolder<T> { private final T value; public SimpleValueHolder(T v, long creationTime, long expirationTime) { super(-1, creationTime, expirationTime); this.value = v; } @Override protected TimeUnit nativeTimeUnit() { return TimeUnit.MILLISECONDS; } @Override public T value() { return value; } @Override public long creationTime(TimeUnit unit) { return 0; } @Override public long expirationTime(TimeUnit unit) { return 0; } @Override public boolean isExpired(long expirationTime, TimeUnit unit) { return false; } @Override public long lastAccessTime(TimeUnit unit) { return 0; } @Override public float hitRate(long now, TimeUnit unit) { return 0; } @Override public long hits() { return 0; } @Override public long getId() { return 0; } } }