File: Resources\SatelliteStringResourceManager.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.Runtime.ExceptionServices;
using System.Threading;

namespace Microsoft.DotNet.Cli.Resources;

/// <summary>
///  Resolves localized strings from one configured culture source and falls back to a neutral
///  <see cref="StringResourceManager"/>.
/// </summary>
/// <remarks>
///  <para>
///   Create an instance with
///   <see cref="FromRuntimeSatellites(string, Assembly, StringResourceManagerOptions)"/> for normal
///   runtime satellite binding,
///   <see cref="FromResourcesDirectory(string, string, Assembly, StringResourceManagerOptions)"/> for loose
///   per-culture <c>.resources</c> files, or
///   <see cref="FromSatelliteDirectory(string, string, Assembly, StringResourceManagerOptions)"/> to parse deployed satellite
///  assemblies as data. Use
///  <see cref="FromAssemblyFiles(string, string, string, StringResourceManagerOptions)"/> when both
///  the neutral owner and its satellites must be parsed as data. A manager never mixes these
///  localized source modes.
///  </para>
///  <para>
///   For a requested culture the manager walks from the most specific culture up to (but not
///   including) the invariant culture or an assembly-declared neutral culture. More-specific tables
///   take precedence. When no localized table supplies the key, lookup defers to the neutral manager.
///  </para>
///  <para>
///   Loaded source tables and culture chains are cached until <see cref="ReleaseAllResources()"/> is
///   called. Successful loads and missing-source results are cached, including during concurrent
///   first lookup. Failed loads throw directly and are retried on the next lookup. Caller-supplied
///   neutral managers remain caller-owned and are not released by this manager.
///  </para>
///  <para>
///   Runtime assembly metadata and successful or missing satellite bind results are cached per
///   resource assembly for the process lifetime. <see cref="ReleaseAllResources()"/> reloads resource
///   tables but does not repeat runtime assembly binding.
///  </para>
///  <para>
///   Each distinct requested culture and each missing source candidate remain cached for the current
///   generation. Callers must not supply an unbounded sequence of attacker-controlled cultures.
///  </para>
///  <para>
///   Directory factories capture a fully qualified root during construction. The resource base name,
///   culture names, and satellite assembly name must each be a single path segment so probing remains
///   deterministic and independent of later current-directory changes.
///  </para>
///  <para>
///   Resource data is expected to be a trusted output of the application's build and deployment
///   pipeline. Strict probing continues parent or neutral fallback only for an absent localized
///   source. <see cref="SatelliteStringResourceProbeMode.FallbackOnFailure"/> also treats unreadable,
///   malformed, unsupported, incorrectly bundled, and identity-mismatched localized satellite
///   candidates as missing. Neutral resource failures always throw.
///  </para>
///  <para>
///   Direct satellite modes validate the satellite simple name, culture, version, and public key
///   against the resource-owning assembly before reading its manifest resource.
///  </para>
///  <para>
///   Null resources always reject a table. By default any non-string resource rejects the table;
///   <see cref="StringResourceManagerOptions.IgnoreNonStringResources"/> skips non-string names and
///   values, so those names behave as missing during culture fallback.
///  </para>
///  <para>
///   Resource names are matched ordinally and case-sensitively. Case-insensitive lookup is not
///   supported.
///  </para>
/// </remarks>
public sealed class SatelliteStringResourceManager : StringResourceManager
{
    private readonly string _resourceName;
    private readonly SatelliteStringResourceSourceKind _sourceKind;
    private readonly string? _localizedRoot;
    private readonly Assembly? _resourceAssembly;
    private readonly ManagedAssemblyStringResourceSource? _resourceAssemblySource;
    private readonly string? _resourceAssemblySimpleNameAlias;
    private readonly Func<string, MappedMemoryManager> _openFile;
    private readonly SatelliteStringResourceProbeMode _probeMode;
    private volatile SatelliteStringResourceSourceMetadata? _neutralSourceMetadata;
    private readonly Lock _localizedLoadGate = new();
    private Dictionary<string, IndexedStringResourceTable[]>? _cultureTables;
    private Dictionary<string, IndexedStringResourceTable?>? _sourceTables;

    private static readonly Func<string, MappedMemoryManager> s_openFile = MappedMemoryManager.CreateFromFile;

#pragma warning disable IDE0028 // ConditionalWeakTable cannot be constructed with a collection expression on net472.
    private static readonly ConditionalWeakTable<Assembly, SatelliteAssemblyCache> s_satelliteAssemblies = new();
#pragma warning restore IDE0028

    /// <summary>
    ///  Initializes a manager that reads loose per-culture resource files from the
    ///  <c>resources</c> directory under <see cref="AppContext.BaseDirectory"/>.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="assembly">The assembly that contains the neutral resource.</param>
    public SatelliteStringResourceManager(string baseName, Assembly assembly)
        : this(baseName, assembly, Path.Join(AppContext.BaseDirectory, "resources"))
    {
    }

