|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using Microsoft.Build.BackEnd;
#nullable disable
namespace Microsoft.Build.Collections
{
/// <summary>
/// A simple string interner designed for IPC.
/// </summary>
/// <remarks>
/// This interner works by providing a way to convert strings to integer IDs. When used as a form of compression,
/// clients will intern their strings and record the set of IDs returned, then transmit those IDs instead of the
/// original strings. The interner itself is also transmitted ahead of time, with the IDs, allowing
/// reconstruction of the original strings. This ensures each string is transmitted exactly once.
/// </remarks>
internal class LookasideStringInterner : ITranslatable
{
/// <summary>
/// Index used for null strings.
/// </summary>
private const int NullStringIndex = -1;
/// <summary>
/// Index used for empty strings.
/// </summary>
private const int EmptyStringIndex = -2;
/// <summary>
/// The map used to intern strings for serialization. This map doesn't exist when the strings
/// are deserialized (it is not needed.)
/// </summary>
private readonly Dictionary<string, int> _stringToIdsMap;
/// <summary>
/// The list of strings by ID.
/// </summary>
private List<string> _strings;
/// <summary>
/// Constructor to be used during serialization.
/// </summary>
public LookasideStringInterner(StringComparer comparer, int defaultCollectionSize)
{
_stringToIdsMap = new Dictionary<string, int>(defaultCollectionSize, comparer);
_strings = new List<string>(defaultCollectionSize);
}
/// <summary>
/// Constructor to be used during deserialization.
/// </summary>
/// <remarks>
/// Intern cannot be used on this interner if it came from serialization, since we do
/// not reconstruct the interning dictionary.
/// </remarks>
public LookasideStringInterner(ITranslator translator)
{
Translate(translator);
}
/// <summary>
/// Interns the specified string.
/// </summary>
/// <param name="str">The string to intern.</param>
/// <returns>The index representing the string.</returns>
public int Intern(string str)
{
if (str == null)
{
return NullStringIndex;
}
else if (str.Length == 0)
{
return EmptyStringIndex;
}
else
{
// If stringToIdsMap is null here, it means we probably tried to intern a string to an interner which came from
// deserialization (and thus doesn't support further interning for efficiency reasons.) No VerifyThrow here
// because this function is called a lot.
if (!_stringToIdsMap.TryGetValue(str, out int index))
{
index = _strings.Count; // This will be the index of the string we are about to add.
_stringToIdsMap.Add(str, index);
_strings.Add(str);
}
return index;
}
}
/// <summary>
/// Retrieves a string corresponding to the provided index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The corresponding string.</returns>
public string GetString(int index)
{
return index switch
{
NullStringIndex => null,
EmptyStringIndex => String.Empty,
_ => _strings[index],
};
}
/// <summary>
/// The translator, for serialization.
/// </summary>
public void Translate(ITranslator translator)
{
translator.Translate(ref _strings);
}
}
}
|