/*
- * Copyright 2018-present Open Networking Foundation
+ * Copyright 2018-2022 Open Networking Foundation and others. All rights reserved.
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*/
package io.atomix.storage.journal.index;
-import java.util.Map;
+import com.google.common.base.MoreObjects;
import java.util.TreeMap;
+import org.eclipse.jdt.annotation.Nullable;
/**
- * Sparse index.
+ * A {@link JournalIndex} maintaining target density.
*/
-public class SparseJournalIndex implements JournalIndex {
- private static final int MIN_DENSITY = 1000;
- private final int density;
- private final TreeMap<Long, Integer> positions = new TreeMap<>();
-
- public SparseJournalIndex(double density) {
- this.density = (int) Math.ceil(MIN_DENSITY / (density * MIN_DENSITY));
- }
-
- @Override
- public void index(long index, int position) {
- if (index % density == 0) {
- positions.put(index, position);
+public final class SparseJournalIndex implements JournalIndex {
+ private static final int MIN_DENSITY = 1000;
+
+ private final TreeMap<Long, Integer> positions = new TreeMap<>();
+ private final int density;
+
+ // Last known position. May not be accurate immediately after a truncate() or construction
+ private @Nullable Position last;
+
+ public SparseJournalIndex() {
+ density = MIN_DENSITY;
+ }
+
+ public SparseJournalIndex(final double density) {
+ this.density = (int) Math.ceil(MIN_DENSITY / (density * MIN_DENSITY));
+ }
+
+ @Override
+ public Position index(final long index, final int position) {
+ final var newLast = new Position(index, position);
+ last = newLast;
+ if (index % density == 0) {
+ positions.put(index, position);
+ }
+ return newLast;
+ }
+
+ @Override
+ public Position last() {
+ return last;
+ }
+
+ @Override
+ public Position lookup(final long index) {
+ return Position.ofNullable(positions.floorEntry(index));
+ }
+
+ @Override
+ public Position truncate(final long index) {
+ // Clear all indexes unto and including index, saving the first removed entry
+ final var tailMap = positions.tailMap(index, true);
+ final var firstRemoved = tailMap.firstEntry();
+ tailMap.clear();
+
+ // Update last position to the last entry, but make sure to return a pointer to index if that is what we have
+ // indexed.
+ final var newLast = Position.ofNullable(positions.lastEntry());
+ last = newLast;
+ return firstRemoved != null && firstRemoved.getKey() == index ? new Position(firstRemoved) : newLast;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("positions", positions).toString();
}
- }
-
- @Override
- public Position lookup(long index) {
- Map.Entry<Long, Integer> entry = positions.floorEntry(index);
- return entry != null ? new Position(entry.getKey(), entry.getValue()) : null;
- }
-
- @Override
- public void truncate(long index) {
- positions.tailMap(index, false).clear();
- }
}