/* * Copyright 2015 Ben Manes. All Rights Reserved. * * 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 com.github.benmanes.caffeine.cache; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeDiagnosingMatcher; import com.github.benmanes.caffeine.cache.Async.AsyncExpiry; import com.github.benmanes.caffeine.cache.Async.AsyncWeigher; import com.github.benmanes.caffeine.testing.DescriptionBuilder; import com.google.common.testing.SerializableTester; /** * A matcher that evaluates a cache by creating a serialized copy and checking its equality. * * @author ben.manes@gmail.com (Ben Manes) */ public final class IsCacheReserializable<T> extends TypeSafeDiagnosingMatcher<T> { DescriptionBuilder desc; private IsCacheReserializable() {} @Override public void describeTo(Description description) { description.appendValue("serialized copy"); if (desc.getDescription() != description) { description.appendText(desc.getDescription().toString()); } } @Override public boolean matchesSafely(T original, Description description) { desc = new DescriptionBuilder(description); T copy = SerializableTester.reserialize(original); if (original instanceof AsyncLoadingCache<?, ?>) { @SuppressWarnings("unchecked") AsyncLoadingCache<Object, Object> asyncCache = (AsyncLoadingCache<Object, Object>) original; @SuppressWarnings("unchecked") AsyncLoadingCache<Object, Object> asyncCopy = (AsyncLoadingCache<Object, Object>) copy; checkAsynchronousCache(asyncCache, asyncCopy, desc); } else if (original instanceof Cache<?, ?>) { @SuppressWarnings("unchecked") Cache<Object, Object> syncCache = (Cache<Object, Object>) original; @SuppressWarnings("unchecked") Cache<Object, Object> syncCopy = (Cache<Object, Object>) copy; checkSyncronousCache(syncCache, syncCopy, desc); } else { throw new UnsupportedOperationException(); } return desc.matches(); } private static <K, V> void checkAsynchronousCache(AsyncLoadingCache<K, V> original, AsyncLoadingCache<K, V> copy, DescriptionBuilder desc) { if (!IsValidAsyncCache.<K, V>validAsyncCache().matchesSafely(copy, desc.getDescription())) { desc.expected("valid async cache"); } else if (original instanceof UnboundedLocalCache.UnboundedLocalAsyncLoadingCache<?, ?>) { checkUnboundedAsyncLocalLoadingCache( (UnboundedLocalCache.UnboundedLocalAsyncLoadingCache<K, V>) original, (UnboundedLocalCache.UnboundedLocalAsyncLoadingCache<K, V>) copy, desc); } else if (original instanceof BoundedLocalCache.BoundedLocalAsyncLoadingCache<?, ?>) { checkBoundedAsyncLocalLoadingCache( (BoundedLocalCache.BoundedLocalAsyncLoadingCache<K, V>) original, (BoundedLocalCache.BoundedLocalAsyncLoadingCache<K, V>) copy, desc); } } private static <K, V> void checkSyncronousCache(Cache<K, V> original, Cache<K, V> copy, DescriptionBuilder desc) { if (!IsValidCache.<K, V>validCache().matchesSafely(copy, desc.getDescription())) { desc.expected("valid cache"); return; } checkIfUnbounded(original, copy, desc); checkIfBounded(original, copy, desc); } /* ---------------- Unbounded -------------- */ @SuppressWarnings("unchecked") private static <K, V> void checkIfUnbounded( Cache<K, V> original, Cache<K, V> copy, DescriptionBuilder desc) { if (original instanceof UnboundedLocalCache.UnboundedLocalManualCache<?, ?>) { checkUnoundedLocalManualCache((UnboundedLocalCache.UnboundedLocalManualCache<K, V>) original, (UnboundedLocalCache.UnboundedLocalManualCache<K, V>) copy, desc); } if (original instanceof UnboundedLocalCache.UnboundedLocalLoadingCache<?, ?>) { checkUnboundedLocalLoadingCache( (UnboundedLocalCache.UnboundedLocalLoadingCache<K, V>) original, (UnboundedLocalCache.UnboundedLocalLoadingCache<K, V>) copy, desc); } if (original instanceof LocalAsyncLoadingCache<?, ?, ?>.LoadingCacheView) { LocalAsyncLoadingCache<?, ?, ?> originalOuter = ((LocalAsyncLoadingCache<?, ?, ?>.LoadingCacheView) original).getOuter(); LocalAsyncLoadingCache<?, ?, ?> copyOuter = ((LocalAsyncLoadingCache<?, ?, ?>.LoadingCacheView) copy).getOuter(); if (originalOuter instanceof UnboundedLocalCache.UnboundedLocalAsyncLoadingCache<?, ?>) { checkUnboundedAsyncLocalLoadingCache( (UnboundedLocalCache.UnboundedLocalAsyncLoadingCache<K, V>) originalOuter, (UnboundedLocalCache.UnboundedLocalAsyncLoadingCache<K, V>) copyOuter, desc); } } } private static <K, V> void checkUnoundedLocalManualCache( UnboundedLocalCache.UnboundedLocalManualCache<K, V> original, UnboundedLocalCache.UnboundedLocalManualCache<K, V> copy, DescriptionBuilder desc) { checkUnboundedLocalCache(original.cache, copy.cache, desc); } private static <K, V> void checkUnboundedLocalLoadingCache( UnboundedLocalCache.UnboundedLocalLoadingCache<K, V> original, UnboundedLocalCache.UnboundedLocalLoadingCache<K, V> copy, DescriptionBuilder desc) { desc.expectThat("same cacheLoader", copy.loader, is(original.loader)); } private static <K, V> void checkUnboundedAsyncLocalLoadingCache( UnboundedLocalCache.UnboundedLocalAsyncLoadingCache<K, V> original, UnboundedLocalCache.UnboundedLocalAsyncLoadingCache<K, V> copy, DescriptionBuilder desc) { checkUnboundedLocalCache(original.cache, copy.cache, desc); desc.expectThat("same cacheLoader", copy.loader, is(original.loader)); } private static <K, V> void checkUnboundedLocalCache(UnboundedLocalCache<K, V> original, UnboundedLocalCache<K, V> copy, DescriptionBuilder desc) { desc.expectThat("estimated empty", copy.estimatedSize(), is(0L)); desc.expectThat("same ticker", copy.ticker, is(original.ticker)); desc.expectThat("same writer", copy.writer, is(original.writer)); desc.expectThat("same isRecordingStats", copy.isRecordingStats, is(original.isRecordingStats)); if (original.removalListener == null) { desc.expectThat("same removalListener", copy.removalListener, is(nullValue())); } else if (copy.removalListener == null) { desc.expected("non-null removalListener"); } else if (copy.removalListener.getClass() != original.removalListener.getClass()) { desc.expected("same removalListener but was " + copy.removalListener.getClass()); } } /* ---------------- Bounded -------------- */ @SuppressWarnings("unchecked") private static <K, V> void checkIfBounded( Cache<K, V> original, Cache<K, V> copy, DescriptionBuilder desc) { if (original instanceof BoundedLocalCache.BoundedLocalManualCache<?, ?>) { checkBoundedLocalManualCache((BoundedLocalCache.BoundedLocalManualCache<K, V>) original, (BoundedLocalCache.BoundedLocalManualCache<K, V>) copy, desc); } if (original instanceof BoundedLocalCache.BoundedLocalLoadingCache<?, ?>) { checkBoundedLocalLoadingCache((BoundedLocalCache.BoundedLocalLoadingCache<K, V>) original, (BoundedLocalCache.BoundedLocalLoadingCache<K, V>) copy, desc); } if (original instanceof LocalAsyncLoadingCache<?, ?, ?>.LoadingCacheView) { LocalAsyncLoadingCache<?, ?, ?> originalOuter = ((LocalAsyncLoadingCache<?, ?, ?>.LoadingCacheView) original).getOuter(); LocalAsyncLoadingCache<?, ?, ?> copyOuter = ((LocalAsyncLoadingCache<?, ?, ?>.LoadingCacheView) copy).getOuter(); if (originalOuter instanceof BoundedLocalCache.BoundedLocalAsyncLoadingCache<?, ?>) { checkBoundedAsyncLocalLoadingCache( (BoundedLocalCache.BoundedLocalAsyncLoadingCache<K, V>) originalOuter, (BoundedLocalCache.BoundedLocalAsyncLoadingCache<K, V>) copyOuter, desc); } } } private static <K, V> void checkBoundedLocalManualCache( BoundedLocalCache.BoundedLocalManualCache<K, V> original, BoundedLocalCache.BoundedLocalManualCache<K, V> copy, DescriptionBuilder desc) { checkBoundedLocalCache(original.cache, copy.cache, desc); } private static <K, V> void checkBoundedLocalLoadingCache( BoundedLocalCache.BoundedLocalLoadingCache<K, V> original, BoundedLocalCache.BoundedLocalLoadingCache<K, V> copy, DescriptionBuilder desc) { desc.expectThat("same cacheLoader", copy.cache.cacheLoader, is(original.cache.cacheLoader)); } private static <K, V> void checkBoundedAsyncLocalLoadingCache( BoundedLocalCache.BoundedLocalAsyncLoadingCache<K, V> original, BoundedLocalCache.BoundedLocalAsyncLoadingCache<K, V> copy, DescriptionBuilder desc) { checkBoundedLocalCache(original.cache, copy.cache, desc); desc.expectThat("same cacheLoader", copy.loader, is(original.loader)); } private static <K, V> void checkBoundedLocalCache(BoundedLocalCache<K, V> original, BoundedLocalCache<K, V> copy, DescriptionBuilder desc) { desc.expectThat("empty", copy.estimatedSize(), is(0L)); desc.expectThat("same weigher", unwrapWeigher(copy.weigher).getClass(), is(equalTo(unwrapWeigher(original.weigher).getClass()))); desc.expectThat("same nodeFactory", copy.nodeFactory, is(original.nodeFactory)); if (original.evicts()) { desc.expectThat("same maximumWeight", copy.maximum(), is(original.maximum())); desc.expectThat("same maximumEdenWeight", copy.edenMaximum(), is(original.edenMaximum())); } if (original.expiresVariable()) { desc.expectThat("same expiry", unwrapExpiry(copy.expiry()).getClass(), is(equalTo(unwrapExpiry(original.expiry()).getClass()))); } else { desc.expectThat("", copy.expiresVariable(), is(false)); } if (original.expiresAfterAccess()) { desc.expectThat("same expiresAfterAccessNanos", copy.expiresAfterAccessNanos(), is(original.expiresAfterAccessNanos())); } else { desc.expectThat("", copy.expiresAfterAccess(), is(false)); } if (original.expiresAfterWrite()) { desc.expectThat("same expireAfterWriteNanos", copy.expiresAfterWriteNanos(), is(original.expiresAfterWriteNanos())); } else { desc.expectThat("", copy.expiresAfterWrite(), is(false)); } if (original.refreshAfterWrite()) { desc.expectThat("same refreshAfterWriteNanos", copy.refreshAfterWriteNanos(), is(original.refreshAfterWriteNanos())); } else { desc.expectThat("", copy.refreshAfterWrite(), is(false)); } if (original.removalListener() == null) { desc.expectThat("same removalListener", copy.removalListener(), is(nullValue())); } else if (copy.removalListener() == null) { desc.expected("non-null removalListener"); } else if (copy.removalListener().getClass() != original.removalListener().getClass()) { desc.expected("same removalListener but was " + copy.removalListener().getClass()); } } @SuppressWarnings("unchecked") private static <K, V> Weigher<K, V> unwrapWeigher(Weigher<K, V> weigher) { for (;;) { if (weigher instanceof BoundedWeigher<?, ?>) { weigher = (Weigher<K, V>) ((BoundedWeigher<?, ?>) weigher).delegate; } else if (weigher instanceof AsyncWeigher<?, ?>) { weigher = (Weigher<K, V>) ((AsyncWeigher<?, ?>) weigher).delegate; } else { return weigher; } } } @SuppressWarnings("unchecked") private static <K, V> Expiry<K, V> unwrapExpiry(Expiry<K, V> expiry) { for (;;) { if (expiry instanceof AsyncExpiry<?, ?>) { expiry = (Expiry<K, V>) ((AsyncExpiry<?, ?>) expiry).delegate; } else { return expiry; } } } public static <T> Matcher<T> reserializable() { return new IsCacheReserializable<T>(); } }