    /// <summary>
    ///  Initializes a manager that reads loose per-culture resource files.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="assembly">The assembly that contains the neutral resource.</param>
    /// <param name="probeRoot">The directory containing culture subdirectories.</param>
    public SatelliteStringResourceManager(string baseName, Assembly assembly, string probeRoot)
        : this(
            baseName,
            new StringResourceManager(baseName, assembly),
            ownsNeutralResources: true,
            SatelliteStringResourceSourceKind.ResourcesDirectory,
            probeRoot,
            assembly,
            resourceAssemblySource: null,
            s_openFile,
            StringResourceManagerOptions.None)
    {
    }

    /// <inheritdoc cref="FromRuntimeSatellites(string, Assembly, StringResourceManagerOptions)"/>
    public static SatelliteStringResourceManager FromRuntimeSatellites(
        string baseName,
        Assembly resourceAssembly)
    {
        return FromRuntimeSatellites(
            baseName,
            resourceAssembly,
            StringResourceManagerOptions.None);
    }

    /// <summary>
    ///  Creates a manager that binds localized satellite assemblies through the runtime.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="resourceAssembly">The assembly that owns the neutral and satellite resources.</param>
    /// <param name="options">The resource loading options.</param>
    /// <returns>A runtime-satellite manager.</returns>
    public static SatelliteStringResourceManager FromRuntimeSatellites(
        string baseName,
        Assembly resourceAssembly,
        StringResourceManagerOptions options)
    {
        ArgumentNullException.ThrowIfNull(baseName);
        ArgumentNullException.ThrowIfNull(resourceAssembly);

        return new(
            baseName,
            new StringResourceManager(baseName, resourceAssembly, options),
            ownsNeutralResources: true,
            SatelliteStringResourceSourceKind.RuntimeSatellites,
            localizedRoot: null,
            resourceAssembly,
            resourceAssemblySource: null,
            s_openFile,
            options);
    }

    /// <inheritdoc cref="FromRuntimeSatellites(string, Assembly, StringResourceManager, StringResourceManagerOptions)"/>
    public static SatelliteStringResourceManager FromRuntimeSatellites(
        string baseName,
        Assembly resourceAssembly,
        StringResourceManager neutralResources)
    {
        return FromRuntimeSatellites(
            baseName,
            resourceAssembly,
            neutralResources,
            StringResourceManagerOptions.None);
    }

    /// <summary>
    ///  Creates a manager that binds localized satellite assemblies through the runtime and delegates
    ///  neutral fallback to <paramref name="neutralResources"/>.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="resourceAssembly">The assembly that owns the localized satellites.</param>
    /// <param name="neutralResources">The caller-owned manager that supplies neutral strings.</param>
    /// <param name="options">The localized resource loading options.</param>
    /// <returns>A runtime-satellite manager.</returns>
    public static SatelliteStringResourceManager FromRuntimeSatellites(
        string baseName,
        Assembly resourceAssembly,
        StringResourceManager neutralResources,
        StringResourceManagerOptions options)
    {
        ArgumentNullException.ThrowIfNull(baseName);
        ArgumentNullException.ThrowIfNull(resourceAssembly);
        ArgumentNullException.ThrowIfNull(neutralResources);

        return new(
            baseName,
            neutralResources,
            ownsNeutralResources: false,
            SatelliteStringResourceSourceKind.RuntimeSatellites,
            localizedRoot: null,
            resourceAssembly,
            resourceAssemblySource: null,
            s_openFile,
            options);
    }

    /// <inheritdoc cref="FromResourcesDirectory(string, string, Assembly, StringResourceManagerOptions)"/>
    public static SatelliteStringResourceManager FromResourcesDirectory(
        string baseName,
        string resourcesDirectory,
        Assembly neutralAssembly)
    {
        return FromResourcesDirectory(
            baseName,
            resourcesDirectory,
            neutralAssembly,
            StringResourceManagerOptions.None);
    }

    /// <summary>
    ///  Creates a manager that reads loose per-culture binary resource files.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="resourcesDirectory">The directory containing culture subdirectories.</param>
    /// <param name="neutralAssembly">The assembly that contains the neutral resource.</param>
    /// <param name="options">The resource loading options.</param>
    /// <returns>A loose-resource manager.</returns>
    public static SatelliteStringResourceManager FromResourcesDirectory(
        string baseName,
        string resourcesDirectory,
        Assembly neutralAssembly,
        StringResourceManagerOptions options)
    {
        ArgumentNullException.ThrowIfNull(baseName);
        ArgumentNullException.ThrowIfNull(resourcesDirectory);
        ArgumentNullException.ThrowIfNull(neutralAssembly);

        return new(
            baseName,
            new StringResourceManager(baseName, neutralAssembly, options),
            ownsNeutralResources: true,
            SatelliteStringResourceSourceKind.ResourcesDirectory,
            resourcesDirectory,
            neutralAssembly,
            resourceAssemblySource: null,
            s_openFile,
            options);
    }

