/* * 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 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.sameInstance; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * OnHeapStoreValueCopierTest */ @RunWith(Parameterized.class) public class OnHeapStoreValueCopierTest { private static final Long KEY = 42L; public static final Value VALUE = new 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<Long, Value> 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(Long.class); when(configuration.getValueType()).thenReturn(Value.class); when(configuration.getExpiry()).thenReturn(Expirations.noExpiration()); @SuppressWarnings("unchecked") Store.Configuration<Long, Value> config = configuration; Copier<Value> valueCopier = new Copier<Value>() { @Override public Value copyForRead(Value obj) { if (copyForRead) { return new Value(obj.state); } return obj; } @Override public Value copyForWrite(Value obj) { if (copyForWrite) { return new Value(obj.state); } return obj; } }; store = new OnHeapStore<Long, Value>(config, SystemTimeSource.INSTANCE, new IdentityCopier<Long>(), valueCopier, new NoopSizeOfEngine(), NullStoreEventDispatcher.<Long, Value>nullStoreEventDispatcher()); } @Test public void testPutAndGet() throws StoreAccessException { store.put(KEY, VALUE); Store.ValueHolder<Value> firstStoreValue = store.get(KEY); Store.ValueHolder<Value> secondStoreValue = store.get(KEY); compareValues(VALUE, firstStoreValue.value()); compareValues(VALUE, secondStoreValue.value()); compareReadValues(firstStoreValue.value(), secondStoreValue.value()); } @Test public void testCompute() throws StoreAccessException { final Store.ValueHolder<Value> firstValue = store.compute(KEY, new BiFunction<Long, Value, Value>() { @Override public Value apply(Long aLong, Value value) { return VALUE; } }); store.compute(KEY, new BiFunction<Long, Value, Value>() { @Override public Value apply(Long aLong, Value value) { compareReadValues(value, firstValue.value()); return value; } }); compareValues(VALUE, firstValue.value()); } @Test public void testComputeWithoutReplaceEqual() throws StoreAccessException { final Store.ValueHolder<Value> firstValue = store.compute(KEY, new BiFunction<Long, Value, Value>() { @Override public Value apply(Long aLong, Value value) { return VALUE; } }, NOT_REPLACE_EQUAL); store.compute(KEY, new BiFunction<Long, Value, Value>() { @Override public Value apply(Long aLong, Value value) { compareReadValues(value, firstValue.value()); return value; } }, NOT_REPLACE_EQUAL); compareValues(VALUE, firstValue.value()); } @Test public void testComputeWithReplaceEqual() throws StoreAccessException { final Store.ValueHolder<Value> firstValue = store.compute(KEY, new BiFunction<Long, Value, Value>() { @Override public Value apply(Long aLong, Value value) { return VALUE; } }, REPLACE_EQUAL); store.compute(KEY, new BiFunction<Long, Value, Value>() { @Override public Value apply(Long aLong, Value value) { compareReadValues(value, firstValue.value()); return value; } }, REPLACE_EQUAL); compareValues(VALUE, firstValue.value()); } @Test public void testComputeIfAbsent() throws StoreAccessException { Store.ValueHolder<Value> computedValue = store.computeIfAbsent(KEY, new Function<Long, Value>() { @Override public Value apply(Long aLong) { return VALUE; } }); Store.ValueHolder<Value> secondComputedValue = store.computeIfAbsent(KEY, new Function<Long, Value>() { @Override public Value apply(Long aLong) { fail("There should have been a mapping"); return null; } }); compareValues(VALUE, computedValue.value()); compareReadValues(computedValue.value(), secondComputedValue.value()); } @Test public void testBulkCompute() throws StoreAccessException { final Map<Long, Store.ValueHolder<Value>> results = store.bulkCompute(singleton(KEY), new Function<Iterable<? extends Map.Entry<? extends Long, ? extends Value>>, Iterable<? extends Map.Entry<? extends Long, ? extends Value>>>() { @Override public Iterable<? extends Map.Entry<? extends Long, ? extends Value>> apply(Iterable<? extends Map.Entry<? extends Long, ? extends Value>> entries) { return singletonMap(KEY, VALUE).entrySet(); } }); store.bulkCompute(singleton(KEY), new Function<Iterable<? extends Map.Entry<? extends Long, ? extends Value>>, Iterable<? extends Map.Entry<? extends Long, ? extends Value>>>() { @Override public Iterable<? extends Map.Entry<? extends Long, ? extends Value>> apply(Iterable<? extends Map.Entry<? extends Long, ? extends Value>> entries) { compareReadValues(results.get(KEY).value(), entries.iterator().next().getValue()); return entries; } }); compareValues(VALUE, results.get(KEY).value()); } @Test public void testBulkComputeWithoutReplaceEqual() throws StoreAccessException { final Map<Long, Store.ValueHolder<Value>> results = store.bulkCompute(singleton(KEY), new Function<Iterable<? extends Map.Entry<? extends Long, ? extends Value>>, Iterable<? extends Map.Entry<? extends Long, ? extends Value>>>() { @Override public Iterable<? extends Map.Entry<? extends Long, ? extends Value>> apply(Iterable<? extends Map.Entry<? extends Long, ? extends Value>> entries) { return singletonMap(KEY, VALUE).entrySet(); } }, NOT_REPLACE_EQUAL); store.bulkCompute(singleton(KEY), new Function<Iterable<? extends Map.Entry<? extends Long, ? extends Value>>, Iterable<? extends Map.Entry<? extends Long, ? extends Value>>>() { @Override public Iterable<? extends Map.Entry<? extends Long, ? extends Value>> apply(Iterable<? extends Map.Entry<? extends Long, ? extends Value>> entries) { compareReadValues(results.get(KEY).value(), entries.iterator().next().getValue()); return entries; } }, NOT_REPLACE_EQUAL); compareValues(VALUE, results.get(KEY).value()); } @Test public void testBulkComputeWithReplaceEqual() throws StoreAccessException { final Map<Long, Store.ValueHolder<Value>> results = store.bulkCompute(singleton(KEY), new Function<Iterable<? extends Map.Entry<? extends Long, ? extends Value>>, Iterable<? extends Map.Entry<? extends Long, ? extends Value>>>() { @Override public Iterable<? extends Map.Entry<? extends Long, ? extends Value>> apply(Iterable<? extends Map.Entry<? extends Long, ? extends Value>> entries) { return singletonMap(KEY, VALUE).entrySet(); } }, REPLACE_EQUAL); store.bulkCompute(singleton(KEY), new Function<Iterable<? extends Map.Entry<? extends Long, ? extends Value>>, Iterable<? extends Map.Entry<? extends Long, ? extends Value>>>() { @Override public Iterable<? extends Map.Entry<? extends Long, ? extends Value>> apply(Iterable<? extends Map.Entry<? extends Long, ? extends Value>> entries) { compareReadValues(results.get(KEY).value(), entries.iterator().next().getValue()); return entries; } }, REPLACE_EQUAL); compareValues(VALUE, results.get(KEY).value()); } @Test public void testBulkComputeIfAbsent() throws StoreAccessException { Map<Long, Store.ValueHolder<Value>> results = store.bulkComputeIfAbsent(singleton(KEY), new Function<Iterable<? extends Long>, Iterable<? extends Map.Entry<? extends Long, ? extends Value>>>() { @Override public Iterable<? extends Map.Entry<? extends Long, ? extends Value>> apply(Iterable<? extends Long> longs) { return singletonMap(KEY, VALUE).entrySet(); } }); Map<Long, Store.ValueHolder<Value>> secondResults = store.bulkComputeIfAbsent(singleton(KEY), new Function<Iterable<? extends Long>, Iterable<? extends Map.Entry<? extends Long, ? extends Value>>>() { @Override public Iterable<? extends Map.Entry<? extends Long, ? extends Value>> apply(Iterable<? extends Long> longs) { fail("There should have been a mapping!"); return null; } }); compareValues(VALUE, results.get(KEY).value()); compareReadValues(results.get(KEY).value(), secondResults.get(KEY).value()); } @Test public void testIterator() throws StoreAccessException { store.put(KEY, VALUE); Store.Iterator<Cache.Entry<Long, Store.ValueHolder<Value>>> iterator = store.iterator(); assertThat(iterator.hasNext(), is(true)); while (iterator.hasNext()) { Cache.Entry<Long, Store.ValueHolder<Value>> entry = iterator.next(); compareValues(entry.getValue().value(), VALUE); } } private void compareValues(Value first, Value second) { if (copyForRead || copyForWrite) { assertThat(first, not(sameInstance(second))); } else { assertThat(first, sameInstance(second)); } } private void compareReadValues(Value first, Value second) { if (copyForRead) { assertThat(first, not(sameInstance(second))); } else { assertThat(first, sameInstance(second)); } } public static final class Value { String state; public Value(String state) { this.state = state; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Value value = (Value) o; return state.equals(value.state); } @Override public int hashCode() { return state.hashCode(); } } }