-
Notifications
You must be signed in to change notification settings - Fork 160
Expand file tree
/
Copy pathHostTypeCollection.cs
More file actions
380 lines (331 loc) · 15.2 KB
/
HostTypeCollection.cs
File metadata and controls
380 lines (331 loc) · 15.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
// 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
{
/// <summary>
/// Represents a scriptable collection of host types.
/// </summary>
/// <remarks>
/// 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 <c>Acme.Gadgets.Button</c> host type. Use
/// <c><see cref="ScriptEngine.AddHostObject(string, object)">AddHostObject</see></c> to expose a host
/// type collection to script code.
/// </remarks>
public class HostTypeCollection : PropertyBag
{
private static readonly Predicate<Type> defaultFilter = _ => true;
private static readonly TypeComparer typeComparer = new();
/// <summary>
/// Initializes a new host type collection.
/// </summary>
public HostTypeCollection()
: base(true)
{
}
/// <summary>
/// Initializes a new host type collection with types from one or more assemblies.
/// </summary>
/// <param name="assemblies">The assemblies that contain the types with which to initialize the collection.</param>
public HostTypeCollection(params Assembly[] assemblies)
: base(true)
{
MiscHelpers.VerifyNonNullArgument(assemblies, nameof(assemblies));
Array.ForEach(assemblies, AddAssembly);
}
/// <summary>
/// Initializes a new host type collection with types from one or more assemblies. The
/// assemblies are specified by name.
/// </summary>
/// <param name="assemblyNames">The names of the assemblies that contain the types with which to initialize the collection.</param>
public HostTypeCollection(params string[] assemblyNames)
: base(true)
{
MiscHelpers.VerifyNonNullArgument(assemblyNames, nameof(assemblyNames));
Array.ForEach(assemblyNames, AddAssembly);
}
/// <summary>
/// Initializes a new host type collection with selected types from one or more assemblies.
/// </summary>
/// <param name="filter">A filter for selecting the types to add.</param>
/// <param name="assemblies">The assemblies that contain the types with which to initialize the collection.</param>
public HostTypeCollection(Predicate<Type> filter, params Assembly[] assemblies)
{
MiscHelpers.VerifyNonNullArgument(assemblies, nameof(assemblies));
Array.ForEach(assemblies, assembly => AddAssembly(assembly, filter));
}
/// <summary>
/// Initializes a new host type collection with selected types from one or more assemblies.
/// The assemblies are specified by name.
/// </summary>
/// <param name="filter">A filter for selecting the types to add.</param>
/// <param name="assemblyNames">The names of the assemblies that contain the types with which to initialize the collection.</param>
public HostTypeCollection(Predicate<Type> filter, params string[] assemblyNames)
{
MiscHelpers.VerifyNonNullArgument(assemblyNames, nameof(assemblyNames));
Array.ForEach(assemblyNames, assemblyName => AddAssembly(assemblyName, filter));
}
/// <summary>
/// Adds types from an assembly to a host type collection.
/// </summary>
/// <param name="assembly">The assembly that contains the types to add.</param>
public void AddAssembly(Assembly assembly)
{
MiscHelpers.VerifyNonNullArgument(assembly, nameof(assembly));
assembly.GetAllTypes().Where(type => type.IsImportable(null)).ForEach(AddType);
}
/// <summary>
/// Adds types from an assembly to a host type collection. The assembly is specified by name.
/// </summary>
/// <param name="assemblyName">The name of the assembly that contains the types to add.</param>
public void AddAssembly(string assemblyName)
{
MiscHelpers.VerifyNonBlankArgument(assemblyName, nameof(assemblyName), "Invalid assembly name");
AddAssembly(Assembly.Load(AssemblyTable.GetFullAssemblyName(assemblyName)));
}
/// <summary>
/// Adds selected types from an assembly to a host type collection.
/// </summary>
/// <param name="assembly">The assembly that contains the types to add.</param>
/// <param name="filter">A filter for selecting the types to add.</param>
public void AddAssembly(Assembly assembly, Predicate<Type> filter)
{
MiscHelpers.VerifyNonNullArgument(assembly, nameof(assembly));
var activeFilter = filter ?? defaultFilter;
assembly.GetAllTypes().Where(type => type.IsImportable(null) && activeFilter(type)).ForEach(AddType);
}
/// <summary>
/// Adds selected types from an assembly to a host type collection. The assembly is
/// specified by name.
/// </summary>
/// <param name="assemblyName">The name of the assembly that contains the types to add.</param>
/// <param name="filter">A filter for selecting the types to add.</param>
public void AddAssembly(string assemblyName, Predicate<Type> filter)
{
MiscHelpers.VerifyNonBlankArgument(assemblyName, nameof(assemblyName), "Invalid assembly name");
AddAssembly(Assembly.Load(AssemblyTable.GetFullAssemblyName(assemblyName)), filter);
}
/// <summary>
/// Adds a type to a host type collection.
/// </summary>
/// <param name="type">The type to add.</param>
public void AddType(Type type)
{
MiscHelpers.VerifyNonNullArgument(type, nameof(type));
AddType(HostType.Wrap(type));
}
/// <summary>
/// Adds a type to a host type collection. The type is specified by name.
/// </summary>
/// <param name="typeName">The fully qualified name of the type to add.</param>
/// <param name="typeArgs">Optional generic type arguments.</param>
public void AddType(string typeName, params Type[] typeArgs)
{
AddType(TypeHelpers.ImportType(typeName, null, false, typeArgs));
}
/// <summary>
/// Adds a type to a host type collection. The type is specified by type name and assembly name.
/// </summary>
/// <param name="typeName">The fully qualified name of the type to add.</param>
/// <param name="assemblyName">The name of the assembly that contains the type to add.</param>
/// <param name="typeArgs">Optional generic type arguments.</param>
public void AddType(string typeName, string assemblyName, params Type[] typeArgs)
{
AddType(TypeHelpers.ImportType(typeName, assemblyName, true, typeArgs));
}
/// <summary>
/// Locates a namespace within a host type collection.
/// </summary>
/// <param name="name">The full name of the namespace to locate.</param>
/// <returns>The node that represents the namespace if it was found, <c>null</c> otherwise.</returns>
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<Type> types)
{
var typeList = types.Distinct(typeComparer).ToIList();
return typeList.SingleOrDefault(type => type.IsPublic) ?? typeList[0];
}
#region Nested type : TypeComparer
private sealed class TypeComparer : EqualityComparer<Type>
{
public override bool Equals(Type x, Type y) => (x == y) || (x.AssemblyQualifiedName == y.AssemblyQualifiedName);
public override int GetHashCode(Type type) => type.AssemblyQualifiedName.GetHashCode();
}
#endregion
}
}