    /// <inheritdoc cref="FromResourcesDirectory(string, string, StringResourceManager, StringResourceManagerOptions)"/>
    public static SatelliteStringResourceManager FromResourcesDirectory(
        string baseName,
        string resourcesDirectory,
        StringResourceManager neutralResources)
    {
        return FromResourcesDirectory(
            baseName,
            resourcesDirectory,
            neutralResources,
            StringResourceManagerOptions.None);
    }

    /// <summary>
    ///  Creates a loose-resource manager that opens files through <paramref name="openFile"/>.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="resourcesDirectory">The directory containing culture subdirectories.</param>
    /// <param name="neutralAssembly">The assembly that contains the neutral resource.</param>
    /// <param name="openFile">The function that opens a file as mapped memory.</param>
    /// <param name="options">The resource loading options.</param>
    /// <returns>A loose-resource manager.</returns>
    internal static SatelliteStringResourceManager FromResourcesDirectory(
        string baseName,
        string resourcesDirectory,
        Assembly neutralAssembly,
        Func<string, MappedMemoryManager> openFile,
        StringResourceManagerOptions options = StringResourceManagerOptions.None)
    {
        return new(
            baseName,
            new StringResourceManager(baseName, neutralAssembly, options),
            ownsNeutralResources: true,
            SatelliteStringResourceSourceKind.ResourcesDirectory,
            resourcesDirectory,
            neutralAssembly,
            resourceAssemblySource: null,
            openFile,
            options);
    }

    /// <summary>
    ///  Creates a manager that reads loose per-culture binary resource files and delegates neutral
    ///  fallback to <paramref name="neutralResources"/>.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="resourcesDirectory">The directory containing culture subdirectories.</param>
    /// <param name="neutralResources">The caller-owned manager that supplies neutral strings.</param>
    /// <param name="options">The localized resource loading options.</param>
    /// <returns>A loose-resource manager.</returns>
    public static SatelliteStringResourceManager FromResourcesDirectory(
        string baseName,
        string resourcesDirectory,
        StringResourceManager neutralResources,
        StringResourceManagerOptions options)
    {
        ArgumentNullException.ThrowIfNull(baseName);
        ArgumentNullException.ThrowIfNull(resourcesDirectory);
        ArgumentNullException.ThrowIfNull(neutralResources);

        return new(
            baseName,
            neutralResources,
            ownsNeutralResources: false,
            SatelliteStringResourceSourceKind.ResourcesDirectory,
            resourcesDirectory,
            neutralResources.SourceAssembly,
            resourceAssemblySource: null,
            s_openFile,
            options);
    }

    /// <inheritdoc cref="FromSatelliteDirectory(string, string, Assembly, StringResourceManagerOptions)"/>
    public static SatelliteStringResourceManager FromSatelliteDirectory(
        string baseName,
        string satelliteDirectory,
        Assembly resourceAssembly)
    {
        return FromSatelliteDirectory(
            baseName,
            satelliteDirectory,
            resourceAssembly,
            StringResourceManagerOptions.None);
    }

    /// <inheritdoc cref="FromSatelliteDirectory(string, string, Assembly, StringResourceManagerOptions, SatelliteStringResourceProbeMode)"/>
    public static SatelliteStringResourceManager FromSatelliteDirectory(
        string baseName,
        string satelliteDirectory,
        Assembly resourceAssembly,
        SatelliteStringResourceProbeMode probeMode) => FromSatelliteDirectory(
            baseName,
            satelliteDirectory,
            resourceAssembly,
            StringResourceManagerOptions.None,
            probeMode);

    /// <summary>
    ///  Creates a direct-satellite manager that maps a generated owner assembly to an external
    ///  assembly family name.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="satelliteDirectory">The directory containing culture subdirectories.</param>
    /// <param name="generatedOwnerAssembly">The generated accessor's owner assembly.</param>
    /// <param name="externalOwnerSimpleName">The external managed owner assembly simple name.</param>
    /// <param name="probeMode">How localized satellite candidate failures affect fallback.</param>
    /// <returns>A direct-satellite manager.</returns>
    public static SatelliteStringResourceManager FromSatelliteDirectory(
        string baseName,
        string satelliteDirectory,
        Assembly generatedOwnerAssembly,
        string externalOwnerSimpleName,
        SatelliteStringResourceProbeMode probeMode)
    {
        ArgumentNullException.ThrowIfNull(baseName);
        ArgumentNullException.ThrowIfNull(satelliteDirectory);
        ArgumentNullException.ThrowIfNull(generatedOwnerAssembly);
        ArgumentNullException.ThrowIfNull(externalOwnerSimpleName);
        ValidatePathSegment(externalOwnerSimpleName, nameof(externalOwnerSimpleName));

        return new(
            baseName,
            new StringResourceManager(baseName, generatedOwnerAssembly),
            ownsNeutralResources: true,
            SatelliteStringResourceSourceKind.SatelliteDirectory,
            satelliteDirectory,
            generatedOwnerAssembly,
            resourceAssemblySource: null,
            s_openFile,
            StringResourceManagerOptions.None,
            probeMode,
            externalOwnerSimpleName);
    }

