using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using Mono.Cecil; namespace Mono.Documentation.Updater.Frameworks { /// /// Represents a set of assemblies that we want to document /// public class AssemblySet : IDisposable { BaseAssemblyResolver resolver = new Frameworks.MDocResolver (); CachedResolver cachedResolver; IMetadataResolver metadataResolver; HashSet assemblyPaths = new HashSet (); Dictionary assemblyPathsMap = new Dictionary (); HashSet assemblySearchPaths = new HashSet (); HashSet forwardedTypes = new HashSet (); IEnumerable importPaths; public IEnumerable Importers { get; private set; } FrameworkEntry fx; public FrameworkEntry Framework { get => fx; set { fx = value; fx.AddAssemblySet (this); } } /// This is meant only for unit test access public IDictionary AssemblyMapsPath { get => assemblyPathsMap; } private IDictionary> forwardedTypesTo = new Dictionary> (); public AssemblySet (IEnumerable paths) : this ("Default", paths, new string[0]) { } public AssemblySet (string name, IEnumerable paths, IEnumerable resolverSearchPaths, IEnumerable imports = null, string version = null, string id = null) { cachedResolver = cachedResolver ?? new CachedResolver (resolver); metadataResolver = metadataResolver ?? new Frameworks.MDocMetadataResolver (cachedResolver); ((MDocResolver)resolver).TypeExported += (sender, e) => { TrackTypeExported (e); }; Name = name; Version = version; Id = id; foreach (var path in paths) { assemblyPaths.Add (path); string pathName = Path.GetFileName (path); if (!assemblyPathsMap.ContainsKey (pathName)) assemblyPathsMap.Add (pathName, true); } // add default search paths var assemblyDirectories = paths .Where (p => p.Contains (Path.DirectorySeparatorChar)) .Select (p => Path.GetDirectoryName (p)); foreach (var searchPath in assemblyDirectories.Union(resolverSearchPaths)) assemblySearchPaths.Add (searchPath); char oppositeSeparator = Path.DirectorySeparatorChar == '/' ? '\\' : '/'; Func sanitize = p => p.Replace (oppositeSeparator, Path.DirectorySeparatorChar); foreach (var searchPath in assemblySearchPaths.Select (sanitize)) resolver.AddSearchDirectory (searchPath); this.importPaths = imports; if (this.importPaths != null) { this.Importers = this.importPaths.Select (p => MDocUpdater.Instance.GetImporter (p, supportsEcmaDoc: false)).ToArray (); } else this.Importers = new DocumentationImporter[0]; } private void TrackTypeExported (MDocResolver.TypeForwardEventArgs e) { if (e.ForType == null) return; // keep track of types that have been exported for this assemblyset if (!forwardedTypesTo.ContainsKey (e.ForType)) { forwardedTypesTo.Add (e.ForType, new HashSet ()); } forwardedTypesTo[e.ForType].Add (e); } public string Name { get; private set; } public string Version { get; private set; } public string Id { get; private set; } IEnumerable assemblies; public IEnumerable Assemblies { get { if (this.assemblies == null) this.assemblies = this.LoadAllAssemblies ().Where (a => a != null).ToArray (); return this.assemblies; } } public IEnumerable AssemblyPaths { get { return this.assemblyPaths; } } /// Adds all subdirectories to the search directories for the resolver to look in. public void RecurseSearchDirectories () { var directories = resolver .GetSearchDirectories () .Select (d => new DirectoryInfo (d)) .Where (d => d.Exists) .Select (d => d.FullName) .Distinct () .ToDictionary (d => d, d => d); var subdirs = directories.Keys .SelectMany (d => Directory.GetDirectories (d, ".", SearchOption.AllDirectories)) .Where (d => !directories.ContainsKey (d)); foreach (var dir in subdirs) resolver.AddSearchDirectory (dir); } /// true, if in set was contained in the set of assemblies, false otherwise. /// An assembly file name public bool Contains (string name) { return assemblyPathsMap.ContainsKey (name);//assemblyPaths.Any (p => Path.GetFileName (p) == name); } /// Tells whether an already enumerated AssemblyDefinition, contains the type. /// Type name public bool ContainsForwardedType (string name) { return forwardedTypes.Contains (name); } /// /// Forwardeds the assemblies. /// /// The assemblies. /// Type. public IEnumerable FullAssemblyChain(TypeDefinition type) { if (forwardedTypesTo.ContainsKey (type.FullName)) { var list = forwardedTypesTo[type.FullName]; var assemblies = (new[] { type.Module.Assembly.Name }) .Union (list.Select (f => f.To)) .Union (list.Select (f => f.From)) .Distinct (anc); return assemblies; } else return new[] { type.Module.Assembly.Name }; } AssemblyNameComparer anc = new AssemblyNameComparer (); class AssemblyNameComparer : IEqualityComparer { public bool Equals (AssemblyNameReference x, AssemblyNameReference y) { return x.FullName.Equals (y.FullName, StringComparison.Ordinal); } public int GetHashCode (AssemblyNameReference obj) { return obj.FullName.GetHashCode (); } } public void Dispose () { this.assemblies = null; cachedResolver?.Dispose(); cachedResolver = null; } public override string ToString () { return string.Format ("[AssemblySet: Name={0}, Assemblies={1}]", Name, assemblyPaths.Count); } IEnumerable LoadAllAssemblies () { foreach (var path in this.assemblyPaths) { var assembly = MDocUpdater.Instance.LoadAssembly (path, metadataResolver, cachedResolver); if (assembly != null) { foreach (var type in assembly.MainModule.ExportedTypes.Where (t => t.IsForwarder).Cast()) { forwardedTypes.Add (type.FullName); TrackTypeExported (new MDocResolver.TypeForwardEventArgs (assembly.Name, (AssemblyNameReference)type.Scope, type?.FullName)); } } yield return assembly; } } } }