File: InterningReadTranslator.cs
Web Access
Project: ..\..\..\src\Framework\Microsoft.Build.Framework.csproj (Microsoft.Build.Framework)
// 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;
 
namespace Microsoft.Build.BackEnd
{
    /// <summary>
    /// Reads strings form a translator which contains interned packets.
    /// </summary>
    /// <remarks>
    /// This maintains a reusable lookup table to deserialize packets interned by <see cref="InterningWriteTranslator"/>.
    /// On Translate, the intern header (aka the array of strings indexed by ID) is deserialized.
    /// The caller can then forward reads to deserialize any interned values in the packet body.
    /// </remarks>
    internal sealed class InterningReadTranslator : ITranslatable
    {
        private readonly ITranslator _translator;
 
        private List<string> _strings = [];
 
        private Dictionary<InternPathIds, string> _pathIdsToString = [];
 
        internal InterningReadTranslator(ITranslator translator)
        {
            if (translator.Mode != TranslationDirection.ReadFromStream)
            {
                throw new InvalidOperationException(
                    $"{nameof(InterningReadTranslator)} can only be used with {nameof(TranslationDirection.ReadFromStream)}.");
            }
 
            _translator = translator;
        }
 
        internal string? Read()
        {
            int key = -1;
            _translator.Translate(ref key);
            return _strings[key];
        }
 
        internal string? ReadPath()
        {
            // If the writer set a null marker, read this as a single string.
            if (!_translator.TranslateNullable(string.Empty))
            {
                return Read();
            }
 
            int directoryKey = -1;
            int fileNameKey = -1;
            _translator.Translate(ref directoryKey);
            _translator.Translate(ref fileNameKey);
 
            InternPathIds pathIds = new(directoryKey, fileNameKey);
 
            // Only concatenate paths the first time we encounter a pair.
            if (_pathIdsToString.TryGetValue(pathIds, out string? path))
            {
                return path;
            }
 
            string directory = _strings[pathIds.DirectoryId];
            string fileName = _strings[pathIds.FileNameId];
            string str = string.Concat(directory, fileName);
            _pathIdsToString.Add(pathIds, str);
 
            return str;
        }
 
        public void Translate(ITranslator translator)
        {
            // Only deserialize the intern header since the caller will be reading directly from the stream.
            _translator.Translate(ref _strings);
#if NET
            _pathIdsToString.Clear();
            _pathIdsToString.EnsureCapacity(_strings.Count);
#else
            _pathIdsToString = new(_strings.Count);
#endif
        }
    }
}