    /// <summary>
    ///  Creates a manager that parses satellite assemblies directly from a directory.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="satelliteDirectory">The directory containing culture subdirectories.</param>
    /// <param name="resourceAssembly">The assembly that owns the neutral and satellite resources.</param>
    /// <param name="options">The resource loading options.</param>
    /// <returns>A direct-satellite manager.</returns>
    public static SatelliteStringResourceManager FromSatelliteDirectory(
        string baseName,
        string satelliteDirectory,
        Assembly resourceAssembly,
        StringResourceManagerOptions options) => FromSatelliteDirectory(
            baseName,
            satelliteDirectory,
            resourceAssembly,
            options,
            SatelliteStringResourceProbeMode.Strict);

    /// <summary>
    ///  Creates a manager that parses satellite assemblies directly from a directory.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="satelliteDirectory">The directory containing culture subdirectories.</param>
    /// <param name="resourceAssembly">The assembly that owns the neutral and satellite resources.</param>
    /// <param name="options">The resource loading options.</param>
    /// <param name="probeMode">How localized satellite candidate failures affect fallback.</param>
    /// <returns>A direct-satellite manager.</returns>
    public static SatelliteStringResourceManager FromSatelliteDirectory(
        string baseName,
        string satelliteDirectory,
        Assembly resourceAssembly,
        StringResourceManagerOptions options,
        SatelliteStringResourceProbeMode probeMode)
    {
        ArgumentNullException.ThrowIfNull(baseName);
        ArgumentNullException.ThrowIfNull(satelliteDirectory);
        ArgumentNullException.ThrowIfNull(resourceAssembly);

        return new(
            baseName,
            new StringResourceManager(baseName, resourceAssembly, options),
            ownsNeutralResources: true,
            SatelliteStringResourceSourceKind.SatelliteDirectory,
            satelliteDirectory,
            resourceAssembly,
            resourceAssemblySource: null,
            s_openFile,
            options,
            probeMode);
    }

    /// <inheritdoc cref="FromSatelliteDirectory(string, string, Assembly, StringResourceManager, StringResourceManagerOptions)"/>
    public static SatelliteStringResourceManager FromSatelliteDirectory(
        string baseName,
        string satelliteDirectory,
        Assembly resourceAssembly,
        StringResourceManager neutralResources)
    {
        return FromSatelliteDirectory(
            baseName,
            satelliteDirectory,
            resourceAssembly,
            neutralResources,
            StringResourceManagerOptions.None);
    }

    /// <summary>
    ///  Creates a direct-satellite manager that opens files through <paramref name="openFile"/>.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="satelliteDirectory">The directory containing culture subdirectories.</param>
    /// <param name="resourceAssembly">The assembly that owns the resources.</param>
    /// <param name="openFile">The function that opens a file as mapped memory.</param>
    /// <param name="options">The resource loading options.</param>
    /// <param name="probeMode">How localized satellite candidate failures affect fallback.</param>
    /// <returns>A direct-satellite manager.</returns>
    internal static SatelliteStringResourceManager FromSatelliteDirectory(
        string baseName,
        string satelliteDirectory,
        Assembly resourceAssembly,
        Func<string, MappedMemoryManager> openFile,
        StringResourceManagerOptions options = StringResourceManagerOptions.None,
        SatelliteStringResourceProbeMode probeMode = SatelliteStringResourceProbeMode.Strict)
    {
        return new(
            baseName,
            new StringResourceManager(baseName, resourceAssembly, options),
            ownsNeutralResources: true,
            SatelliteStringResourceSourceKind.SatelliteDirectory,
            satelliteDirectory,
            resourceAssembly,
            resourceAssemblySource: null,
            openFile,
            options,
            probeMode);
    }

    /// <summary>
    ///  Creates a manager that parses satellite assemblies directly from a directory and delegates
    ///  neutral fallback to <paramref name="neutralResources"/>.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="satelliteDirectory">The directory containing culture subdirectories.</param>
    /// <param name="resourceAssembly">The assembly that owns the localized satellites.</param>
    /// <param name="neutralResources">The caller-owned manager that supplies neutral strings.</param>
    /// <param name="options">The localized resource loading options.</param>
    /// <returns>A direct-satellite manager.</returns>
    public static SatelliteStringResourceManager FromSatelliteDirectory(
        string baseName,
        string satelliteDirectory,
        Assembly resourceAssembly,
        StringResourceManager neutralResources,
        StringResourceManagerOptions options)
    {
        ArgumentNullException.ThrowIfNull(baseName);
        ArgumentNullException.ThrowIfNull(satelliteDirectory);
        ArgumentNullException.ThrowIfNull(resourceAssembly);
        ArgumentNullException.ThrowIfNull(neutralResources);

        return new(
            baseName,
            neutralResources,
            ownsNeutralResources: false,
            SatelliteStringResourceSourceKind.SatelliteDirectory,
            satelliteDirectory,
            resourceAssembly,
            resourceAssemblySource: null,
            s_openFile,
            options);
    }

