File: Serialization\SerializableSourceText.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.Immutable;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Serialization
{
    /// <summary>
    /// Represents a <see cref="SourceText"/> which can be serialized for sending to another process. The text is not
    /// required to be a live object in the current process, and can instead be held in temporary storage accessible by
    /// both processes.
    /// </summary>
    internal sealed class SerializableSourceText
    {
        /// <summary>
        /// The storage location for <see cref="SourceText"/>.
        /// </summary>
        /// <remarks>
        /// Exactly one of <see cref="_storage"/> or <see cref="_text"/> will be non-<see langword="null"/>.
        /// </remarks>
        private readonly ITemporaryTextStorageWithName? _storage;
 
        /// <summary>
        /// The <see cref="SourceText"/> in the current process.
        /// </summary>
        /// <remarks>
        /// <inheritdoc cref="Storage"/>
        /// </remarks>
        private readonly SourceText? _text;
 
        /// <summary>
        /// Weak reference to a SourceText computed from <see cref="_storage"/>.  Useful so that if multiple requests
        /// come in for the source text, the same one can be returned as long as something is holding it alive.
        /// </summary>
        private readonly WeakReference<SourceText?> _computedText = new(target: null);
 
        public SerializableSourceText(ITemporaryTextStorageWithName storage)
            : this(storage, text: null)
        {
        }
 
        public SerializableSourceText(SourceText text)
            : this(storage: null, text)
        {
        }
 
        private SerializableSourceText(ITemporaryTextStorageWithName? storage, SourceText? text)
        {
            Debug.Assert(storage is null != text is null);
 
            _storage = storage;
            _text = text;
        }
 
        /// <summary>
        /// Returns the strongly referenced SourceText if we have it, or tries to retrieve it from the weak reference if
        /// it's still being held there.
        /// </summary>
        /// <returns></returns>
        private SourceText? TryGetText()
            => _text ?? _computedText.GetTarget();
 
        public ImmutableArray<byte> GetChecksum()
        {
            return TryGetText()?.GetChecksum() ?? _storage!.GetChecksum();
        }
 
        public async ValueTask<SourceText> GetTextAsync(CancellationToken cancellationToken)
        {
            var text = TryGetText();
            if (text != null)
                return text;
 
            // Read and cache the text from the storage object so that other requests may see it if still kept alive by something.
            text = await _storage!.ReadTextAsync(cancellationToken).ConfigureAwait(false);
            _computedText.SetTarget(text);
            return text;
        }
 
        public SourceText GetText(CancellationToken cancellationToken)
        {
            var text = TryGetText();
            if (text != null)
                return text;
 
            // Read and cache the text from the storage object so that other requests may see it if still kept alive by something.
            text = _storage!.ReadText(cancellationToken);
            _computedText.SetTarget(text);
            return text;
        }
 
        public static ValueTask<SerializableSourceText> FromTextDocumentStateAsync(TextDocumentState state, CancellationToken cancellationToken)
        {
            if (state.Storage is ITemporaryTextStorageWithName storage)
            {
                return new ValueTask<SerializableSourceText>(new SerializableSourceText(storage));
            }
            else
            {
                return SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync(
                    static (state, cancellationToken) => state.GetTextAsync(cancellationToken),
                    static (text, _) => new SerializableSourceText(text),
                    state,
                    cancellationToken);
            }
        }
 
        public void Serialize(ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (_storage is not null)
            {
                context.AddResource(_storage);
 
                writer.WriteInt32((int)_storage.ChecksumAlgorithm);
                writer.WriteEncoding(_storage.Encoding);
 
                writer.WriteInt32((int)SerializationKinds.MemoryMapFile);
                writer.WriteString(_storage.Name);
                writer.WriteInt64(_storage.Offset);
                writer.WriteInt64(_storage.Size);
            }
            else
            {
                RoslynDebug.AssertNotNull(_text);
 
                writer.WriteInt32((int)_text.ChecksumAlgorithm);
                writer.WriteEncoding(_text.Encoding);
                writer.WriteInt32((int)SerializationKinds.Bits);
                _text.WriteTo(writer, cancellationToken);
            }
        }
 
        public static SerializableSourceText Deserialize(
            ObjectReader reader,
            ITemporaryStorageServiceInternal storageService,
            ITextFactoryService textService,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32();
            var encoding = (Encoding)reader.ReadValue();
 
            var kind = (SerializationKinds)reader.ReadInt32();
            if (kind == SerializationKinds.MemoryMapFile)
            {
                var storage2 = (ITemporaryStorageService2)storageService;
 
                var name = reader.ReadString();
                var offset = reader.ReadInt64();
                var size = reader.ReadInt64();
 
                var storage = storage2.AttachTemporaryTextStorage(name, offset, size, checksumAlgorithm, encoding);
                if (storage is ITemporaryTextStorageWithName storageWithName)
                {
                    return new SerializableSourceText(storageWithName);
                }
                else
                {
                    return new SerializableSourceText(storage.ReadText(cancellationToken));
                }
            }
 
            Contract.ThrowIfFalse(kind == SerializationKinds.Bits);
            return new SerializableSourceText(SourceTextExtensions.ReadFrom(textService, reader, encoding, checksumAlgorithm, cancellationToken));
        }
    }
}