File: FindSymbols\SymbolTree\SymbolTreeInfo_Serialization.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Storage;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.FindSymbols
{
    internal partial class SymbolTreeInfo : IObjectWritable
    {
        private const string PrefixSymbolTreeInfo = "<SymbolTreeInfo>";
        private static readonly Checksum SerializationFormatChecksum = Checksum.Create("24");
 
        /// <summary>
        /// Generalized function for loading/creating/persisting data.  Used as the common core code for serialization
        /// of source and metadata SymbolTreeInfos.
        /// </summary>
        private static async Task<SymbolTreeInfo> LoadOrCreateAsync(
            SolutionServices services,
            SolutionKey solutionKey,
            Checksum checksum,
            Func<Checksum, ValueTask<SymbolTreeInfo>> createAsync,
            string keySuffix,
            CancellationToken cancellationToken)
        {
            using (Logger.LogBlock(FunctionId.SymbolTreeInfo_TryLoadOrCreate, cancellationToken))
            {
                // Ok, we can use persistence.  First try to load from the persistence service. The data in the
                // persistence store must match the checksum passed in.
 
                var read = await LoadAsync(services, solutionKey, checksum, checksumMustMatch: true, keySuffix, cancellationToken).ConfigureAwait(false);
                if (read != null)
                {
                    // If we were able to read something in, it's checksum better
                    // have matched the checksum we expected.
                    Debug.Assert(read.Checksum == checksum);
                    return read;
                }
 
                cancellationToken.ThrowIfCancellationRequested();
 
                // Now, try to create a new instance and write it to the persistence service.
                SymbolTreeInfo result;
                using (Logger.LogBlock(FunctionId.SymbolTreeInfo_Create, cancellationToken))
                {
                    result = await createAsync(checksum).ConfigureAwait(false);
                    Contract.ThrowIfNull(result);
                }
 
                var persistentStorageService = services.GetPersistentStorageService();
 
                var storage = await persistentStorageService.GetStorageAsync(solutionKey, cancellationToken).ConfigureAwait(false);
                await using var _ = storage.ConfigureAwait(false);
 
                using (var stream = SerializableBytes.CreateWritableStream())
                {
                    using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken))
                    {
                        result.WriteTo(writer);
                    }
 
                    stream.Position = 0;
 
                    var key = PrefixSymbolTreeInfo + keySuffix;
                    await storage.WriteStreamAsync(key, stream, checksum, cancellationToken).ConfigureAwait(false);
                }
 
                return result;
            }
        }
 
        private static async Task<SymbolTreeInfo?> LoadAsync(
            SolutionServices services,
            SolutionKey solutionKey,
            Checksum checksum,
            bool checksumMustMatch,
            string keySuffix,
            CancellationToken cancellationToken)
        {
            var persistentStorageService = services.GetPersistentStorageService();
 
            var storage = await persistentStorageService.GetStorageAsync(solutionKey, cancellationToken).ConfigureAwait(false);
            await using var _ = storage.ConfigureAwait(false);
 
            // Get the unique key to identify our data.
            var key = PrefixSymbolTreeInfo + keySuffix;
 
            // If the checksum doesn't need to match, then we can pass in 'null' here allowing any result to be found.
            using var stream = await storage.ReadStreamAsync(key, checksumMustMatch ? checksum : null, cancellationToken).ConfigureAwait(false);
            using var reader = ObjectReader.TryGetReader(stream, cancellationToken: cancellationToken);
 
            // We have some previously persisted data.  Attempt to read it back.  
            // If we're able to, and the version of the persisted data matches
            // our version, then we can reuse this instance.
            return TryReadSymbolTreeInfo(reader, checksum);
        }
 
        bool IObjectWritable.ShouldReuseInSerialization => true;
 
        public void WriteTo(ObjectWriter writer)
        {
            writer.WriteInt32(_nodes.Length);
            foreach (var group in GroupByName(_nodes.AsMemory()))
            {
                writer.WriteString(group.Span[0].Name);
                writer.WriteInt32(group.Length);
                foreach (var item in group.Span)
                {
                    writer.WriteInt32(item.ParentIndex);
                }
            }
 
            writer.WriteInt32(_inheritanceMap.Keys.Count);
            foreach (var kvp in _inheritanceMap)
            {
                writer.WriteInt32(kvp.Key);
                writer.WriteInt32(kvp.Value.Count);
 
                foreach (var v in kvp.Value)
                {
                    writer.WriteInt32(v);
                }
            }
 
            if (_receiverTypeNameToExtensionMethodMap == null)
            {
                writer.WriteInt32(0);
            }
            else
            {
                writer.WriteInt32(_receiverTypeNameToExtensionMethodMap.Count);
                foreach (var key in _receiverTypeNameToExtensionMethodMap.Keys)
                {
                    writer.WriteString(key);
 
                    var values = _receiverTypeNameToExtensionMethodMap[key];
                    writer.WriteInt32(values.Count);
 
                    foreach (var value in values)
                    {
                        writer.WriteString(value.FullyQualifiedContainerName);
                        writer.WriteString(value.Name);
                    }
                }
            }
 
            _spellChecker.WriteTo(writer);
            return;
 
            // sortedNodes is an array of Node instances which is often sorted by Node.Name by the caller. This method
            // produces a sequence of spans within sortedNodes for Node instances that all have the same Name, allowing
            // serialization to record the string once followed by the remaining properties for the nodes in the group.
            static IEnumerable<ReadOnlyMemory<Node>> GroupByName(ReadOnlyMemory<Node> sortedNodes)
            {
                if (sortedNodes.IsEmpty)
                    yield break;
 
                var startIndex = 0;
                var currentName = sortedNodes.Span[0].Name;
                for (var i = 1; i < sortedNodes.Length; i++)
                {
                    var node = sortedNodes.Span[i];
                    if (node.Name != currentName)
                    {
                        yield return sortedNodes[startIndex..i];
                        startIndex = i;
                    }
                }
 
                yield return sortedNodes[startIndex..sortedNodes.Length];
            }
        }
 
        private static SymbolTreeInfo? TryReadSymbolTreeInfo(
            ObjectReader reader, Checksum checksum)
        {
            if (reader == null)
                return null;
 
            try
            {
                var nodeCount = reader.ReadInt32();
                using var _ = ArrayBuilder<Node>.GetInstance(nodeCount, out var nodes);
 
                for (var i = 0; i < nodeCount; i++)
                {
                    var name = reader.ReadString();
                    var groupCount = reader.ReadInt32();
                    for (var j = 0; j < groupCount; j++)
                    {
                        var parentIndex = reader.ReadInt32();
                        nodes.Add(new Node(name, parentIndex));
                    }
                }
 
                var inheritanceMap = new OrderPreservingMultiDictionary<int, int>();
                var inheritanceMapKeyCount = reader.ReadInt32();
                for (var i = 0; i < inheritanceMapKeyCount; i++)
                {
                    var key = reader.ReadInt32();
                    var valueCount = reader.ReadInt32();
 
                    for (var j = 0; j < valueCount; j++)
                    {
                        var value = reader.ReadInt32();
                        inheritanceMap.Add(key, value);
                    }
                }
 
                MultiDictionary<string, ExtensionMethodInfo>? receiverTypeNameToExtensionMethodMap;
 
                var keyCount = reader.ReadInt32();
                if (keyCount == 0)
                {
                    receiverTypeNameToExtensionMethodMap = null;
                }
                else
                {
                    receiverTypeNameToExtensionMethodMap = new MultiDictionary<string, ExtensionMethodInfo>();
 
                    for (var i = 0; i < keyCount; i++)
                    {
                        var typeName = reader.ReadString();
                        var valueCount = reader.ReadInt32();
 
                        for (var j = 0; j < valueCount; j++)
                        {
                            var containerName = reader.ReadString();
                            var name = reader.ReadString();
 
                            receiverTypeNameToExtensionMethodMap.Add(typeName, new ExtensionMethodInfo(containerName, name));
                        }
                    }
                }
 
                var spellChecker = SpellChecker.TryReadFrom(reader);
                if (spellChecker is null)
                    return null;
 
                var nodeArray = nodes.ToImmutableAndClear();
 
                return new SymbolTreeInfo(
                    checksum, nodeArray, spellChecker, inheritanceMap, receiverTypeNameToExtensionMethodMap);
            }
            catch
            {
                Logger.Log(FunctionId.SymbolTreeInfo_ExceptionInCacheRead);
            }
 
            return null;
        }
 
        internal readonly partial struct TestAccessor
        {
            public static SymbolTreeInfo? ReadSymbolTreeInfo(ObjectReader reader, Checksum checksum)
                => TryReadSymbolTreeInfo(reader, checksum);
        }
    }
}