@Test
  public void testBulkComputeStoreRemovesValueWhenFunctionReturnsNullMappings() throws Exception {
    Store.Configuration<Number, CharSequence> configuration = mockStoreConfig();

    OnHeapStore<Number, CharSequence> store =
        new OnHeapStore<Number, CharSequence>(configuration, SystemTimeSource.INSTANCE, false);
    store.put(1, "one");
    store.put(2, "two");
    store.put(3, "three");

    Map<Number, Store.ValueHolder<CharSequence>> result =
        store.bulkCompute(
            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 testBulkComputeIfAbsentFunctionDoesNotGetPresentKeys() throws Exception {
    Store.Configuration<Number, CharSequence> configuration = mockStoreConfig();

    OnHeapStore<Number, CharSequence> store =
        new OnHeapStore<Number, CharSequence>(configuration, SystemTimeSource.INSTANCE, false);
    store.put(1, "one");
    store.put(2, "two");
    store.put(3, "three");

    Map<Number, Store.ValueHolder<CharSequence>> result =
        store.bulkComputeIfAbsent(
            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 testComputeIfPresentWithReplaceEqual() throws CacheAccessException {
   store.put(KEY, VALUE);
   final Store.ValueHolder<Value> computeValue =
       store.computeIfPresent(
           KEY,
           new BiFunction<Long, Value, Value>() {
             @Override
             public Value apply(Long aLong, Value value) {
               return value;
             }
           },
           REPLACE_EQUAL);
   store.computeIfPresent(
       KEY,
       new BiFunction<Long, Value, Value>() {
         @Override
         public Value apply(Long aLong, Value value) {
           compareReadValues(computeValue.value(), value);
           return value;
         }
       },
       REPLACE_EQUAL);
   compareValues(VALUE, computeValue.value());
 }
  @Test
  public void testBulkComputeIfAbsentDoNothingOnNullValues() throws Exception {
    Store.Configuration<Number, CharSequence> configuration = mockStoreConfig();

    OnHeapStore<Number, CharSequence> store =
        new OnHeapStore<Number, CharSequence>(configuration, SystemTimeSource.INSTANCE, false);
    store.put(1, "one");
    store.put(2, "two");
    store.put(3, "three");

    Map<Number, Store.ValueHolder<CharSequence>> result =
        store.bulkComputeIfAbsent(
            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()));
  }
 @Test
 public void testIterator() throws CacheAccessException {
   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);
   }
 }
  @Test
  public void testPutAndGet() throws CacheAccessException {
    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 testPutNotSerializableKey() throws Exception {
   OnHeapStore<Serializable, Serializable> store = newStore();
   try {
     store.put(
         new ArrayList<Object>() {
           {
             add(new Object());
           }
         },
         "value");
     fail();
   } catch (CacheAccessException cae) {
     assertThat(cae.getCause(), instanceOf(SerializerException.class));
   }
 }
  @Test
  public void testKeyUniqueObject() throws Exception {
    OnHeapStore<Serializable, Serializable> store = newStore();

    List<String> key = new ArrayList<String>();
    key.add("key");
    String value = "value";

    store.put((Serializable) key, value);

    // mutate the key -- should not affect cache
    key.clear();

    Serializable storeKey = store.iterator().next().getKey();
    if (storeKey == key || !storeKey.equals(Collections.singletonList("key"))) {
      throw new AssertionError();
    }
  }
  @Test
  public void testValueUniqueObject() throws Exception {
    OnHeapStore<Serializable, Serializable> store = newStore();

    String key = "key";
    List<String> value = new ArrayList<String>();
    value.add("value");

    store.put(key, (Serializable) value);

    // mutate the value -- should not affect cache
    value.clear();

    ValueHolder<Serializable> valueHolder = store.get(key);
    if (valueHolder.value() == value
        || !valueHolder.value().equals(Collections.singletonList("value"))) {
      throw new AssertionError();
    }
  }
  @Test
  public void testBulkComputeHappyPath() throws Exception {
    Store.Configuration<Number, CharSequence> configuration = mockStoreConfig();

    OnHeapStore<Number, CharSequence> store =
        new OnHeapStore<Number, CharSequence>(configuration, SystemTimeSource.INSTANCE, false);
    store.put(1, "one");

    Map<Number, Store.ValueHolder<CharSequence>> result =
        store.bulkCompute(
            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 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);
    Store.Configuration<Number, Number> configuration = config;

    OnHeapStore<Number, Number> store =
        new OnHeapStore<Number, Number>(configuration, SystemTimeSource.INSTANCE, false);
    store.put(1, 2);
    store.put(2, 3);
    store.put(3, 4);

    Map<Number, Store.ValueHolder<Number>> result =
        store.bulkCompute(
            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));
  }