File: ResourceDescription.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.IO;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.Emit;
using System.Reflection;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Security.Cryptography;
 
namespace Microsoft.CodeAnalysis
{
    /// <summary>
    /// Representation of a resource whose contents are to be embedded in the output assembly.
    /// </summary>
    public sealed class ResourceDescription : Cci.IFileReference
    {
        internal readonly string ResourceName;
        internal readonly string? FileName; // null if embedded
        internal readonly bool IsPublic;
        internal readonly Func<Stream> DataProvider;
        private readonly CryptographicHashProvider _hashes;
 
        /// <summary>
        /// Creates a representation of a resource whose contents are to be embedded in the output assembly.
        /// </summary>
        /// <param name="resourceName">Resource name.</param>
        /// <param name="dataProvider">The callers will dispose the result after use.
        /// This allows the resources to be opened and read one at a time.
        /// </param>
        /// <param name="isPublic">True if the resource is public.</param>
        /// <remarks>
        /// Returns a stream of the data to embed.
        /// </remarks> 
        public ResourceDescription(string resourceName, Func<Stream> dataProvider, bool isPublic)
            : this(resourceName, fileName: null, dataProvider, isPublic, isEmbedded: true, checkArgs: true)
        {
        }
 
        /// <summary>
        /// Creates a representation of a resource whose file name will be recorded in the assembly.
        /// </summary>
        /// <param name="resourceName">Resource name.</param>
        /// <param name="fileName">File name with an extension to be stored in metadata.</param>
        /// <param name="dataProvider">The callers will dispose the result after use.
        /// This allows the resources to be opened and read one at a time.
        /// </param>
        /// <param name="isPublic">True if the resource is public.</param>
        /// <remarks>
        /// Function returning a stream of the resource content (used to calculate hash).
        /// </remarks>
        public ResourceDescription(string resourceName, string? fileName, Func<Stream> dataProvider, bool isPublic)
            : this(resourceName, fileName, dataProvider, isPublic, isEmbedded: false, checkArgs: true)
        {
        }
 
        internal ResourceDescription(string resourceName, string? fileName, Func<Stream> dataProvider, bool isPublic, bool isEmbedded, bool checkArgs)
        {
            if (checkArgs)
            {
                if (dataProvider == null)
                {
                    throw new ArgumentNullException(nameof(dataProvider));
                }
 
                if (resourceName == null)
                {
                    throw new ArgumentNullException(nameof(resourceName));
                }
 
                if (!MetadataHelpers.IsValidMetadataIdentifier(resourceName))
                {
                    throw new ArgumentException(CodeAnalysisResources.EmptyOrInvalidResourceName, nameof(resourceName));
                }
 
                if (!isEmbedded)
                {
                    if (fileName == null)
                    {
                        throw new ArgumentNullException(nameof(fileName));
                    }
 
                    if (!MetadataHelpers.IsValidMetadataFileName(fileName))
                    {
                        throw new ArgumentException(CodeAnalysisResources.EmptyOrInvalidFileName, nameof(fileName));
                    }
                }
            }
 
            this.ResourceName = resourceName;
            this.DataProvider = dataProvider;
            this.FileName = isEmbedded ? null : fileName;
            this.IsPublic = isPublic;
            _hashes = new ResourceHashProvider(this);
        }
 
        private sealed class ResourceHashProvider : CryptographicHashProvider
        {
            private readonly ResourceDescription _resource;
 
            public ResourceHashProvider(ResourceDescription resource)
            {
                RoslynDebug.Assert(resource != null);
                _resource = resource;
            }
 
            internal override ImmutableArray<byte> ComputeHash(HashAlgorithm algorithm)
            {
                try
                {
                    using (var stream = _resource.DataProvider())
                    {
                        if (stream == null)
                        {
                            throw new InvalidOperationException(CodeAnalysisResources.ResourceDataProviderShouldReturnNonNullStream);
                        }
 
                        return ImmutableArray.CreateRange(algorithm.ComputeHash(stream));
                    }
                }
                catch (Exception ex)
                {
                    throw new ResourceException(_resource.FileName, ex);
                }
            }
        }
 
        internal bool IsEmbedded
        {
            get { return FileName == null; }
        }
 
        internal Cci.ManagedResource ToManagedResource()
        {
            return new Cci.ManagedResource(ResourceName, IsPublic, IsEmbedded ? DataProvider : null, IsEmbedded ? null : this, offset: 0);
        }
 
        ImmutableArray<byte> Cci.IFileReference.GetHashValue(AssemblyHashAlgorithm algorithmId)
        {
            return _hashes.GetHash(algorithmId);
        }
 
        string? Cci.IFileReference.FileName
        {
            get { return FileName; }
        }
 
        bool Cci.IFileReference.HasMetadata
        {
            get { return false; }
        }
    }
}