// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.InteropServices.ComTypes; using Microsoft.ClearScript.Util; using Microsoft.ClearScript.Util.COM; namespace Microsoft.ClearScript { ///

/// Represents a scriptable collection of host types. /// /// /// Host type collections provide convenient scriptable access to all the types defined in one /// or more host assemblies. They are hierarchical collections where leaf nodes represent types /// and parent nodes represent namespaces. For example, if an assembly contains a type named /// "Acme.Gadgets.Button", the corresponding collection will have a property named "Acme" whose /// value is an object with a property named "Gadgets" whose value is an object with a property /// named "Button" whose value represents the Acme.Gadgets.Button host type. Use /// AddHostObject to expose a host /// type collection to script code. /// public class HostTypeCollection : PropertyBag { private static readonly Predicate defaultFilter = _ => true; private static readonly TypeComparer typeComparer = new(); /// /// Initializes a new host type collection. /// public HostTypeCollection() : base(true) { } /// /// Initializes a new host type collection with types from one or more assemblies. /// /// The assemblies that contain the types with which to initialize the collection. public HostTypeCollection(params Assembly[] assemblies) : base(true) { MiscHelpers.VerifyNonNullArgument(assemblies, nameof(assemblies)); Array.ForEach(assemblies, AddAssembly); } /// /// Initializes a new host type collection with types from one or more assemblies. The /// assemblies are specified by name. /// /// The names of the assemblies that contain the types with which to initialize the collection. public HostTypeCollection(params string[] assemblyNames) : base(true) { MiscHelpers.VerifyNonNullArgument(assemblyNames, nameof(assemblyNames)); Array.ForEach(assemblyNames, AddAssembly); } /// /// Initializes a new host type collection with selected types from one or more assemblies. /// /// A filter for selecting the types to add. /// The assemblies that contain the types with which to initialize the collection. public HostTypeCollection(Predicate filter, params Assembly[] assemblies) { MiscHelpers.VerifyNonNullArgument(assemblies, nameof(assemblies)); Array.ForEach(assemblies, assembly => AddAssembly(assembly, filter)); } /// /// Initializes a new host type collection with selected types from one or more assemblies. /// The assemblies are specified by name. /// /// A filter for selecting the types to add. /// The names of the assemblies that contain the types with which to initialize the collection. public HostTypeCollection(Predicate filter, params string[] assemblyNames) { MiscHelpers.VerifyNonNullArgument(assemblyNames, nameof(assemblyNames)); Array.ForEach(assemblyNames, assemblyName => AddAssembly(assemblyName, filter)); } /// /// Adds types from an assembly to a host type collection. /// /// The assembly that contains the types to add. public void AddAssembly(Assembly assembly) { MiscHelpers.VerifyNonNullArgument(assembly, nameof(assembly)); assembly.GetAllTypes().Where(type => type.IsImportable(null)).ForEach(AddType); } /// /// Adds types from an assembly to a host type collection. The assembly is specified by name. /// /// The name of the assembly that contains the types to add. public void AddAssembly(string assemblyName) { MiscHelpers.VerifyNonBlankArgument(assemblyName, nameof(assemblyName), "Invalid assembly name"); AddAssembly(Assembly.Load(AssemblyTable.GetFullAssemblyName(assemblyName))); } /// /// Adds selected types from an assembly to a host type collection. /// /// The assembly that contains the types to add. /// A filter for selecting the types to add. public void AddAssembly(Assembly assembly, Predicate filter) { MiscHelpers.VerifyNonNullArgument(assembly, nameof(assembly)); var activeFilter = filter ?? defaultFilter; assembly.GetAllTypes().Where(type => type.IsImportable(null) && activeFilter(type)).ForEach(AddType); } /// /// Adds selected types from an assembly to a host type collection. The assembly is /// specified by name. /// /// The name of the assembly that contains the types to add. /// A filter for selecting the types to add. public void AddAssembly(string assemblyName, Predicate filter) { MiscHelpers.VerifyNonBlankArgument(assemblyName, nameof(assemblyName), "Invalid assembly name"); AddAssembly(Assembly.Load(AssemblyTable.GetFullAssemblyName(assemblyName)), filter); } /// /// Adds a type to a host type collection. /// /// The type to add. public void AddType(Type type) { MiscHelpers.VerifyNonNullArgument(type, nameof(type)); AddType(HostType.Wrap(type)); } /// /// Adds a type to a host type collection. The type is specified by name. /// /// The fully qualified name of the type to add. /// Optional generic type arguments. public void AddType(string typeName, params Type[] typeArgs) { AddType(TypeHelpers.ImportType(typeName, null, false, typeArgs)); } /// /// Adds a type to a host type collection. The type is specified by type name and assembly name. /// /// The fully qualified name of the type to add. /// The name of the assembly that contains the type to add. /// Optional generic type arguments. public void AddType(string typeName, string assemblyName, params Type[] typeArgs) { AddType(TypeHelpers.ImportType(typeName, assemblyName, true, typeArgs)); } /// /// Locates a namespace within a host type collection. /// /// The full name of the namespace to locate. /// The node that represents the namespace if it was found, null otherwise. public PropertyBag GetNamespaceNode(string name) { MiscHelpers.VerifyNonNullArgument(name, nameof(name)); PropertyBag namespaceNode = this; var segments = name.Split('.'); foreach (var segment in segments) { if (!namespaceNode.TryGetValue(segment, out var node)) { return null; } namespaceNode = node as PropertyBag; if (namespaceNode is null) { return null; } } return namespaceNode; } internal void AddEnumTypeInfo(ITypeInfo typeInfo) { AddEnumTypeInfoInternal(typeInfo); } private PropertyBag AddEnumTypeInfoInternal(ITypeInfo typeInfo) { using (var attrScope = typeInfo.CreateAttrScope()) { if (attrScope.Value.typekind == TYPEKIND.TKIND_ALIAS) { typeInfo.GetRefTypeInfo(unchecked((int)attrScope.Value.tdescAlias.lpValue.ToInt64()), out var refTypeInfo); var node = AddEnumTypeInfoInternal(refTypeInfo); if (node is not null) { var locator = typeInfo.GetManagedName(); var segments = locator.Split('.'); if (segments.Length > 0) { var namespaceNode = GetOrCreateNamespaceNode(locator); if (namespaceNode is not null) { namespaceNode.SetPropertyNoCheck(segments.Last(), node); return node; } } } } else if (attrScope.Value.typekind == TYPEKIND.TKIND_ENUM) { var node = GetOrCreateEnumTypeInfoNode(typeInfo); if (node is not null) { var count = attrScope.Value.cVars; for (var index = 0; index < count; index++) { using (var varDescScope = typeInfo.CreateVarDescScope(index)) { if (varDescScope.Value.varkind == VARKIND.VAR_CONST) { var name = typeInfo.GetMemberName(varDescScope.Value.memid); node.SetPropertyNoCheck(name, MiscHelpers.GetObjectForVariant(varDescScope.Value.desc.lpvarValue)); } } } return node; } } } return null; } private PropertyBag GetOrCreateEnumTypeInfoNode(ITypeInfo typeInfo) { var locator = typeInfo.GetManagedName(); var segments = locator.Split('.'); if (segments.Length < 1) { return null; } PropertyBag enumTypeInfoNode = this; foreach (var segment in segments) { PropertyBag innerNode; if (!enumTypeInfoNode.TryGetValue(segment, out var node)) { innerNode = new PropertyBag(true); enumTypeInfoNode.SetPropertyNoCheck(segment, innerNode); } else { innerNode = node as PropertyBag; if (innerNode is null) { throw new OperationCanceledException(MiscHelpers.FormatInvariant("Enumeration conflicts with '{0}' at '{1}'", node.GetFriendlyName(), locator)); } } enumTypeInfoNode = innerNode; } return enumTypeInfoNode; } private void AddType(HostType hostType) { MiscHelpers.VerifyNonNullArgument(hostType, nameof(hostType)); foreach (var type in hostType.Types) { var namespaceNode = GetOrCreateNamespaceNode(type); if (namespaceNode is not null) { AddTypeToNamespaceNode(namespaceNode, type); } } } private PropertyBag GetOrCreateNamespaceNode(Type type) { return GetOrCreateNamespaceNode(type.GetLocator()); } private PropertyBag GetOrCreateNamespaceNode(string locator) { var segments = locator.Split('.'); if (segments.Length < 1) { return null; } PropertyBag namespaceNode = this; foreach (var segment in segments.Take(segments.Length - 1)) { PropertyBag innerNode; if (!namespaceNode.TryGetValue(segment, out var node)) { innerNode = new PropertyBag(true); namespaceNode.SetPropertyNoCheck(segment, innerNode); } else { innerNode = node as PropertyBag; if (innerNode is null) { throw new OperationCanceledException(MiscHelpers.FormatInvariant("Namespace conflicts with '{0}' at '{1}'", node.GetFriendlyName(), locator)); } } namespaceNode = innerNode; } return namespaceNode; } private static void AddTypeToNamespaceNode(PropertyBag node, Type type) { var name = type.GetRootName(); if (!node.TryGetValue(name, out var value)) { node.SetPropertyNoCheck(name, HostType.Wrap(type)); return; } if (value is HostType hostType) { var types = type.ToEnumerable().Concat(hostType.Types).ToArray(); var groups = types.GroupBy(testType => testType.GetGenericParamCount()).ToIList(); if (groups.Any(group => group.Count() > 1)) { types = groups.Select(ResolveTypeConflict).ToArray(); } node.SetPropertyNoCheck(name, HostType.Wrap(types)); return; } if (value is PropertyBag) { throw new OperationCanceledException(MiscHelpers.FormatInvariant("Type conflicts with namespace at '{0}'", type.GetLocator())); } throw new OperationCanceledException(MiscHelpers.FormatInvariant("Type conflicts with '{0}' at '{1}'", value.GetFriendlyName(), type.GetLocator())); } private static Type ResolveTypeConflict(IEnumerable types) { var typeList = types.Distinct(typeComparer).ToIList(); return typeList.SingleOrDefault(type => type.IsPublic) ?? typeList[0]; } #region Nested type : TypeComparer private sealed class TypeComparer : EqualityComparer { public override bool Equals(Type x, Type y) => (x == y) || (x.AssemblyQualifiedName == y.AssemblyQualifiedName); public override int GetHashCode(Type type) => type.AssemblyQualifiedName.GetHashCode(); } #endregion } }