File: Resources\StringResourceManagerProvider.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.Reflection;
using System.Threading;

namespace Microsoft.DotNet.Cli.Resources;

/// <summary>
///  Selects the process-wide factory used by generated localized string resource accessors.
/// </summary>
/// <remarks>
///  <para>
///   Register a provider during application startup, before any generated localized resource
///   accessor initializes its manager. If no provider is registered before first use, the selection
///   is frozen to <see cref="SatelliteStringResourceManager.FromRuntimeSatellites(string, Assembly)"/>.
///  </para>
///  <para>
///   Provider selection is thread-safe and can happen only once. Each generated resource class
///   caches the resulting manager independently.
///  </para>
/// </remarks>
public static class StringResourceManagerProvider
{
    private static readonly Func<string, Assembly, StringResourceManager> s_runtimeSatelliteProvider =
        SatelliteStringResourceManager.FromRuntimeSatellites;
    private static Func<string, Assembly, StringResourceManager>? s_selectedProvider;

    /// <summary>
    ///  Registers the provider used by generated localized string resource accessors.
    /// </summary>
    /// <param name="provider">A factory that receives the base name and generated owner assembly.</param>
    /// <exception cref="ArgumentNullException"><paramref name="provider"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">
    ///  A provider or the default runtime-satellite behavior has already been selected.
    /// </exception>
    public static void Register(Func<string, Assembly, StringResourceManager> provider)
    {
        ArgumentNullException.ThrowIfNull(provider);
        if (Interlocked.CompareExchange(ref s_selectedProvider, provider, comparand: null) is not null)
        {
            throw new InvalidOperationException("A string resource manager provider has already been selected.");
        }
    }

    /// <summary>
    ///  Registers the platform resource manager adapter for resources embedded by the deployment
    ///  toolchain.
    /// </summary>
    /// <exception cref="InvalidOperationException">
    ///  A provider or the default runtime-satellite behavior has already been selected.
    /// </exception>
    public static void RegisterEmbedded() => Register(
        static (baseName, ownerAssembly) => new EmbeddedStringResourceManager(baseName, ownerAssembly));

    /// <summary>
    ///  Creates a manager using the registered provider or the runtime-satellite default.
    /// </summary>
    /// <param name="baseName">The root name of the resource table.</param>
    /// <param name="ownerAssembly">The generated accessor's owner assembly.</param>
    /// <returns>The created string resource manager.</returns>
    /// <exception cref="ArgumentNullException">
    ///  <paramref name="baseName"/> or <paramref name="ownerAssembly"/> is <see langword="null"/>.
    /// </exception>
    /// <exception cref="InvalidOperationException">The registered provider returns <see langword="null"/>.</exception>
    public static StringResourceManager Create(string baseName, Assembly ownerAssembly)
    {
        ArgumentNullException.ThrowIfNull(baseName);
        ArgumentNullException.ThrowIfNull(ownerAssembly);

        Func<string, Assembly, StringResourceManager> provider = Volatile.Read(ref s_selectedProvider)
            ?? Interlocked.CompareExchange(
                ref s_selectedProvider,
                s_runtimeSatelliteProvider,
                comparand: null)
            ?? s_runtimeSatelliteProvider;

        StringResourceManager? manager = provider(baseName, ownerAssembly);
        return manager
            ?? throw new InvalidOperationException("The string resource manager provider returned null.");
    }

    /// <summary>
    ///  Resets provider selection for isolated tests.
    /// </summary>
    internal static void ResetForTests() => Interlocked.Exchange(ref s_selectedProvider, value: null);
}