    /// <inheritdoc cref="FromAssemblyFiles(string, string, string, StringResourceManagerOptions)"/>
    public static SatelliteStringResourceManager FromAssemblyFiles(
        string baseName,
        string resourceAssemblyFile,
        string satelliteDirectory) => FromAssemblyFiles(
            baseName,
            resourceAssemblyFile,
            satelliteDirectory,
            StringResourceManagerOptions.None);

    /// <inheritdoc cref="FromAssemblyFiles(string, string, string, StringResourceManagerOptions, SatelliteStringResourceProbeMode)"/>
    public static SatelliteStringResourceManager FromAssemblyFiles(
        string baseName,
        string resourceAssemblyFile,
        string satelliteDirectory,
        SatelliteStringResourceProbeMode probeMode) => FromAssemblyFiles(
            baseName,
            resourceAssemblyFile,
            satelliteDirectory,
            StringResourceManagerOptions.None,
            probeMode);

    /// <summary>
    ///  Strictly validates an aliased external owner assembly, then creates an external-all manager.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="resourceAssemblyFile">The external managed owner assembly file.</param>
    /// <param name="satelliteDirectory">The directory containing culture subdirectories.</param>
    /// <param name="generatedOwnerAssembly">The generated accessor's owner assembly.</param>
    /// <param name="externalOwnerSimpleName">The expected external owner simple name.</param>
    /// <param name="probeMode">How localized satellite candidate failures affect fallback.</param>
    /// <returns>An external-all string resource manager.</returns>
    public static SatelliteStringResourceManager FromAssemblyFiles(
        string baseName,
        string resourceAssemblyFile,
        string satelliteDirectory,
        Assembly generatedOwnerAssembly,
        string externalOwnerSimpleName,
        SatelliteStringResourceProbeMode probeMode)
    {
        StringResourceManager.ValidateAssemblyFile(
            baseName,
            resourceAssemblyFile,
            generatedOwnerAssembly,
            externalOwnerSimpleName);

        return FromAssemblyFiles(
            baseName,
            resourceAssemblyFile,
            satelliteDirectory,
            probeMode);
    }

    /// <summary>
    ///  Creates a manager that parses neutral resources from a managed owner assembly and localized
    ///  resources from its satellite assemblies without loading those assemblies.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="resourceAssemblyFile">The managed assembly containing the neutral resource.</param>
    /// <param name="satelliteDirectory">
    ///  The directory containing culture subdirectories with satellite resource assemblies.
    /// </param>
    /// <param name="options">The resource loading options.</param>
    /// <returns>An external-assembly string resource manager.</returns>
    /// <exception cref="ArgumentNullException">
    ///  <paramref name="baseName"/>, <paramref name="resourceAssemblyFile"/>, or
    ///  <paramref name="satelliteDirectory"/> is <see langword="null"/>.
    /// </exception>
    /// <exception cref="ArgumentException"><paramref name="baseName"/> is not a single path segment.</exception>
    /// <exception cref="ArgumentOutOfRangeException"><paramref name="options"/> contains an unknown flag.</exception>
    public static SatelliteStringResourceManager FromAssemblyFiles(
        string baseName,
        string resourceAssemblyFile,
        string satelliteDirectory,
        StringResourceManagerOptions options) => FromAssemblyFiles(
            baseName,
            resourceAssemblyFile,
            satelliteDirectory,
            options,
            SatelliteStringResourceProbeMode.Strict);

    /// <summary>
    ///  Creates a manager that parses a neutral owner assembly and localized satellite assemblies
    ///  without loading them.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="resourceAssemblyFile">The managed assembly containing the neutral resource.</param>
    /// <param name="satelliteDirectory">The directory containing culture subdirectories.</param>
    /// <param name="options">The resource loading options.</param>
    /// <param name="probeMode">How localized satellite candidate failures affect fallback.</param>
    /// <returns>An external-assembly string resource manager.</returns>
    public static SatelliteStringResourceManager FromAssemblyFiles(
        string baseName,
        string resourceAssemblyFile,
        string satelliteDirectory,
        StringResourceManagerOptions options,
        SatelliteStringResourceProbeMode probeMode) => FromAssemblyFiles(
            baseName,
            resourceAssemblyFile,
            satelliteDirectory,
            s_openFile,
            options,
            probeMode);

