// 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.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(); } }