blob: d7c3435ce5d2a3039d3f62c8de7a27522c0bca24 [file] [log] [blame]
// 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) {
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();
s = StringCanonicalizer.intern(s);
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 synchronized boolean addString(String s) {
int originalSize = size();
getOrCreateIndex(s);
return (size() > originalSize);
}
@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();
}
}