// 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 Microsoft.ClearScript.Util;
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 = type => true;
///
/// 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, "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, "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, "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, "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, "assembly");
foreach (var type in assembly.GetTypes().Where(type => type.IsImportable()))
{
AddType(type);
}
}
///
/// 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, "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, "assembly");
var activeFilter = filter ?? defaultFilter;
foreach (var type in assembly.GetTypes().Where(type => type.IsImportable() && activeFilter(type)))
{
AddType(type);
}
}
///
/// 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, "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, "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, "name");
PropertyBag namespaceNode = this;
var segments = name.Split('.');
foreach (var segment in segments)
{
object node;
if (!namespaceNode.TryGetValue(segment, out node))
{
return null;
}
namespaceNode = node as PropertyBag;
if (namespaceNode == null)
{
return null;
}
}
return namespaceNode;
}
private void AddType(HostType hostType)
{
MiscHelpers.VerifyNonNullArgument(hostType, "hostType");
foreach (var type in hostType.Types)
{
var namespaceNode = GetNamespaceNode(type);
if (namespaceNode != null)
{
AddTypeToNamespaceNode(namespaceNode, type);
}
}
}
private PropertyBag GetNamespaceNode(Type type)
{
var locator = type.GetLocator();
var segments = locator.Split('.');
if (segments.Length < 1)
{
return null;
}
PropertyBag namespaceNode = this;
foreach (var segment in segments.Take(segments.Length - 1))
{
PropertyBag innerNode;
object node;
if (!namespaceNode.TryGetValue(segment, out node))
{
innerNode = new PropertyBag(true);
namespaceNode.SetPropertyNoCheck(segment, innerNode);
}
else
{
innerNode = node as PropertyBag;
if (innerNode == 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)
{
object value;
var name = type.GetRootName();
if (!node.TryGetValue(name, out value))
{
node.SetPropertyNoCheck(name, HostType.Wrap(type));
return;
}
var hostType = value as HostType;
if (hostType != null)
{
var types = new[] { type }.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.ToIList();
return typeList.SingleOrDefault(type => type.IsPublic) ?? typeList[0];
}
}
}