Collapse inheritance hierarchy of `PersistentStringIndexer`.
`AbstractIndexer` and `CanonicalStringIndexer` only have a single subclass. Merge them both into `PersistentStringIndexer`.
PiperOrigin-RevId: 625452496
Change-Id: I12eaab56ffb36b5e5f3cb1c822ee737249d16eb8
diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java b/src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java
index 5cefc15..ba21a14 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java
@@ -210,7 +210,7 @@
PersistentStringIndexer indexer;
try {
- indexer = PersistentStringIndexer.newPersistentStringIndexer(indexFile, clock);
+ indexer = PersistentStringIndexer.create(indexFile, clock);
} catch (IOException e) {
return logAndThrowOrRecurse(
cacheRoot,
diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/PersistentStringIndexer.java b/src/main/java/com/google/devtools/build/lib/actions/cache/PersistentStringIndexer.java
index e12826c..18a401e 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/cache/PersistentStringIndexer.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/cache/PersistentStringIndexer.java
@@ -13,10 +13,13 @@
// limitations under the License.
package com.google.devtools.build.lib.actions.cache;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.devtools.build.lib.clock.Clock;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ConditionallyThreadSafe;
-import com.google.devtools.build.lib.util.CanonicalStringIndexer;
import com.google.devtools.build.lib.util.PersistentMap;
+import com.google.devtools.build.lib.util.StringCanonicalizer;
+import com.google.devtools.build.lib.util.StringIndexer;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import java.io.DataInputStream;
@@ -24,21 +27,114 @@
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nullable;
/**
- * Persistent version of the CanonicalStringIndexer.
+ * Persistent implementation of {@link StringIndexer}.
*
- * <p>This class is backed by a PersistentMap that holds one direction of the canonicalization
- * mapping. The other direction is handled purely in memory and reconstituted at load-time.
+ * <p>This class is backed by a {@link PersistentMap} that holds one direction of the
+ * canonicalization mapping. The other direction is handled purely in memory and reconstituted at
+ * load-time.
*
* <p>Thread-safety is ensured by locking on all mutating operations from the superclass. Read-only
* operations are not locked, but rather backed by ConcurrentMaps.
*/
-@ConditionallyThreadSafe // condition: each instance must instantiated with
-// different dataFile.
-final class PersistentStringIndexer extends CanonicalStringIndexer {
+@ConditionallyThreadSafe // Each instance must be instantiated with a different dataPath.
+final class PersistentStringIndexer implements StringIndexer {
+
+ private static final int NOT_FOUND = -1;
+ private static final int INITIAL_ENTRIES = 10000;
+
+ /** Instantiates and loads instance of the persistent string indexer. */
+ static PersistentStringIndexer create(Path dataPath, Clock clock) throws IOException {
+ PersistentIndexMap persistentIndexMap =
+ new PersistentIndexMap(
+ dataPath, FileSystemUtils.replaceExtension(dataPath, ".journal"), clock);
+ Map<Integer, String> reverseMapping = new ConcurrentHashMap<>(INITIAL_ENTRIES);
+ for (Map.Entry<String, Integer> entry : persistentIndexMap.entrySet()) {
+ if (reverseMapping.put(entry.getValue(), entry.getKey()) != null) {
+ throw new IOException("Corrupted filename index has duplicate entry: " + entry.getKey());
+ }
+ }
+ return new PersistentStringIndexer(persistentIndexMap, reverseMapping);
+ }
+
+ // This is similar to (Synchronized) BiMap.
+ // These maps *must* be weakly threadsafe to ensure thread safety for string indexer as a whole.
+ // Specifically, mutating operations are serialized, but read-only operations may be executed
+ // concurrently with mutators.
+ private final PersistentIndexMap stringToInt;
+ private final Map<Integer, String> intToString;
+
+ private PersistentStringIndexer(
+ PersistentIndexMap stringToInt, Map<Integer, String> intToString) {
+ this.stringToInt = stringToInt;
+ this.intToString = intToString;
+ }
+
+ @Override
+ public synchronized void clear() {
+ stringToInt.clear();
+ intToString.clear();
+ }
+
+ @Override
+ public int size() {
+ return intToString.size();
+ }
+
+ @Override
+ public int getOrCreateIndex(String s) {
+ Integer i = stringToInt.get(s);
+ if (i == null) {
+ s = StringCanonicalizer.intern(s);
+ synchronized (this) {
+ // First, make sure another thread hasn't just added the entry:
+ i = stringToInt.get(s);
+ if (i != null) {
+ return i;
+ }
+
+ int ind = intToString.size();
+ stringToInt.put(s, ind);
+ intToString.put(ind, s);
+ return ind;
+ }
+ } else {
+ return i;
+ }
+ }
+
+ @Override
+ public int getIndex(String s) {
+ return stringToInt.getOrDefault(s, NOT_FOUND);
+ }
+
+ @Override
+ @Nullable
+ public String getStringForIndex(int i) {
+ return intToString.get(i);
+ }
+
+ /** Saves index data to the file. */
+ synchronized long save() throws IOException {
+ return stringToInt.save();
+ }
+
+ /** Flushes the journal. */
+ synchronized void flush() {
+ stringToInt.flush();
+ }
+
+ @Override
+ public synchronized String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("size = ").append(size()).append("\n");
+ for (Map.Entry<String, Integer> entry : stringToInt.entrySet()) {
+ builder.append(entry.getKey()).append(" <==> ").append(entry.getValue()).append("\n");
+ }
+ return builder.toString();
+ }
/**
* Persistent metadata map. Used as a backing map to provide a persistent implementation of the
@@ -51,12 +147,8 @@
private final Clock clock;
private long nextUpdate;
- public PersistentIndexMap(Path mapFile, Path journalFile, Clock clock) throws IOException {
- super(
- VERSION,
- PersistentStringIndexer.<String, Integer>newConcurrentMap(INITIAL_ENTRIES),
- mapFile,
- journalFile);
+ PersistentIndexMap(Path mapFile, Path journalFile, Clock clock) throws IOException {
+ super(VERSION, new ConcurrentHashMap<>(INITIAL_ENTRIES), mapFile, journalFile);
this.clock = clock;
nextUpdate = clock.nanoTime();
load(/* failFast= */ true);
@@ -73,13 +165,12 @@
}
@Override
- @Nullable
public Integer remove(Object object) {
throw new UnsupportedOperationException();
}
- public void flush() {
- super.forceFlush();
+ void flush() {
+ forceFlush();
}
@Override
@@ -90,7 +181,7 @@
}
byte[] content = new byte[length];
in.readFully(content);
- return bytes2string(content);
+ return new String(content, UTF_8);
}
@Override
@@ -100,7 +191,7 @@
@Override
protected void writeKey(String key, DataOutputStream out) throws IOException {
- byte[] content = string2bytes(key);
+ byte[] content = key.getBytes(UTF_8);
out.writeInt(content.length);
out.write(content);
}
@@ -110,48 +201,4 @@
out.writeInt(value);
}
}
-
- private final PersistentIndexMap persistentIndexMap;
- private static final int INITIAL_ENTRIES = 10000;
-
- /**
- * Instantiates and loads instance of the persistent string indexer.
- */
- static PersistentStringIndexer newPersistentStringIndexer(Path dataPath,
- Clock clock) throws IOException {
- PersistentIndexMap persistentIndexMap = new PersistentIndexMap(dataPath,
- FileSystemUtils.replaceExtension(dataPath, ".journal"), clock);
- Map<Integer, String> reverseMapping = newConcurrentMap(INITIAL_ENTRIES);
- for (Map.Entry<String, Integer> entry : persistentIndexMap.entrySet()) {
- if (reverseMapping.put(entry.getValue(), entry.getKey()) != null) {
- throw new IOException("Corrupted filename index has duplicate entry: " + entry.getKey());
- }
- }
- return new PersistentStringIndexer(persistentIndexMap, reverseMapping);
- }
-
- private PersistentStringIndexer(PersistentIndexMap stringToInt,
- Map<Integer, String> intToString) {
- super(stringToInt, intToString);
- this.persistentIndexMap = stringToInt;
- }
-
- /**
- * Saves index data to the file.
- */
- synchronized long save() throws IOException {
- return persistentIndexMap.save();
- }
-
- /**
- * Flushes the journal.
- */
- synchronized void flush() {
- persistentIndexMap.flush();
- }
-
- private static <K, V> ConcurrentMap<K, V> newConcurrentMap(int expectedCapacity) {
- return new ConcurrentHashMap<>(expectedCapacity);
- }
-
}
diff --git a/src/main/java/com/google/devtools/build/lib/util/AbstractIndexer.java b/src/main/java/com/google/devtools/build/lib/util/AbstractIndexer.java
deleted file mode 100644
index 110b2e1..0000000
--- a/src/main/java/com/google/devtools/build/lib/util/AbstractIndexer.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.build.lib.util;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-/**
- * Abstract class for string indexers.
- */
-abstract class AbstractIndexer implements StringIndexer {
-
- /**
- * Conversion from String to byte[].
- */
- protected static byte[] string2bytes(String string) {
- return string.getBytes(UTF_8);
- }
-
- /**
- * Conversion from byte[] to String.
- */
- protected static String bytes2string(byte[] bytes) {
- return new String(bytes, UTF_8);
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/util/BUILD b/src/main/java/com/google/devtools/build/lib/util/BUILD
index f21f547..e39f8a7 100644
--- a/src/main/java/com/google/devtools/build/lib/util/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/util/BUILD
@@ -206,10 +206,8 @@
java_library(
name = "util",
srcs = [
- "AbstractIndexer.java",
"AnsiStrippingOutputStream.java",
"CPU.java",
- "CanonicalStringIndexer.java",
"ClassName.java",
"DependencySet.java",
"Either.java",
@@ -243,7 +241,6 @@
deps = [
":os",
":shell_escaper",
- ":string",
"//src/main/java/com/google/devtools/build/lib/bugreport",
"//src/main/java/com/google/devtools/build/lib/concurrent",
"//src/main/java/com/google/devtools/build/lib/vfs",
diff --git a/src/main/java/com/google/devtools/build/lib/util/CanonicalStringIndexer.java b/src/main/java/com/google/devtools/build/lib/util/CanonicalStringIndexer.java
deleted file mode 100644
index 43e0ed9..0000000
--- a/src/main/java/com/google/devtools/build/lib/util/CanonicalStringIndexer.java
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package com.google.devtools.build.lib.util;
-
-import com.google.common.base.Preconditions;
-import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
-import java.util.Map;
-
-/**
- * A string indexer backed by a map and reverse index lookup.
- * Every unique string is stored in memory exactly once.
- */
-@ThreadSafe
-public class CanonicalStringIndexer extends AbstractIndexer {
-
- private static final int NOT_FOUND = -1;
-
- // This is similar to (Synchronized) BiMap.
- // These maps *must* be weakly threadsafe to ensure thread safety for string
- // indexer as a whole. Specifically, mutating operations are serialized, but
- // read-only operations may be executed concurrently with mutators.
- private final Map<String, Integer> stringToInt;
- private final Map<Integer, String> intToString;
-
- /*
- * Creates an indexer instance from two backing maps. These maps may be
- * pre-initialized with data, but *must*:
- * a. Support read-only operations done concurrently with mutations.
- * Note that mutations will be serialized.
- * b. Be reverse mappings of each other, if pre-initialized.
- */
- public CanonicalStringIndexer(Map<String, Integer> stringToInt,
- Map<Integer, String> intToString) {
- Preconditions.checkArgument(stringToInt.size() == intToString.size());
- this.stringToInt = stringToInt;
- this.intToString = intToString;
- }
-
-
- @Override
- public synchronized void clear() {
- stringToInt.clear();
- intToString.clear();
- }
-
- @Override
- public int size() {
- return intToString.size();
- }
-
- @Override
- public int getOrCreateIndex(String s) {
- Integer i = stringToInt.get(s);
- if (i == null) {
- s = StringCanonicalizer.intern(s);
- synchronized (this) {
- // First, make sure another thread hasn't just added the entry:
- i = stringToInt.get(s);
- if (i != null) {
- return i;
- }
-
- int ind = intToString.size();
- stringToInt.put(s, ind);
- intToString.put(ind, s);
- return ind;
- }
- } else {
- return i;
- }
- }
-
- @Override
- public int getIndex(String s) {
- Integer i = stringToInt.get(s);
- return (i == null) ? NOT_FOUND : i;
- }
-
- @Override
- public String getStringForIndex(int i) {
- return intToString.get(i);
- }
-
- @Override
- public synchronized String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("size = ").append(size()).append("\n");
- for (Map.Entry<String, Integer> entry : stringToInt.entrySet()) {
- builder.append(entry.getKey()).append(" <==> ").append(entry.getValue()).append("\n");
- }
- return builder.toString();
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/util/StringIndexer.java b/src/main/java/com/google/devtools/build/lib/util/StringIndexer.java
index 9bbd8d6..28a4f39 100644
--- a/src/main/java/com/google/devtools/build/lib/util/StringIndexer.java
+++ b/src/main/java/com/google/devtools/build/lib/util/StringIndexer.java
@@ -13,40 +13,32 @@
// limitations under the License.
package com.google.devtools.build.lib.util;
-/**
- * An object that provides bidirectional String <-> unique integer mapping.
- */
+import javax.annotation.Nullable;
+
+/** Provides bidirectional string ⇔ unique integer mapping. */
public interface StringIndexer {
- /**
- * Removes all mappings.
- */
- public void clear();
+ /** Removes all mappings. */
+ void clear();
+
+ /** Returns the number of strings in the index. */
+ int size();
/**
- * @return some measure of the size of the index.
+ * Creates new mapping for the given string if necessary and returns string index. Also, as a side
+ * effect, additional mappings may be created for various prefixes of the given string.
*/
- public int size();
+ int getOrCreateIndex(String s);
/**
- * Creates new mapping for the given string if necessary and returns
- * string index. Also, as a side effect, zero or more additional mappings
- * may be created for various prefixes of the given string.
- *
- * @return a unique index.
+ * Returns the unique index for the given string if one was created via {@link #getOrCreateIndex},
+ * or else {@code -1}.
*/
- public int getOrCreateIndex(String s);
+ int getIndex(String s);
/**
- * @return a unique index for the given string or -1 if string
- * was not added.
+ * Returns the string associated with the given index or {@code null} if it is not in the index.
*/
- public int getIndex(String s);
-
- /**
- * @return string associated with the given index or null if
- * mapping does not exist.
- */
- public String getStringForIndex(int i);
-
+ @Nullable
+ String getStringForIndex(int i);
}
diff --git a/src/test/java/com/google/devtools/build/lib/actions/cache/PersistentStringIndexerTest.java b/src/test/java/com/google/devtools/build/lib/actions/cache/PersistentStringIndexerTest.java
index f307b5c..6876590 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/cache/PersistentStringIndexerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/cache/PersistentStringIndexerTest.java
@@ -71,7 +71,7 @@
public final void createIndexer() throws Exception {
dataPath = scratch.resolve("/cache/test.dat");
journalPath = scratch.resolve("/cache/test.journal");
- psi = PersistentStringIndexer.newPersistentStringIndexer(dataPath, clock);
+ psi = PersistentStringIndexer.create(dataPath, clock);
}
private void assertSize(int expected) {
@@ -160,7 +160,7 @@
assertThat(journalPath.exists()).isFalse();
// Now restore data from file and verify it.
- psi = PersistentStringIndexer.newPersistentStringIndexer(dataPath, clock);
+ psi = PersistentStringIndexer.create(dataPath, clock);
assertThat(journalPath.exists()).isFalse();
clock.advance(4);
assertSize(10);
@@ -182,7 +182,7 @@
assertThat(journalPath.exists()).isTrue();
// Now restore data from file and verify it. All data should be restored from journal;
- psi = PersistentStringIndexer.newPersistentStringIndexer(dataPath, clock);
+ psi = PersistentStringIndexer.create(dataPath, clock);
assertThat(dataPath.exists()).isTrue();
assertThat(journalPath.exists()).isFalse();
clock.advance(4);
@@ -208,7 +208,7 @@
assertThat(journalPath.exists()).isTrue();
// Now restore data from file and verify it. All data should be restored from journal;
- psi = PersistentStringIndexer.newPersistentStringIndexer(dataPath, clock);
+ psi = PersistentStringIndexer.create(dataPath, clock);
assertThat(dataPath.exists()).isTrue();
assertThat(journalPath.exists()).isFalse();
assertThat(dataPath.getFileSize())
@@ -240,7 +240,7 @@
assertThat(journalPath.exists()).isTrue();
// Now restore data from file and verify it. All data should be restored from journal;
- psi = PersistentStringIndexer.newPersistentStringIndexer(dataPath, clock);
+ psi = PersistentStringIndexer.create(dataPath, clock);
assertThat(dataPath.exists()).isTrue();
assertThat(journalPath.exists()).isFalse();
assertThat(dataPath.getFileSize())
@@ -257,8 +257,7 @@
FileSystemUtils.writeContentAsLatin1(journalPath, "bogus content");
IOException e =
assertThrows(
- IOException.class,
- () -> psi = PersistentStringIndexer.newPersistentStringIndexer(dataPath, clock));
+ IOException.class, () -> psi = PersistentStringIndexer.create(dataPath, clock));
assertThat(e).hasMessageThat().contains("too short: Only 13 bytes");
journalPath.delete();
@@ -274,7 +273,7 @@
byte[] journalContent = FileSystemUtils.readContent(journalPath);
// Now restore data from file and verify it. All data should be restored from journal;
- psi = PersistentStringIndexer.newPersistentStringIndexer(dataPath, clock);
+ psi = PersistentStringIndexer.create(dataPath, clock);
assertThat(dataPath.exists()).isTrue();
assertThat(journalPath.exists()).isFalse();
@@ -282,9 +281,7 @@
assertThat(dataPath.delete()).isTrue();
FileSystemUtils.writeContent(journalPath,
Arrays.copyOf(journalContent, journalContent.length - 1));
- assertThrows(
- EOFException.class,
- () -> psi = PersistentStringIndexer.newPersistentStringIndexer(dataPath, clock));
+ assertThrows(EOFException.class, () -> psi = PersistentStringIndexer.create(dataPath, clock));
// Corrupt the journal with a negative size value.
byte[] journalCopy = journalContent.clone();
@@ -293,16 +290,13 @@
FileSystemUtils.writeContent(journalPath, journalCopy);
e =
assertThrows(
- IOException.class,
- () -> psi = PersistentStringIndexer.newPersistentStringIndexer(dataPath, clock));
+ IOException.class, () -> psi = PersistentStringIndexer.create(dataPath, clock));
assertThat(e).hasMessageThat().contains("corrupt key length");
// Now put back corrupted journal. We should get an error.
journalContent[journalContent.length - 13] = 100;
FileSystemUtils.writeContent(journalPath, journalContent);
- assertThrows(
- IOException.class,
- () -> psi = PersistentStringIndexer.newPersistentStringIndexer(dataPath, clock));
+ assertThrows(IOException.class, () -> psi = PersistentStringIndexer.create(dataPath, clock));
}
@Test
@@ -337,8 +331,7 @@
IOException e =
assertThrows(
- IOException.class,
- () -> psi = PersistentStringIndexer.newPersistentStringIndexer(dataPath, clock));
+ IOException.class, () -> psi = PersistentStringIndexer.create(dataPath, clock));
assertThat(e).hasMessageThat().contains("Corrupted filename index has duplicate entry");
}