    /// <summary>
    ///  Creates an external-assembly manager that opens files through <paramref name="openFile"/>.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="resourceAssemblyFile">The managed assembly containing the neutral resource.</param>
    /// <param name="satelliteDirectory">The directory containing culture subdirectories.</param>
    /// <param name="openFile">The function that opens a file as mapped memory.</param>
    /// <param name="options">The resource loading options.</param>
    /// <param name="probeMode">How localized satellite candidate failures affect fallback.</param>
    /// <returns>An external-assembly string resource manager.</returns>
    internal static SatelliteStringResourceManager FromAssemblyFiles(
        string baseName,
        string resourceAssemblyFile,
        string satelliteDirectory,
        Func<string, MappedMemoryManager> openFile,
        StringResourceManagerOptions options = StringResourceManagerOptions.None,
        SatelliteStringResourceProbeMode probeMode = SatelliteStringResourceProbeMode.Strict)
    {
        ArgumentNullException.ThrowIfNull(baseName);
        ArgumentNullException.ThrowIfNull(resourceAssemblyFile);
        ArgumentNullException.ThrowIfNull(satelliteDirectory);
        ArgumentNullException.ThrowIfNull(openFile);
        options.Validate();

        StringResourceManager neutralResources = StringResourceManager.FromAssemblyFile(
            baseName,
            resourceAssemblyFile,
            openFile,
            options);

        ManagedAssemblyStringResourceSource source = neutralResources.ManagedAssemblySource
            ?? throw new InvalidOperationException("The managed assembly source was not initialized.");

        return new(
            baseName,
            neutralResources,
            ownsNeutralResources: true,
            SatelliteStringResourceSourceKind.SatelliteDirectory,
            satelliteDirectory,
            resourceAssembly: null,
            source,
            openFile,
            options,
            probeMode);
    }

    private SatelliteStringResourceManager(
        string baseName,
        StringResourceManager neutralResources,
        bool ownsNeutralResources,
        SatelliteStringResourceSourceKind sourceKind,
        string? localizedRoot,
        Assembly? resourceAssembly,
        ManagedAssemblyStringResourceSource? resourceAssemblySource,
        Func<string, MappedMemoryManager> openFile,
        StringResourceManagerOptions options,
        SatelliteStringResourceProbeMode probeMode = SatelliteStringResourceProbeMode.Strict,
        string? resourceAssemblySimpleNameAlias = null)
        : base(
            baseName,
            neutralResources,
            ownsNeutralResources,
            options)
    {
        ArgumentNullException.ThrowIfNull(baseName);
        ArgumentNullException.ThrowIfNull(neutralResources);
        ArgumentNullException.ThrowIfNull(openFile);
        if (probeMode is not SatelliteStringResourceProbeMode.Strict
            and not SatelliteStringResourceProbeMode.FallbackOnFailure)
        {
            throw new ArgumentOutOfRangeException(nameof(probeMode));
        }

        if (sourceKind != SatelliteStringResourceSourceKind.RuntimeSatellites)
        {
            ArgumentNullException.ThrowIfNull(localizedRoot);
            ValidatePathSegment(baseName, nameof(baseName));
            localizedRoot = Path.GetFullPath(localizedRoot);
        }

        if (sourceKind == SatelliteStringResourceSourceKind.RuntimeSatellites)
        {
            ArgumentNullException.ThrowIfNull(resourceAssembly);
        }

        if (sourceKind == SatelliteStringResourceSourceKind.SatelliteDirectory
            && resourceAssembly is null
            && resourceAssemblySource is null)
        {
            throw new ArgumentException(
                "A resource assembly or managed assembly file source is required.",
                nameof(resourceAssembly));
        }

        _resourceName = $"{baseName}.resources";
        _sourceKind = sourceKind;
        _localizedRoot = localizedRoot;
        _resourceAssembly = resourceAssembly;
        _resourceAssemblySource = resourceAssemblySource;
        _resourceAssemblySimpleNameAlias = resourceAssemblySimpleNameAlias;
        _openFile = openFile;
        _probeMode = probeMode;
    }

    /// <summary>
    ///  Gets the string with the given name for the requested culture, its parents, or the neutral
    ///  resource table.
    /// </summary>
    /// <param name="name">The resource name.</param>
    /// <param name="culture">
    ///  The requested culture, or <see langword="null"/> to use <see cref="CultureInfo.CurrentUICulture"/>.
    /// </param>
    /// <returns>The string value, or <see langword="null"/> when the resource does not exist.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
    /// <exception cref="NotSupportedException">
    ///  A table contains a non-string resource and <see cref="StringResourceManagerOptions.IgnoreNonStringResources"/>
    ///  is not set.
    /// </exception>
    /// <exception cref="ArgumentException">A configured source is not a valid resource file.</exception>
    /// <exception cref="BadImageFormatException">
    ///  A configured source contains malformed data or a null resource.
    /// </exception>
    /// <exception cref="IOException">A configured file or stream cannot be read.</exception>
    /// <exception cref="ObjectDisposedException">A resource backing has been disposed.</exception>
    public override string? GetString(string name, CultureInfo? culture)
    {
        ArgumentNullException.ThrowIfNull(name);
        culture ??= CultureInfo.CurrentUICulture;

        foreach (IndexedStringResourceTable table in GetCultureTables(culture))
        {
            string? value = table.Lookup(name);
            if (value is not null)
            {
                return value;
            }
        }

        return base.GetString(name, culture);
    }

