/* * 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.config.units.EntryUnit; import org.ehcache.expiry.Expirations; import org.ehcache.core.spi.function.Function; import org.ehcache.impl.internal.concurrent.ConcurrentHashMap; 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.hamcrest.Matchers; import org.junit.Test; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author Ludovic Orban */ public class OnHeapStoreBulkMethodsTest { public static final Copier DEFAULT_COPIER = new IdentityCopier(); @SuppressWarnings("unchecked") protected <K, V> Store.Configuration<K, V> mockStoreConfig() { @SuppressWarnings("rawtypes") Store.Configuration config = mock(Store.Configuration.class); when(config.getExpiry()).thenReturn(Expirations.noExpiration()); when(config.getKeyType()).thenReturn(Number.class); when(config.getValueType()).thenReturn(CharSequence.class); when(config.getResourcePools()).thenReturn(newResourcePoolsBuilder().heap(Long.MAX_VALUE, EntryUnit.ENTRIES).build()); return config; } @SuppressWarnings("unchecked") protected <Number, CharSequence> OnHeapStore<Number, CharSequence> newStore() { Store.Configuration<Number, CharSequence> configuration = mockStoreConfig(); return new OnHeapStore<Number, CharSequence>(configuration, SystemTimeSource.INSTANCE, DEFAULT_COPIER, DEFAULT_COPIER, new NoopSizeOfEngine(), NullStoreEventDispatcher.<Number, CharSequence>nullStoreEventDispatcher()); } @Test @SuppressWarnings("unchecked") public void testBulkComputeFunctionGetsValuesOfEntries() throws Exception { @SuppressWarnings("rawtypes") Store.Configuration config = mock(Store.Configuration.class); when(config.getExpiry()).thenReturn(Expirations.noExpiration()); when(config.getKeyType()).thenReturn(Number.class); when(config.getValueType()).thenReturn(Number.class); when(config.getResourcePools()).thenReturn(newResourcePoolsBuilder().heap(Long.MAX_VALUE, EntryUnit.ENTRIES).build()); Store.Configuration<Number, Number> configuration = config; OnHeapStore<Number, Number> store = new OnHeapStore<Number, Number>(configuration, SystemTimeSource.INSTANCE, DEFAULT_COPIER, DEFAULT_COPIER, new NoopSizeOfEngine(), NullStoreEventDispatcher.<Number, Number>nullStoreEventDispatcher()); store.put(1, 2); store.put(2, 3); store.put(3, 4); Map<Number, Store.ValueHolder<Number>> result = store.bulkCompute(new HashSet<Number>(Arrays.asList(1, 2, 3, 4, 5, 6)), new Function<Iterable<? extends Map.Entry<? extends Number, ? extends Number>>, Iterable<? extends Map.Entry<? extends Number, ? extends Number>>>() { @Override public Iterable<? extends Map.Entry<? extends Number, ? extends Number>> apply(Iterable<? extends Map.Entry<? extends Number, ? extends Number>> entries) { Map<Number, Number> newValues = new HashMap<Number, Number>(); for (Map.Entry<? extends Number, ? extends Number> entry : entries) { final Number currentValue = entry.getValue(); if(currentValue == null) { if(entry.getKey().equals(4)) { newValues.put(entry.getKey(), null); } else { newValues.put(entry.getKey(), 0); } } else { newValues.put(entry.getKey(), currentValue.intValue() * 2); } } return newValues.entrySet(); } }); ConcurrentMap<Number, Number> check = new ConcurrentHashMap<Number, Number>(); check.put(1, 4); check.put(2, 6); check.put(3, 8); check.put(4, 0); check.put(5, 0); check.put(6, 0); assertThat(result.get(1).value(), Matchers.<Number>is(check.get(1))); assertThat(result.get(2).value(), Matchers.<Number>is(check.get(2))); assertThat(result.get(3).value(), Matchers.<Number>is(check.get(3))); assertThat(result.get(4), nullValue()); assertThat(result.get(5).value(), Matchers.<Number>is(check.get(5))); assertThat(result.get(6).value(), Matchers.<Number>is(check.get(6))); for (Number key : check.keySet()) { final Store.ValueHolder<Number> holder = store.get(key); if(holder != null) { check.remove(key, holder.value()); } } assertThat(check.size(), is(1)); assertThat(check.containsKey(4), is(true)); } @Test public void testBulkComputeHappyPath() throws Exception { OnHeapStore<Number, CharSequence> store = newStore(); store.put(1, "one"); Map<Number, Store.ValueHolder<CharSequence>> result = store.bulkCompute(new HashSet<Number>(Arrays.asList(1, 2)), new Function<Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>>() { @Override public Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> apply(Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> entries) { Map<Number, CharSequence> newValues = new HashMap<Number, CharSequence>(); for (Map.Entry<? extends Number, ? extends CharSequence> entry : entries) { if(entry.getKey().intValue() == 1) { newValues.put(entry.getKey(), "un"); } else if (entry.getKey().intValue() == 2) { newValues.put(entry.getKey(), "deux"); } } return newValues.entrySet(); } }); assertThat(result.size(), is(2)); assertThat(result.get(1).value(), Matchers.<CharSequence>equalTo("un")); assertThat(result.get(2).value(), Matchers.<CharSequence>equalTo("deux")); assertThat(store.get(1).value(), Matchers.<CharSequence>equalTo("un")); assertThat(store.get(2).value(), Matchers.<CharSequence>equalTo("deux")); } @Test public void testBulkComputeStoreRemovesValueWhenFunctionReturnsNullMappings() throws Exception { Store.Configuration<Number, CharSequence> configuration = mockStoreConfig(); @SuppressWarnings("unchecked") OnHeapStore<Number, CharSequence> store = new OnHeapStore<Number, CharSequence>(configuration, SystemTimeSource.INSTANCE, DEFAULT_COPIER, DEFAULT_COPIER, new NoopSizeOfEngine(), NullStoreEventDispatcher.<Number, CharSequence>nullStoreEventDispatcher()); store.put(1, "one"); store.put(2, "two"); store.put(3, "three"); Map<Number, Store.ValueHolder<CharSequence>> result = store.bulkCompute(new HashSet<Number>(Arrays.asList(2, 1, 5)), new Function<Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>>() { @Override public Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> apply(Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> entries) { Map<Number, CharSequence> newValues = new HashMap<Number, CharSequence>(); for (Map.Entry<? extends Number, ? extends CharSequence> entry : entries) { newValues.put(entry.getKey(), null); } return newValues.entrySet(); } }); assertThat(result.size(), is(3)); assertThat(store.get(1), is(nullValue())); assertThat(store.get(2), is(nullValue())); assertThat(store.get(3).value(), Matchers.<CharSequence>equalTo("three")); assertThat(store.get(5), is(nullValue())); } @Test public void testBulkComputeRemoveNullValueEntriesFromFunctionReturn() throws Exception { OnHeapStore<Number, CharSequence> store = newStore(); store.put(1, "one"); store.put(2, "two"); store.put(3, "three"); Map<Number, Store.ValueHolder<CharSequence>> result = store.bulkCompute(new HashSet<Number>(Arrays.asList(1, 2, 3)), new Function<Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>>() { @Override public Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> apply(Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> entries) { Map<Number, CharSequence> result = new HashMap<Number, CharSequence>(); for (Map.Entry<? extends Number, ? extends CharSequence> entry : entries) { if (entry.getKey().equals(1)) { result.put(entry.getKey(), null); } else if (entry.getKey().equals(3)) { result.put(entry.getKey(), null); } else { result.put(entry.getKey(), entry.getValue()); } } return result.entrySet(); } }); assertThat(result.size(), is(3)); assertThat(result.get(1), is(nullValue())); assertThat(result.get(2).value(), Matchers.<CharSequence>equalTo("two")); assertThat(result.get(3), is(nullValue())); assertThat(store.get(1),is(nullValue())); assertThat(store.get(2).value(), Matchers.<CharSequence>equalTo("two")); assertThat(store.get(3),is(nullValue())); } @Test public void testBulkComputeIfAbsentFunctionDoesNotGetPresentKeys() throws Exception { OnHeapStore<Number, CharSequence> store = newStore(); store.put(1, "one"); store.put(2, "two"); store.put(3, "three"); Map<Number, Store.ValueHolder<CharSequence>> result = store.bulkComputeIfAbsent(new HashSet<Number>(Arrays.asList(1, 2, 3, 4, 5, 6)), new Function<Iterable<? extends Number>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>>() { @Override public Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> apply(Iterable<? extends Number> keys) { Map<Number, CharSequence> result = new HashMap<Number, CharSequence>(); for (Number key : keys) { if (key.equals(1)) { fail(); } else if (key.equals(2)) { fail(); } else if (key.equals(3)) { fail(); } else { result.put(key, null); } } return result.entrySet(); } }); assertThat(result.size(), is(6)); assertThat(result.get(1).value(), Matchers.<CharSequence>equalTo("one")); assertThat(result.get(2).value(), Matchers.<CharSequence>equalTo("two")); assertThat(result.get(3).value(), Matchers.<CharSequence>equalTo("three")); assertThat(result.get(4), is(nullValue())); assertThat(result.get(5), is(nullValue())); assertThat(result.get(6), is(nullValue())); assertThat(store.get(1).value(), Matchers.<CharSequence>equalTo("one")); assertThat(store.get(2).value(), Matchers.<CharSequence>equalTo("two")); assertThat(store.get(3).value(), Matchers.<CharSequence>equalTo("three")); assertThat(store.get(4), is(nullValue())); assertThat(store.get(5), is(nullValue())); assertThat(store.get(6), is(nullValue())); } @Test public void testBulkComputeIfAbsentDoesNotOverridePresentKeys() throws Exception { OnHeapStore<Number, CharSequence> store = newStore(); store.put(1, "one"); store.put(2, "two"); store.put(3, "three"); Map<Number, Store.ValueHolder<CharSequence>> result = store.bulkComputeIfAbsent(new HashSet<Number>(Arrays.asList(1, 2, 3, 4, 5, 6)), new Function<Iterable<? extends Number>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>>() { @Override public Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> apply(Iterable<? extends Number> numbers) { Map<Number, CharSequence> result = new HashMap<Number, CharSequence>(); for (Number key : numbers) { if(key.equals(4)) { result.put(key, "quatre"); } else if(key.equals(5)) { result.put(key, "cinq"); } else if(key.equals(6)) { result.put(key, "six"); } } return result.entrySet(); } }); assertThat(result.size(), is(6)); assertThat(result.get(1).value(), Matchers.<CharSequence>equalTo("one")); assertThat(result.get(2).value(), Matchers.<CharSequence>equalTo("two")); assertThat(result.get(3).value(), Matchers.<CharSequence>equalTo("three")); assertThat(result.get(4).value(), Matchers.<CharSequence>equalTo("quatre")); assertThat(result.get(5).value(), Matchers.<CharSequence>equalTo("cinq")); assertThat(result.get(6).value(), Matchers.<CharSequence>equalTo("six")); assertThat(store.get(1).value(), Matchers.<CharSequence>equalTo("one")); assertThat(store.get(2).value(), Matchers.<CharSequence>equalTo("two")); assertThat(store.get(3).value(), Matchers.<CharSequence>equalTo("three")); assertThat(store.get(4).value(), Matchers.<CharSequence>equalTo("quatre")); assertThat(store.get(5).value(), Matchers.<CharSequence>equalTo("cinq")); assertThat(store.get(6).value(), Matchers.<CharSequence>equalTo("six")); } @Test public void testBulkComputeIfAbsentDoNothingOnNullValues() throws Exception { OnHeapStore<Number, CharSequence> store = newStore(); store.put(1, "one"); store.put(2, "two"); store.put(3, "three"); Map<Number, Store.ValueHolder<CharSequence>> result = store.bulkComputeIfAbsent(new HashSet<Number>(Arrays.asList(2, 1, 5)), new Function<Iterable<? extends Number>, Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>>>() { @Override public Iterable<? extends Map.Entry<? extends Number, ? extends CharSequence>> apply(Iterable<? extends Number> numbers) { Map<Number, CharSequence> result = new HashMap<Number, CharSequence>(); for (Number key : numbers) { // 5 is a missing key, so it's the only key that is going passed to the function if(key.equals(5)) { result.put(key, null); } } Set<Number> numbersSet = new HashSet<Number>(); for (Number number : numbers) { numbersSet.add(number); } assertThat(numbersSet.size(), is(1)); assertThat(numbersSet.iterator().next(), Matchers.<Number>equalTo(5)); return result.entrySet(); } }); assertThat(result.size(), is(3)); assertThat(result.get(2).value(), Matchers.<CharSequence>equalTo("two")); assertThat(result.get(1).value(), Matchers.<CharSequence>equalTo("one")); assertThat(result.get(5), is(nullValue())); assertThat(store.get(1).value(), Matchers.<CharSequence>equalTo("one")); assertThat(store.get(2).value(), Matchers.<CharSequence>equalTo("two")); assertThat(store.get(3).value(), Matchers.<CharSequence>equalTo("three")); assertThat(store.get(5), is(nullValue())); } }