private void assertFurtherValuesSorting(final FDate key) {
    final FDate firstKey = extractKey(key, furtherValues.getHead());
    if (firstKey.compareTo(key) <= -1) {
      /*
       * readAllValuesAscendingFrom loads all data, thus we set the min key very deep so that later queries are
       * skipped if they are before minKey
       */
      minKey = minKey();
    }
    if (minKeyInDB == null || firstKey.compareTo(minKey) <= -1) {
      minKeyInDB = firstKey;
    }
    minKeyInDBFromLoadFurtherValues = FDates.min(minKeyInDBFromLoadFurtherValues, firstKey);
    final FDate lastKey = extractKey(key, furtherValues.getTail());
    if (maxKeyInDB == null || lastKey.compareTo(maxKeyInDB) <= -1) {
      maxKeyInDB = FDates.max(maxKeyInDB, lastKey);
    }
    maxKeyInDBFromLoadFurtherValues = FDates.max(maxKeyInDBFromLoadFurtherValues, lastKey);

    if (furtherValues.size() > 1) {
      Assertions.checkState(
          firstKey.compareTo(lastKey) <= 0,
          "Not ascending sorted! At firstKey [%s] and lastKey [%s]",
          firstKey,
          lastKey);
    }
  }
  private boolean shouldLoadFurtherValues(final FDate key, final boolean newMinKey) {
    if (furtherValues.isEmpty()) {
      final V tail = lastValuesFromFurtherValues.getTail();
      if (tail == null) {
        return true;
      }
      final V head = lastValuesFromFurtherValues.getHead();
      final FDate tailKey = extractKey(key, tail);
      final FDate headKey = extractKey(key, head);
      final boolean isEndReachedAnyway =
          tailKey.equals(maxKeyInDB)
              && key.isBeforeOrEqualTo(maxKeyInDB)
              && headKey.isBeforeOrEqualTo(key);
      return !isEndReachedAnyway;
    }
    final boolean keyIsBeforeMinKeyFromLoadFurtherValues =
        newMinKey && key.isBefore(minKeyInDBFromLoadFurtherValues);
    if (keyIsBeforeMinKeyFromLoadFurtherValues) {
      return true;
    }
    final boolean newMinKeyFromDBMayFindNewValues =
        isMinKeyInDBFromLoadFurtherValues() && key.compareTo(minKeyInDB) <= -1 && newMinKey;
    if (newMinKeyFromDBMayFindNewValues) {
      return true;
    }

    return false;
  }
  private boolean eventuallyLoadFurtherValues(
      final String source,
      final FDate key,
      final FDate adjustedKey,
      final boolean newMinKey,
      final boolean forced) {
    if (forced || shouldLoadFurtherValues(key, newMinKey)) {
      final FDate keyForReadAllValues;
      if (newMinKey
          && minKeyInDBFromLoadFurtherValues != null
          && key.isBefore(minKeyInDBFromLoadFurtherValues)) {
        // performance optimization for first load
        keyForReadAllValues = FDates.min(minKeyInDB, FDates.max(minKeyInDB, adjustedKey));
      } else {
        keyForReadAllValues = FDates.max(minKeyInDB, adjustedKey);
      }
      furtherValues.clear();
      lastValuesFromFurtherValues.clear();
      furtherValues.addAll(readAllValuesAscendingFrom(keyForReadAllValues));

      if (!furtherValues.isEmpty()) {
        assertFurtherValuesSorting(key);
      }
      return true;
    }
    return false;
  }
 @Override
 public synchronized void clear() {
   super.clear();
   // remove flags so that the limit check gets skipped if get has not been called yet and this
   // method might be called again
   maxKeyInDB = null;
   minKeyInDB = null;
   // a clear forces the list to be completely reloaded next time get is called
   furtherValues.clear();
   lastValuesFromFurtherValues.clear();
 }
  private V readNewestValueFromDB(final FDate key) {
    // we give up and use the newest value from db
    V value = readLatestValueFor(key);

    // try to use first value of furthervalues
    if (value == null && furtherValuesLoaded && !furtherValues.isEmpty()) {
      value = furtherValues.getHead();
    }

    if (value != null) {
      // we remember the db key of the value so that it can be found again later
      // to use the parameter key would make the result incorrect
      final FDate valueKey = extractKey(key, value);
      getValuesMap().put(valueKey, value);
      return value;
    } else {
      return (V) null;
    }
  }
  private boolean isPotentiallyAlreadyEvicted(final FDate key, final V value) {
    final boolean isEvictedBeforeCurrentFurtherValues =
        (value == null || extractKey(key, value).isAfter(key))
            && (key.isAfter(minKeyInDB) || key.isAfter(minKeyInDBFromLoadFurtherValues));
    if (isEvictedBeforeCurrentFurtherValues) {
      return true;
    }
    final boolean mightBeEvictedAfterFurtherValues = value != null && furtherValues.isEmpty();
    if (mightBeEvictedAfterFurtherValues) {
      final FDate valueKey = extractKey(key, value);
      final boolean isEvictedAfterCurrentFurtherValues =
          valueKey.isBefore(key) && valueKey.isBeforeOrEqualTo(maxKeyInDB);
      if (isEvictedAfterCurrentFurtherValues) {
        return true;
      }
    }

    return false;
  }
 private void pushLastValueFromFurtherValues() {
   while (lastValuesFromFurtherValues.size() >= MAX_LAST_VALUES_FROM_LOAD_FURTHER_VALUES) {
     lastValuesFromFurtherValues.next();
   }
   lastValuesFromFurtherValues.add(furtherValues.next());
 }
  private V searchInFurtherValues(final FDate key) {
    // Take the first matching value from the sorted list
    // Search for the newest value
    V prevValue = (V) null;
    FDate prevKey = null;
    if (!lastValuesFromFurtherValues.isEmpty()) {
      // though maybe use the last one for smaller increments than the data itself is loaded
      for (final V lastValueFromFurtherValues : lastValuesFromFurtherValues) {
        final FDate keyLastValueFromFurtherValues = extractKey(key, lastValueFromFurtherValues);
        if (keyLastValueFromFurtherValues.isBeforeOrEqualTo(key)) {
          prevValue = lastValueFromFurtherValues;
          prevKey = keyLastValueFromFurtherValues;
        } else {
          // only go to further values if it might be possible that those are useable
          return prevValue;
        }
      }
    }

    final FDate earliestStartOfLoadFurtherValues = determineEaliestStartOfLoadFurtherValues(key);
    while (furtherValues.size() > 0) {
      final V newValue = furtherValues.getHead();
      final FDate newValueKey = extractKey(key, newValue);
      final int compare = key.compareTo(newValueKey);
      if (compare < 0) {
        // key < newValueKey
        // run over the key we wanted
        break;
      } else if (compare == 0) {
        // key == newValueKey
        // This is the value we searched for! It will later be added with the db key to the cache.
        pushLastValueFromFurtherValues();
        return newValue;
      } else {
        // key > newValueKey
        // put this value into the cache; gaps do not get filled here, so that the max size of the
        // cache does not get reached prematurely
        put(newValueKey, newValue, prevKey, prevValue);
        pushLastValueFromFurtherValues();
        // continue with the next one
        prevValue = newValue;
        prevKey = newValueKey;

        if (furtherValues.isEmpty()
            && newValueKey.isBefore(maxKeyInDB)
            && key.isBefore(maxKeyInDB)
            && maxKeyInDBFromLoadFurtherValues.isBefore(maxKeyInDB)) {
          final FDate timeForLoadFurtherValues =
              FDates.max(newValueKey, earliestStartOfLoadFurtherValues);
          Assertions.checkState(
              eventuallyLoadFurtherValues(
                  "searchInFurtherValues", newValueKey, timeForLoadFurtherValues, false, true));
          if (!furtherValues.isEmpty()) {
            pushLastValueFromFurtherValues();
            if (!timeForLoadFurtherValues.equals(newValue)) {
              // do not distort prev/next lookup when using earlisetStartOfLoadFurtherValues, thus
              // reset those
              prevValue = null;
              prevKey = null;
            }
          }
        }
      }
    }
    return prevValue;
  }