/**
     * Common core for the {@link #compute(String, BiFunction, NullaryFunction)} and {@link
     * #computeIfPresent(String, BiFunction, NullaryFunction)} methods.
     *
     * @param key the key of the entry to process
     * @param currentValue the existing value, if any, for {@code key}
     * @param mappingFunction the function that will produce the value. The function will be
     *     supplied with the key and existing value (or null if no entry exists) as parameters. The
     *     function should return the desired new value for the entry or null to remove the entry.
     *     If the method throws an unchecked exception the Store will not be modified (the caller
     *     will receive the exception)
     * @param replaceEqual If the existing value in the store is {@link
     *     java.lang.Object#equals(Object)} to the value returned from the mappingFunction this
     *     function will be invoked. If this function returns {@link java.lang.Boolean#FALSE} then
     *     the existing entry in the store will not be replaced with a new entry and the existing
     *     entry will have its access time updated
     * @return the new value associated with the key or null if none
     */
    private FakeValueHolder computeInternal(
        final String key,
        final FakeValueHolder currentValue,
        final BiFunction<? super String, ? super String, ? extends String> mappingFunction,
        final NullaryFunction<Boolean> replaceEqual) {

      final String remappedValue =
          mappingFunction.apply(key, (currentValue == null ? null : currentValue.value()));
      FakeValueHolder newValue =
          (remappedValue == null ? null : new FakeValueHolder(remappedValue));
      if (newValue == null) {
        /* Remove entry from store */
        this.entries.remove(key);
      } else if (!newValue.equals(currentValue)) {
        /* New, remapped value is different */
        this.entries.put(key, newValue);
      } else {
        /* New, remapped value is the same */
        if (replaceEqual.apply()) {
          /* Replace existing equal value */
          this.entries.put(key, newValue);
        } else {
          /* Update access time of current entry */
          currentValue.lastAccessTime = System.currentTimeMillis();
          newValue = currentValue;
        }
      }
      return newValue;
    }
 @Override
 public ValueHolder<String> get(final String key) throws CacheAccessException {
   this.checkFailingKey(key);
   final FakeValueHolder valueHolder = this.entries.get(key);
   if (valueHolder != null) {
     valueHolder.lastAccessTime = System.currentTimeMillis();
   }
   return valueHolder;
 }
 @Override
 public ValueHolder<String> putIfAbsent(final String key, final String value)
     throws CacheAccessException {
   this.checkFailingKey(key);
   final FakeValueHolder currentValue = this.entries.get(key);
   if (currentValue == null) {
     this.entries.put(key, new FakeValueHolder(value));
     return null;
   }
   currentValue.lastAccessTime = System.currentTimeMillis();
   return currentValue;
 }
 @Override
 public ValueHolder<String> computeIfAbsent(
     final String key, final Function<? super String, ? extends String> mappingFunction)
     throws CacheAccessException {
   this.checkFailingKey(key);
   FakeValueHolder currentValue = this.entries.get(key);
   if (currentValue == null) {
     final String newValue = mappingFunction.apply(key);
     if (newValue != null) {
       final FakeValueHolder newValueHolder = new FakeValueHolder(newValue);
       this.entries.put(key, newValueHolder);
       currentValue = newValueHolder;
     }
   } else {
     currentValue.lastAccessTime = System.currentTimeMillis();
   }
   return currentValue;
 }