File: ExtensionFramework\Utilities\LazyExtension.cs
Web Access
Project: src\src\vstest\src\Microsoft.TestPlatform.Common\Microsoft.TestPlatform.Common.csproj (Microsoft.VisualStudio.TestPlatform.Common)
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Linq;

using Microsoft.VisualStudio.TestPlatform.Common.ExtensionDecorators;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
using Microsoft.VisualStudio.TestPlatform.Utilities;

namespace Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities;

/// <summary>
/// Class to hold a test extension type
/// </summary>
/// <typeparam name="TExtension">Test extension type</typeparam>
/// <typeparam name="TMetadata">Test extension metadata</typeparam>
public class LazyExtension<TExtension, TMetadata>
{
    private static readonly object Synclock = new();
    private readonly Type? _metadataType;
    private readonly Func<TExtension>? _extensionCreator;
    private readonly ExtensionDecoratorFactory _extensionDecoratorFactory = new(FeatureFlag.Instance);
    private TExtension? _extension;
    private TMetadata? _metadata;

    /// <summary>
    /// The constructor.
    /// </summary>
    /// <param name="instance">Test extension Instance</param>
    /// <param name="metadata">test extension metadata</param>
    public LazyExtension(TExtension instance, TMetadata metadata)
    {
        _extension = instance ?? throw new ArgumentNullException(nameof(instance));
        _metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
        IsExtensionCreated = true;
    }

    /// <summary>
    /// The constructor.
    /// </summary>
    /// <param name="pluginInfo">Test plugin to instantiated on demand.</param>
    /// <param name="metadataType">Metadata type to instantiate on demand</param>
    public LazyExtension(TestPluginInformation pluginInfo, Type metadataType)
    {
        TestPluginInfo = pluginInfo ?? throw new ArgumentNullException(nameof(pluginInfo));
        _metadataType = metadataType ?? throw new ArgumentNullException(nameof(metadataType));
        IsExtensionCreated = false;
    }

    /// <summary>
    /// The constructor.
    /// </summary>
    /// <param name="pluginInfo">Test plugin to instantiated on demand</param>
    /// <param name="metadata">Test extension metadata</param>
    public LazyExtension(TestPluginInformation pluginInfo, TMetadata metadata)
    {
        TestPluginInfo = pluginInfo ?? throw new ArgumentNullException(nameof(pluginInfo));
        _metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
        IsExtensionCreated = false;
    }

    /// <summary>
    /// Delegate Constructor
    /// </summary>
    /// <param name="creator">Test extension creator delegate</param>
    /// <param name="metadata">test extension metadata</param>
    public LazyExtension(Func<TExtension> creator, TMetadata metadata)
    {
        _extensionCreator = creator ?? throw new ArgumentNullException(nameof(creator));
        _metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
        IsExtensionCreated = false;
    }

    /// <summary>
    /// Gets a value indicating whether is extension created.
    /// </summary>
    internal bool IsExtensionCreated { get; private set; }

    internal TestPluginInformation? TestPluginInfo { get; }

    /// <summary>
    /// Gets the test extension instance.
    /// </summary>
    public TExtension Value
    {
        get
        {
            if (!IsExtensionCreated)
            {
                if (_extensionCreator != null)
                {
                    _extension = _extensionCreator();
                }
                else if (_extension == null)
                {
                    lock (Synclock)
                    {
                        if (_extension == null && TestPluginInfo != null)
                        {
                            TPDebug.Assert(TestPluginInfo.AssemblyQualifiedName is not null, "TestPluginInfo.AssemblyQualifiedName is null");
                            var pluginType = TestPluginManager.GetTestExtensionType(TestPluginInfo.AssemblyQualifiedName);
                            TPDebug.Assert(pluginType is not null, "pluginType is null");

                            // If the extension is a test executor we decorate the adapter to augment the test platform capabilities.
                            var extension = TestPluginManager.CreateTestExtension<TExtension>(pluginType);
                            if (typeof(ITestExecutor).IsAssignableFrom(typeof(TExtension)))
                            {
                                extension = (TExtension)_extensionDecoratorFactory.Decorate((ITestExecutor)extension!);
                            }

                            _extension = extension;
                        }
                    }
                }

                IsExtensionCreated = true;
            }

            TPDebug.Assert(_extension is not null, "_extension is null");
            return _extension;
        }
    }

    /// <summary>
    /// Gets the test extension metadata
    /// </summary>
    public TMetadata Metadata
    {
        get
        {
            if (_metadata == null)
            {
                lock (Synclock)
                {
                    if (_metadata == null && TestPluginInfo != null)
                    {
                        var parameters = TestPluginInfo.Metadata?.ToArray();
                        TPDebug.Assert(_metadataType is not null, "_metadataType is null");
                        var dataObject = Activator.CreateInstance(_metadataType, parameters);
                        TPDebug.Assert(dataObject is not null, "dataObject is null");
                        _metadata = (TMetadata)dataObject;
                    }
                }
            }

            TPDebug.Assert(_metadata is not null, "_metadata is null");
            return _metadata;
        }
    }

}