/* * 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.heap; import org.ehcache.Cache; import org.ehcache.config.units.EntryUnit; import org.ehcache.core.spi.store.StoreAccessException; import org.ehcache.expiry.Expirations; import org.ehcache.core.spi.function.BiFunction; import org.ehcache.core.spi.function.Function; import org.ehcache.core.spi.function.NullaryFunction; import org.ehcache.impl.copy.IdentityCopier; import org.ehcache.core.events.NullStoreEventDispatcher; import org.ehcache.impl.internal.sizeof.NoopSizeOfEngine; import org.ehcache.core.spi.time.SystemTimeSource; import org.ehcache.core.spi.store.Store; import org.ehcache.spi.copy.Copier; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import static java.util.Collections.singleton; import static java.util.Collections.singletonMap; import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * OnHeapStoreKeyCopierTest */ @RunWith(Parameterized.class) public class OnHeapStoreKeyCopierTest { private static final Key KEY = new Key("WHat?"); public static final String VALUE = "TheAnswer"; public static final NullaryFunction<Boolean> NOT_REPLACE_EQUAL = new NullaryFunction<Boolean>() { @Override public Boolean apply() { return false; } }; public static final NullaryFunction<Boolean> REPLACE_EQUAL = new NullaryFunction<Boolean>() { @Override public Boolean apply() { return true; } }; @Parameterized.Parameters(name = "copyForRead: {0} - copyForWrite: {1}") public static Collection<Object[]> config() { return Arrays.asList(new Object[][] { {false, false}, {false, true}, {true, false}, {true, true} }); } @Parameterized.Parameter(value = 0) public boolean copyForRead; @Parameterized.Parameter(value = 1) public boolean copyForWrite; private OnHeapStore<Key, String> store; @Before public void setUp() { Store.Configuration configuration = mock(Store.Configuration.class); when(configuration.getResourcePools()).thenReturn(newResourcePoolsBuilder().heap(10, EntryUnit.ENTRIES).build()); when(configuration.getKeyType()).thenReturn(Key.class); when(configuration.getValueType()).thenReturn(String.class); when(configuration.getExpiry()).thenReturn(Expirations.noExpiration()); @SuppressWarnings("unchecked") Store.Configuration<Key, String> config = configuration; Copier<Key> keyCopier = new Copier<Key>() { @Override public Key copyForRead(Key obj) { if (copyForRead) { return new Key(obj); } return obj; } @Override public Key copyForWrite(Key obj) { if (copyForWrite) { return new Key(obj); } return obj; } }; store = new OnHeapStore<Key, String>(config, SystemTimeSource.INSTANCE, keyCopier, new IdentityCopier<String>(), new NoopSizeOfEngine(), NullStoreEventDispatcher.<Key, String>nullStoreEventDispatcher()); } @Test public void testPutAndGet() throws StoreAccessException { Key copyKey = new Key(KEY); store.put(copyKey, VALUE); copyKey.state = "Different!"; Store.ValueHolder<String> firstStoreValue = store.get(KEY); Store.ValueHolder<String> secondStoreValue = store.get(copyKey); if (copyForWrite) { assertThat(firstStoreValue.value(), is(VALUE)); assertThat(secondStoreValue, nullValue()); } else { assertThat(firstStoreValue, nullValue()); assertThat(secondStoreValue.value(), is(VALUE)); } } @Test public void testCompute() throws StoreAccessException { final Key copyKey = new Key(KEY); store.compute(copyKey, new BiFunction<Key, String, String>() { @Override public String apply(Key key, String value) { assertThat(key, is(copyKey)); return VALUE; } }); copyKey.state = "Different!"; store.compute(copyKey, new BiFunction<Key, String, String>() { @Override public String apply(Key key, String value) { if (copyForWrite) { assertThat(value, nullValue()); } else { assertThat(value, is(VALUE)); assertThat(key, is(copyKey)); if (copyForRead) { key.state = "Changed!"; } } return value; } }); if (copyForRead) { assertThat(copyKey.state, is("Different!")); } } @Test public void testComputeWithoutReplaceEqual() throws StoreAccessException { final Key copyKey = new Key(KEY); store.compute(copyKey, new BiFunction<Key, String, String>() { @Override public String apply(Key key, String value) { assertThat(key, is(copyKey)); return VALUE; } }, NOT_REPLACE_EQUAL); copyKey.state = "Different!"; store.compute(copyKey, new BiFunction<Key, String, String>() { @Override public String apply(Key key, String value) { if (copyForWrite) { assertThat(value, nullValue()); } else { assertThat(value, is(VALUE)); assertThat(key, is(copyKey)); if (copyForRead) { key.state = "Changed!"; } } return value; } }, NOT_REPLACE_EQUAL); if (copyForRead) { assertThat(copyKey.state, is("Different!")); } } @Test public void testComputeWithReplaceEqual() throws StoreAccessException { final Key copyKey = new Key(KEY); store.compute(copyKey, new BiFunction<Key, String, String>() { @Override public String apply(Key key, String value) { assertThat(key, is(copyKey)); return VALUE; } }, REPLACE_EQUAL); copyKey.state = "Different!"; store.compute(copyKey, new BiFunction<Key, String, String>() { @Override public String apply(Key key, String value) { if (copyForWrite) { assertThat(value, nullValue()); } else { assertThat(value, is(VALUE)); assertThat(key, is(copyKey)); if (copyForRead) { key.state = "Changed!"; } } return value; } }, REPLACE_EQUAL); if (copyForRead) { assertThat(copyKey.state, is("Different!")); } } @Test public void testIteration() throws StoreAccessException { store.put(KEY, VALUE); Store.Iterator<Cache.Entry<Key, Store.ValueHolder<String>>> iterator = store.iterator(); assertThat(iterator.hasNext(), is(true)); while (iterator.hasNext()) { Cache.Entry<Key, Store.ValueHolder<String>> entry = iterator.next(); if (copyForRead || copyForWrite) { assertThat(entry.getKey(), not(sameInstance(KEY))); } else { assertThat(entry.getKey(), sameInstance(KEY)); } } } @Test public void testComputeIfAbsent() throws StoreAccessException { store.computeIfAbsent(KEY, new Function<Key, String>() { @Override public String apply(Key key) { if (copyForRead || copyForWrite) { assertThat(key, not(sameInstance(KEY))); } else { assertThat(key, sameInstance(KEY)); } return VALUE; } }); } @Test public void testBulkCompute() throws StoreAccessException { final AtomicReference<Key> keyRef = new AtomicReference<Key>(); store.bulkCompute(singleton(KEY), new Function<Iterable<? extends Map.Entry<? extends Key, ? extends String>>, Iterable<? extends Map.Entry<? extends Key, ? extends String>>>() { @Override public Iterable<? extends Map.Entry<? extends Key, ? extends String>> apply(Iterable<? extends Map.Entry<? extends Key, ? extends String>> entries) { Key key = entries.iterator().next().getKey(); if (copyForRead || copyForWrite) { assertThat(key, not(sameInstance(KEY))); } else { assertThat(key, sameInstance(KEY)); } keyRef.set(key); return singletonMap(KEY, VALUE).entrySet(); } }); store.bulkCompute(singleton(KEY), new Function<Iterable<? extends Map.Entry<? extends Key, ? extends String>>, Iterable<? extends Map.Entry<? extends Key, ? extends String>>>() { @Override public Iterable<? extends Map.Entry<? extends Key, ? extends String>> apply(Iterable<? extends Map.Entry<? extends Key, ? extends String>> entries) { if (copyForRead) { assertThat(entries.iterator().next().getKey(), not(sameInstance(keyRef.get()))); } return singletonMap(KEY, VALUE).entrySet(); } }); } @Test public void testBulkComputeWithoutReplaceEqual() throws StoreAccessException { store.bulkCompute(singleton(KEY), new Function<Iterable<? extends Map.Entry<? extends Key, ? extends String>>, Iterable<? extends Map.Entry<? extends Key, ? extends String>>>() { @Override public Iterable<? extends Map.Entry<? extends Key, ? extends String>> apply(Iterable<? extends Map.Entry<? extends Key, ? extends String>> entries) { if (copyForRead || copyForWrite) { assertThat(entries.iterator().next().getKey(), not(sameInstance(KEY))); } else { assertThat(entries.iterator().next().getKey(), sameInstance(KEY)); } return singletonMap(KEY, VALUE).entrySet(); } }, NOT_REPLACE_EQUAL); } @Test public void testBulkComputeWithReplaceEqual() throws StoreAccessException { store.bulkCompute(singleton(KEY), new Function<Iterable<? extends Map.Entry<? extends Key, ? extends String>>, Iterable<? extends Map.Entry<? extends Key, ? extends String>>>() { @Override public Iterable<? extends Map.Entry<? extends Key, ? extends String>> apply(Iterable<? extends Map.Entry<? extends Key, ? extends String>> entries) { if (copyForRead || copyForWrite) { assertThat(entries.iterator().next().getKey(), not(sameInstance(KEY))); } else { assertThat(entries.iterator().next().getKey(), sameInstance(KEY)); } return singletonMap(KEY, VALUE).entrySet(); } }, REPLACE_EQUAL); } @Test public void testBulkComputeIfAbsent() throws StoreAccessException { store.bulkComputeIfAbsent(singleton(KEY), new Function<Iterable<? extends Key>, Iterable<? extends Map.Entry<? extends Key, ? extends String>>>() { @Override public Iterable<? extends Map.Entry<? extends Key, ? extends String>> apply(Iterable<? extends Key> keys) { if (copyForWrite || copyForRead) { assertThat(keys.iterator().next(), not(sameInstance(KEY))); } else { assertThat(keys.iterator().next(), sameInstance(KEY)); } return singletonMap(KEY, VALUE).entrySet(); } }); } public static final class Key { String state; final int hashCode; public Key(String state) { this.state = state; this.hashCode = state.hashCode(); } public Key(Key key) { this.state = key.state; this.hashCode = key.hashCode(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Key value = (Key) o; return state.equals(value.state); } @Override public int hashCode() { return hashCode; } } }