private boolean eventuallyGetMaxKeyInDB(final FDate key, final boolean force) {
   // not updating highest allowed key, since this already happened during key adjustment
   final FDate newMaxKeyInDB = getAdjustKeyProvider().getHighestAllowedKey();
   if (newMaxKeyInDB != null) {
     if (newMaxKeyInDB.isAfter(maxKeyInDB)) {
       maxKeyInDB = newMaxKeyInDB;
       return true;
     } else {
       return false;
     }
   }
   // fallback to normal procedure if curHighWaterMark is not provided by provider
   if (maxKeyInDB == null || force) {
     final V maxValue = readNewestValueFromDB(maxKey());
     if (maxValue != null) {
       final FDate maxValueKey = extractKey(key, maxValue);
       if (maxKeyInDB == null || maxValueKey.compareTo(maxKeyInDB) <= -1) {
         maxKeyInDB = maxValueKey;
         getValuesMap().put(maxValueKey, maxValue);
         return true;
       }
     }
   }
   return false;
 }
 private V eventuallyGetMinValue(final FDate key, final boolean newMinKey) {
   // if key < minKey; use value for minKey
   if (minKeyInDB != null) {
     final boolean afterMinKey = !newMinKey && key.compareTo(minKey) >= 0;
     if (afterMinKey && key.compareTo(minKeyInDB) <= 0 && containsKey(minKey)) {
       // via readNewestValueTo
       return query().withFuture().getValue(minKey);
     }
     if (key.compareTo(minKeyInDB) <= 0 && containsKey(minKeyInDB)) {
       // via searchInFurtherValues
       return query().withFuture().getValue(minKeyInDB);
     }
   }
   return (V) null;
 }
 private boolean eventuallyGetMinKeyInDB(final FDate key, final boolean force) {
   if (minKeyInDB == null || force) {
     final V minValue = readNewestValueFromDB(minKey());
     if (minValue != null) {
       final FDate minValueKey = extractKey(key, minValue);
       // min key must be kept intact if all values have been loaded from a later key
       if (minKeyInDB == null || minValueKey.compareTo(minKeyInDB) <= -1) {
         minKeyInDB = minValueKey;
         getValuesMap().put(minValueKey, minValue);
         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;
  }
 private boolean updateMinKey(final FDate key) {
   if (minKey == null || key.compareTo(minKey) <= -1) {
     minKey = key;
     return true;
   } else {
     return false;
   }
 }
 private boolean updateMaxKey(final FDate key) {
   if (maxKey == null || key.compareTo(maxKey) >= 1) {
     maxKey = key;
     return true;
   } else {
     return false;
   }
 }
  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 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 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 V loadFromCacheBeforeLoadFurtherValues(
      final FDate key, final boolean newMaxKey, final boolean newMinKey) {
    final V value = eventuallyGetMinValue(key, newMinKey);
    if (value != null) {
      return value;
    }

    // maybe use max value
    if (maxKeyInDB != null && key.compareTo(maxKeyInDB) >= 0 && containsKey(maxKeyInDB)) {
      return query().withFuture().getValue(maxKeyInDB);
    }
    return (V) null;
  }
  /**
   * These checks may only be called after furtherValues were searched and eventuelly the list has
   * been reloaded.
   */
  private V tryLoadFromCacheAfterLoadFurtherValues(
      final FDate key, final boolean newMaxKey, final FDate previousMaxKey) {
    // maybe minKey in db did not change even though the minKey in the cache changed
    // after reloading of furtherValues it is ok to search this again instead of doing another query
    // for the newest value
    if (furtherValuesLoaded) {
      final V value = eventuallyGetMinValue(key, false);
      if (value != null) {
        return value;
      }
    }

    // with maxKey
    if (newMaxKey
        && previousMaxKey != null
        && containsKey(previousMaxKey)
        && key.isAfterOrEqualTo(maxKeyInDB)) {
      // use the last maxKey
      // because this one is behind it and not a new one
      // thus working if the db does not have further values
      return query().withFuture().getValue(previousMaxKey);
    }
    return (V) null;
  }
 /**
  * when this does not match, then getLatestValue will be used automatically anyway to go further
  * back in time
  */
 private FDate determineEaliestStartOfLoadFurtherValues(final FDate key) {
   // 1 day is fine for most cases
   return key.addMilliseconds(-cacheMissCounter.getOptimalReadBackStepMillis());
 }
  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;
  }