File: Resources\SatelliteAssemblyCache.cs
Web Access
Project: Microsoft.DotNet.Cli.CoreUtils.csproj (Microsoft.DotNet.Cli.CoreUtils)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using System.Reflection;
using System.Resources;
using System.Threading;

namespace Microsoft.DotNet.Cli.Resources.Internal;

/// <summary>
///  Caches runtime satellite binding results for one resource assembly.
/// </summary>
internal sealed class SatelliteAssemblyCache
{
    private readonly Dictionary<string, Assembly?> _assemblies = [with(StringComparer.Ordinal)];
    private readonly Lock _lock = new();
    private SatelliteStringResourceSourceMetadata? _metadata;
    private SatelliteStringResourceSourceMetadata? _runtimeMetadata;

    /// <summary>
    ///  Gets cached source metadata for the resource assembly.
    /// </summary>
    /// <param name="resourceAssembly">The main resource assembly.</param>
    /// <param name="includeContractVersion">
    ///  Whether to read and validate <see cref="SatelliteContractVersionAttribute"/>.
    /// </param>
    /// <returns>The cached source metadata.</returns>
    internal SatelliteStringResourceSourceMetadata GetMetadata(
        Assembly resourceAssembly,
        bool includeContractVersion)
    {
        lock (_lock)
        {
            SatelliteStringResourceSourceMetadata? metadata = includeContractVersion
                ? _runtimeMetadata
                : _metadata;

            if (metadata is not null)
            {
                return metadata;
            }

            string? assemblyName = resourceAssembly.GetName().Name;
            ManagedAssemblyIdentity resourceAssemblyIdentity = ManagedAssemblyIdentity.FromAssemblyName(
                resourceAssembly.GetName());

            string? satelliteAssemblyFileName = assemblyName is null
                ? null
                : $"{assemblyName}.resources.dll";

            string? neutralCultureName = null;
            Attribute? neutralAttribute = Attribute.GetCustomAttribute(
                resourceAssembly,
                typeof(NeutralResourcesLanguageAttribute));

            if (neutralAttribute is NeutralResourcesLanguageAttribute neutralResources)
            {
                if (neutralResources.Location == UltimateResourceFallbackLocation.Satellite)
                {
                    throw new NotSupportedException("Neutral resources stored in a satellite are not supported.");
                }

                neutralCultureName = neutralResources.CultureName;
            }

            Version? satelliteContractVersion = null;
            if (includeContractVersion)
            {
                Attribute? contractAttribute = Attribute.GetCustomAttribute(
                    resourceAssembly,
                    typeof(SatelliteContractVersionAttribute));

                if (contractAttribute is SatelliteContractVersionAttribute contract
                    && !Version.TryParse(contract.Version, out satelliteContractVersion))
                {
                    throw new ArgumentException(
                        "The satellite contract version is invalid.",
                        nameof(resourceAssembly));
                }
            }

            metadata = new(
                resourceAssemblyIdentity,
                satelliteAssemblyFileName,
                neutralCultureName,
                satelliteContractVersion);

            if (includeContractVersion)
            {
                _runtimeMetadata = metadata;
            }
            else
            {
                _metadata = metadata;
            }

            return metadata;
        }
    }

    /// <summary>
    ///  Gets the satellite assembly for <paramref name="culture"/>, caching both successful and
    ///  missing binds.
    /// </summary>
    /// <param name="resourceAssembly">The main resource assembly.</param>
    /// <param name="culture">The satellite culture.</param>
    /// <param name="contractVersion">The satellite contract version, if declared.</param>
    /// <returns>The satellite assembly, or <see langword="null"/> when it is absent.</returns>
    internal Assembly? GetSatelliteAssembly(
        Assembly resourceAssembly,
        CultureInfo culture,
        Version? contractVersion)
    {
        lock (_lock)
        {
            if (_assemblies.TryGetValue(culture.Name, out Assembly? satellite))
            {
                return satellite;
            }

            try
            {
                satellite = contractVersion is null
                    ? resourceAssembly.GetSatelliteAssembly(culture)
                    : resourceAssembly.GetSatelliteAssembly(culture, contractVersion);
            }
            catch (FileNotFoundException)
            {
                satellite = null;
            }

            _assemblies.Add(culture.Name, satellite);
            return satellite;
        }
    }
}