    /// <summary>
    ///  Releases localized state and any neutral manager created by this instance. A caller-supplied
    ///  neutral manager remains unchanged.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   Callers must ensure no resource lookups are in progress when releasing resources.
    ///  </para>
    /// </remarks>
    public override void ReleaseAllResources()
    {
        ExceptionDispatchInfo? releaseFailure = null;
        lock (_localizedLoadGate)
        {
            Dictionary<string, IndexedStringResourceTable?>? sourceTables = _sourceTables;
            _cultureTables = null;
            _sourceTables = null;

            if (sourceTables is not null)
            {
                foreach (IndexedStringResourceTable? table in sourceTables.Values)
                {
                    try
                    {
                        table?.Dispose();
                    }
                    catch (Exception exception)
                    {
                        releaseFailure ??= ExceptionDispatchInfo.Capture(exception);
                    }
                }
            }
        }

        try
        {
            base.ReleaseAllResources();
        }
        catch (Exception exception)
        {
            releaseFailure ??= ExceptionDispatchInfo.Capture(exception);
        }

        releaseFailure?.Throw();
    }

    private IndexedStringResourceTable[] GetCultureTables(CultureInfo culture)
    {
        lock (_localizedLoadGate)
        {
            if (_cultureTables is not null
                && _cultureTables.TryGetValue(culture.Name, out IndexedStringResourceTable[]? tables))
            {
                return tables;
            }

            SatelliteStringResourceSourceMetadata metadata = GetSourceMetadata();
            tables = LoadCultureChain(culture, metadata);
            (_cultureTables ??= [with(StringComparer.Ordinal)])[culture.Name] = tables;
            return tables;
        }
    }

    private IndexedStringResourceTable[] LoadCultureChain(
        CultureInfo culture,
        SatelliteStringResourceSourceMetadata metadata)
    {
        IndexedStringResourceTable? first = null;
        List<IndexedStringResourceTable>? multiple = null;

        for (CultureInfo current = culture;
            !current.Equals(CultureInfo.InvariantCulture)
                && !IsNeutralCulture(current.Name, metadata.NeutralCultureName);
            current = current.Parent)
        {
            if (_sourceKind != SatelliteStringResourceSourceKind.RuntimeSatellites)
            {
                ValidatePathSegment(current.Name, nameof(culture));
            }

            IndexedStringResourceTable? table = GetOrLoadSourceTable(current, metadata);
            if (table is null)
            {
                continue;
            }

            if (first is null)
            {
                first = table;
                continue;
            }

            (multiple ??= [first]).Add(table);
        }

        return first is null
            ? []
            : multiple is null ? [first] : [.. multiple];
    }

    private IndexedStringResourceTable? GetOrLoadSourceTable(
        CultureInfo culture,
        SatelliteStringResourceSourceMetadata metadata)
    {
        _sourceTables ??= [with(StringComparer.Ordinal)];
        if (_sourceTables.TryGetValue(culture.Name, out IndexedStringResourceTable? table))
        {
            return table;
        }

        table = TryLoadSourceTable(culture, metadata);
        _sourceTables.Add(culture.Name, table);
        return table;
    }

    /// <summary>
    ///  Returns whether two names identify the same resource culture.
    /// </summary>
    /// <param name="cultureName">The requested culture name.</param>
    /// <param name="neutralCultureName">The declared neutral culture name.</param>
    /// <returns><see langword="true"/> when the names identify the same culture.</returns>
    internal static bool IsNeutralCulture(string cultureName, string? neutralCultureName) =>
        string.Equals(cultureName, neutralCultureName, StringComparison.OrdinalIgnoreCase);

    private SatelliteStringResourceSourceMetadata GetSourceMetadata()
    {
        if (_resourceAssemblySource is not null)
        {
            SatelliteStringResourceSourceMetadata externalMetadata =
                GetManagedAssemblySourceMetadata(_resourceAssemblySource);

            string satelliteAssemblyFileName = externalMetadata.SatelliteAssemblyFileName
                ?? throw new InvalidOperationException("The satellite assembly filename was not initialized.");

            ValidatePathSegment(satelliteAssemblyFileName, "resourceAssemblyFile");
            return externalMetadata;
        }

        SatelliteStringResourceSourceMetadata? metadata = _neutralSourceMetadata;
        if (metadata is not null)
        {
            return metadata;
        }

        Assembly? resourceAssembly = _resourceAssembly ?? SourceAssembly;
        if (resourceAssembly is null)
        {
            metadata = new(
                resourceAssemblyIdentity: null,
                satelliteAssemblyFileName: null,
                neutralCultureName: null,
                satelliteContractVersion: null);
        }
        else
        {
            metadata = s_satelliteAssemblies
                .GetOrCreateValue(resourceAssembly)
                .GetMetadata(
                    resourceAssembly,
                    includeContractVersion: _sourceKind != SatelliteStringResourceSourceKind.ResourcesDirectory);

            if (_resourceAssemblySimpleNameAlias is not null)
            {
                metadata = metadata.WithSimpleName(_resourceAssemblySimpleNameAlias);
            }

            if (_sourceKind == SatelliteStringResourceSourceKind.SatelliteDirectory
                && metadata.SatelliteAssemblyFileName is not null)
            {
                ValidatePathSegment(metadata.SatelliteAssemblyFileName, nameof(resourceAssembly));
            }
        }

        _neutralSourceMetadata = metadata;
        return metadata;
    }

