// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.IO; using System.Linq; using System.Windows.Threading; using System.Xml.Linq; namespace ICSharpCode.ILSpy { /// /// A list of assemblies. /// public sealed class AssemblyList { readonly string listName; /// Dirty flag, used to mark modifications so that the list is saved later bool dirty; internal readonly ConcurrentDictionary assemblyLookupCache = new ConcurrentDictionary(); internal readonly ConcurrentDictionary winRTMetadataLookupCache = new ConcurrentDictionary(); /// /// The assemblies in this list. /// Needs locking for multi-threaded access! /// Write accesses are allowed on the GUI thread only (but still need locking!) /// /// /// Technically read accesses need locking when done on non-GUI threads... but whenever possible, use the /// thread-safe method. /// internal readonly ObservableCollection assemblies = new ObservableCollection(); public AssemblyList(string listName) { this.listName = listName; assemblies.CollectionChanged += Assemblies_CollectionChanged; } /// /// Loads an assembly list from XML. /// public AssemblyList(XElement listElement) : this((string)listElement.Attribute("name")) { foreach (var asm in listElement.Elements("Assembly")) { OpenAssembly((string)asm); } this.dirty = false; // OpenAssembly() sets dirty, so reset it afterwards } /// /// Gets the loaded assemblies. This method is thread-safe. /// public LoadedAssembly[] GetAssemblies() { lock (assemblies) { return assemblies.ToArray(); } } /// /// Saves this assembly list to XML. /// internal XElement SaveAsXml() { return new XElement( "List", new XAttribute("name", this.ListName), assemblies.Where(asm => !asm.IsAutoLoaded).Select(asm => new XElement("Assembly", asm.FileName)) ); } /// /// Gets the name of this list. /// public string ListName { get { return listName; } } void Assemblies_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { ClearCache(); // Whenever the assembly list is modified, mark it as dirty // and enqueue a task that saves it once the UI has finished modifying the assembly list. if (!dirty) { dirty = true; App.Current.Dispatcher.BeginInvoke( DispatcherPriority.Background, new Action( delegate { dirty = false; AssemblyListManager.SaveList(this); ClearCache(); }) ); } } internal void RefreshSave() { if (!dirty) { dirty = true; App.Current.Dispatcher.BeginInvoke( DispatcherPriority.Background, new Action( delegate { dirty = false; AssemblyListManager.SaveList(this); }) ); } } internal void ClearCache() { assemblyLookupCache.Clear(); winRTMetadataLookupCache.Clear(); } /// /// Opens an assembly from disk. /// Returns the existing assembly node if it is already loaded. /// public LoadedAssembly OpenAssembly(string file, bool isAutoLoaded=false) { App.Current.Dispatcher.VerifyAccess(); file = Path.GetFullPath(file); foreach (LoadedAssembly asm in this.assemblies) { if (file.Equals(asm.FileName, StringComparison.OrdinalIgnoreCase)) return asm; } var newAsm = new LoadedAssembly(this, file); newAsm.IsAutoLoaded = isAutoLoaded; lock (assemblies) { this.assemblies.Add(newAsm); } return newAsm; } /// /// Replace the assembly object model from a crafted stream, without disk I/O /// Returns null if it is not already loaded. /// public LoadedAssembly HotReplaceAssembly(string file, Stream stream) { App.Current.Dispatcher.VerifyAccess(); file = Path.GetFullPath(file); var target = this.assemblies.FirstOrDefault(asm => file.Equals(asm.FileName, StringComparison.OrdinalIgnoreCase)); if (target == null) return null; var index = this.assemblies.IndexOf(target); var newAsm = new LoadedAssembly(this, file, stream); newAsm.IsAutoLoaded = target.IsAutoLoaded; lock (assemblies) { this.assemblies.Remove(target); this.assemblies.Insert(index, newAsm); } return newAsm; } public LoadedAssembly ReloadAssembly(string file) { App.Current.Dispatcher.VerifyAccess(); file = Path.GetFullPath(file); var target = this.assemblies.FirstOrDefault(asm => file.Equals(asm.FileName, StringComparison.OrdinalIgnoreCase)); if (target == null) return null; var index = this.assemblies.IndexOf(target); var newAsm = new LoadedAssembly(this, file); newAsm.IsAutoLoaded = target.IsAutoLoaded; lock (assemblies) { this.assemblies.Remove(target); this.assemblies.Insert(index, newAsm); } return newAsm; } public void Unload(LoadedAssembly assembly) { App.Current.Dispatcher.VerifyAccess(); lock (assemblies) { assemblies.Remove(assembly); } RequestGC(); } static bool gcRequested; void RequestGC() { if (gcRequested) return; gcRequested = true; App.Current.Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action( delegate { gcRequested = false; GC.Collect(); })); } public void Sort(IComparer comparer) { Sort(0, int.MaxValue, comparer); } public void Sort(int index, int count, IComparer comparer) { App.Current.Dispatcher.VerifyAccess(); lock (assemblies) { List list = new List(assemblies); list.Sort(index, Math.Min(count, list.Count - index), comparer); assemblies.Clear(); assemblies.AddRange(list); } } } }