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