    private IndexedStringResourceTable? TryLoadSourceTable(
        CultureInfo culture,
        SatelliteStringResourceSourceMetadata metadata)
    {
        return _sourceKind switch
        {
            SatelliteStringResourceSourceKind.RuntimeSatellites => LoadRuntimeSatellite(culture, metadata),
            SatelliteStringResourceSourceKind.ResourcesDirectory => LoadResourcesFile(culture),
            SatelliteStringResourceSourceKind.SatelliteDirectory => LoadSatelliteFile(culture, metadata),
            _ => throw new InvalidOperationException("The localized resource source is invalid.")
        };
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private IndexedStringResourceTable? LoadRuntimeSatellite(
        CultureInfo culture,
        SatelliteStringResourceSourceMetadata metadata)
    {
        Assembly resourceAssembly = _resourceAssembly
            ?? throw new InvalidOperationException("The resource assembly was not initialized.");

        SatelliteAssemblyCache assemblyCache = s_satelliteAssemblies.GetOrCreateValue(resourceAssembly);
        Assembly? satellite = assemblyCache.GetSatelliteAssembly(
            resourceAssembly,
            culture,
            metadata.SatelliteContractVersion);

        if (satellite is null)
        {
            return null;
        }

        string resourceName = $"{BaseName}.{culture.Name}.resources";
        return StringResourceTableLoader.LoadIndexedTableFromAssembly(
            satellite,
            resourceName,
            Options)
            ?? throw new MissingManifestResourceException(
                $"The satellite assembly '{satellite.FullName}' does not contain '{resourceName}'.");
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private IndexedStringResourceTable? LoadResourcesFile(CultureInfo culture)
    {
        string resourcesRoot = _localizedRoot
            ?? throw new InvalidOperationException("The resources directory was not initialized.");

        string resourcesPath = Path.Join(resourcesRoot, culture.Name, _resourceName);
        MappedMemoryManager resourcesFile;
        try
        {
            resourcesFile = _openFile(resourcesPath);
        }
        catch (Exception exception) when (exception is FileNotFoundException or DirectoryNotFoundException)
        {
            return null;
        }

        return StringResourceTableLoader.LoadIndexedTableFromResourcesFile(
            resourcesFile.Memory,
            Options,
            resourcesFile);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private IndexedStringResourceTable? LoadSatelliteFile(
        CultureInfo culture,
        SatelliteStringResourceSourceMetadata metadata)
    {
        string satelliteRoot = _localizedRoot
            ?? throw new InvalidOperationException("The satellite directory was not initialized.");

        string satelliteFileName = metadata.SatelliteAssemblyFileName
            ?? throw new InvalidOperationException("The satellite assembly filename was not initialized.");

        string satellitePath = Path.Join(satelliteRoot, culture.Name, satelliteFileName);
        try
        {
            MappedMemoryManager satelliteFile = _openFile(satellitePath);
            string resourceName = $"{BaseName}.{culture.Name}.resources";
            return StringResourceTableLoader.LoadIndexedTableFromSatelliteAssembly(
                satelliteFile.Memory,
                resourceName,
                Options,
                satelliteFile,
                metadata,
                culture.Name)
                ?? throw new MissingManifestResourceException(
                    $"The satellite assembly '{satellitePath}' does not contain '{resourceName}'.");
        }
        catch (Exception exception) when (exception is FileNotFoundException or DirectoryNotFoundException)
        {
            return null;
        }
        catch (Exception exception) when (
            _probeMode == SatelliteStringResourceProbeMode.FallbackOnFailure
                && IsToleratedSatelliteFailure(exception))
        {
            return null;
        }
    }

    private static void ValidatePathSegment(string value, string paramName)
    {
        if (value.Length == 0
            || value is "." or ".."
            || value.Contains('/')
            || value.Contains('\\'))
        {
            throw new ArgumentException("The value must be a single non-empty path segment.", paramName);
        }
    }

    private static bool IsToleratedSatelliteFailure(Exception exception) =>
        exception is ArgumentException
            or BadImageFormatException
            or IOException
            or MissingManifestResourceException
            or NotSupportedException
            or UnauthorizedAccessException
            or System.Security.SecurityException;
}