PageRenderTime 54ms CodeModel.GetById 18ms app.highlight 28ms RepoModel.GetById 1ms app.codeStats 0ms

/ILSpy/AssemblyList.cs

http://github.com/icsharpcode/ILSpy
C# | 302 lines | 210 code | 34 blank | 58 comment | 15 complexity | 9daac72f762f7fbe512f0bda186d3e7f MD5 | raw file
  1// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
  2// 
  3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4// software and associated documentation files (the "Software"), to deal in the Software
  5// without restriction, including without limitation the rights to use, copy, modify, merge,
  6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7// to whom the Software is furnished to do so, subject to the following conditions:
  8// 
  9// The above copyright notice and this permission notice shall be included in all copies or
 10// substantial portions of the Software.
 11// 
 12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
 15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 17// DEALINGS IN THE SOFTWARE.
 18
 19using System;
 20using System.Collections.Concurrent;
 21using System.Collections.Generic;
 22using System.Collections.ObjectModel;
 23using System.Collections.Specialized;
 24using System.IO;
 25using System.Linq;
 26using System.Threading.Tasks;
 27using System.Windows.Threading;
 28using System.Xml.Linq;
 29
 30namespace ICSharpCode.ILSpy
 31{
 32	/// <summary>
 33	/// A list of assemblies.
 34	/// </summary>
 35	public sealed class AssemblyList
 36	{
 37		readonly string listName;
 38		
 39		/// <summary>Dirty flag, used to mark modifications so that the list is saved later</summary>
 40		bool dirty;
 41
 42		internal readonly ConcurrentDictionary<(string assemblyName, bool isWinRT, string targetFrameworkIdentifier), LoadedAssembly> assemblyLookupCache = new ConcurrentDictionary<(string assemblyName, bool isWinRT, string targetFrameworkIdentifier), LoadedAssembly>();
 43		internal readonly ConcurrentDictionary<string, LoadedAssembly> moduleLookupCache = new ConcurrentDictionary<string, LoadedAssembly>();
 44
 45		/// <summary>
 46		/// The assemblies in this list.
 47		/// Needs locking for multi-threaded access!
 48		/// Write accesses are allowed on the GUI thread only (but still need locking!)
 49		/// </summary>
 50		/// <remarks>
 51		/// Technically read accesses need locking when done on non-GUI threads... but whenever possible, use the
 52		/// thread-safe <see cref="GetAssemblies()"/> method.
 53		/// </remarks>
 54		internal readonly ObservableCollection<LoadedAssembly> assemblies = new ObservableCollection<LoadedAssembly>();
 55		
 56		public AssemblyList(string listName)
 57		{
 58			this.listName = listName;
 59			assemblies.CollectionChanged += Assemblies_CollectionChanged;
 60		}
 61		
 62		/// <summary>
 63		/// Loads an assembly list from XML.
 64		/// </summary>
 65		public AssemblyList(XElement listElement)
 66			: this((string)listElement.Attribute("name"))
 67		{
 68			foreach (var asm in listElement.Elements("Assembly")) {
 69				OpenAssembly((string)asm);
 70			}
 71			this.dirty = false; // OpenAssembly() sets dirty, so reset it afterwards
 72		}
 73
 74		/// <summary>
 75		/// Creates a copy of an assembly list.
 76		/// </summary>
 77		public AssemblyList(AssemblyList list, string newName)
 78			: this(newName)
 79		{
 80			this.assemblies.AddRange(list.assemblies);
 81		}
 82		
 83		/// <summary>
 84		/// Gets the loaded assemblies. This method is thread-safe.
 85		/// </summary>
 86		public LoadedAssembly[] GetAssemblies()
 87		{
 88			lock (assemblies) {
 89				return assemblies.ToArray();
 90			}
 91		}
 92		
 93		/// <summary>
 94		/// Saves this assembly list to XML.
 95		/// </summary>
 96		internal XElement SaveAsXml()
 97		{
 98			return new XElement(
 99				"List",
100				new XAttribute("name", this.ListName),
101				assemblies.Where(asm => !asm.IsAutoLoaded).Select(asm => new XElement("Assembly", asm.FileName))
102			);
103		}
104		
105		/// <summary>
106		/// Gets the name of this list.
107		/// </summary>
108		public string ListName {
109			get { return listName; }
110		}
111		
112		void Assemblies_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
113		{
114			ClearCache();
115			// Whenever the assembly list is modified, mark it as dirty
116			// and enqueue a task that saves it once the UI has finished modifying the assembly list.
117			if (!dirty) {
118				dirty = true;
119				App.Current.Dispatcher.BeginInvoke(
120					DispatcherPriority.Background,
121					new Action(
122						delegate {
123							dirty = false;
124							AssemblyListManager.SaveList(this);
125							ClearCache();
126						})
127				);
128			}
129		}
130
131		internal void RefreshSave()
132		{
133			if (!dirty) {
134				dirty = true;
135				App.Current.Dispatcher.BeginInvoke(
136					DispatcherPriority.Background,
137					new Action(
138						delegate {
139							dirty = false;
140							AssemblyListManager.SaveList(this);
141						})
142				);
143			}
144		}
145		
146		internal void ClearCache()
147		{
148			assemblyLookupCache.Clear();
149			moduleLookupCache.Clear();
150		}
151
152		public LoadedAssembly Open(string assemblyUri, bool isAutoLoaded = false)
153		{
154			if (assemblyUri.StartsWith("nupkg://", StringComparison.OrdinalIgnoreCase)) {
155				string fileName = assemblyUri.Substring("nupkg://".Length);
156				int separator = fileName.LastIndexOf(';');
157				string componentName = null;
158				if (separator > -1) {
159					componentName = fileName.Substring(separator + 1);
160					fileName = fileName.Substring(0, separator);
161					LoadedNugetPackage package = new LoadedNugetPackage(fileName);
162					var entry = package.Entries.FirstOrDefault(e => e.Name == componentName);
163					if (entry != null) {
164						return OpenAssembly(assemblyUri, entry.Stream, true);
165					}
166				}
167				return null;
168			} else {
169				return OpenAssembly(assemblyUri, isAutoLoaded);
170			}
171		}
172
173		/// <summary>
174		/// Opens an assembly from disk.
175		/// Returns the existing assembly node if it is already loaded.
176		/// </summary>
177		public LoadedAssembly OpenAssembly(string file, bool isAutoLoaded = false)
178		{
179			App.Current.Dispatcher.VerifyAccess();
180			
181			file = Path.GetFullPath(file);
182			
183			foreach (LoadedAssembly asm in this.assemblies) {
184				if (file.Equals(asm.FileName, StringComparison.OrdinalIgnoreCase))
185					return asm;
186			}
187			
188			var newAsm = new LoadedAssembly(this, file);
189			newAsm.IsAutoLoaded = isAutoLoaded;
190			lock (assemblies) {
191				this.assemblies.Add(newAsm);
192			}
193			return newAsm;
194		}
195
196		/// <summary>
197		/// Opens an assembly from a stream.
198		/// </summary>
199		public LoadedAssembly OpenAssembly(string file, Stream stream, bool isAutoLoaded = false)
200		{
201			App.Current.Dispatcher.VerifyAccess();
202
203			foreach (LoadedAssembly asm in this.assemblies) {
204				if (file.Equals(asm.FileName, StringComparison.OrdinalIgnoreCase))
205					return asm;
206			}
207
208			var newAsm = new LoadedAssembly(this, file, stream);
209			newAsm.IsAutoLoaded = isAutoLoaded;
210			lock (assemblies) {
211				this.assemblies.Add(newAsm);
212			}
213			return newAsm;
214		}
215
216		/// <summary>
217		/// Replace the assembly object model from a crafted stream, without disk I/O
218		/// Returns null if it is not already loaded.
219		/// </summary>
220		public LoadedAssembly HotReplaceAssembly(string file, Stream stream)
221		{
222			App.Current.Dispatcher.VerifyAccess();
223			file = Path.GetFullPath(file);
224
225			var target = this.assemblies.FirstOrDefault(asm => file.Equals(asm.FileName, StringComparison.OrdinalIgnoreCase));
226			if (target == null)
227				return null;
228
229			var index = this.assemblies.IndexOf(target);
230			var newAsm = new LoadedAssembly(this, file, stream);
231			newAsm.IsAutoLoaded = target.IsAutoLoaded;
232			lock (assemblies) {
233				this.assemblies.Remove(target);
234				this.assemblies.Insert(index, newAsm);
235			}
236			return newAsm;
237		}
238
239		public LoadedAssembly ReloadAssembly(string file)
240		{
241			App.Current.Dispatcher.VerifyAccess();
242			file = Path.GetFullPath(file);
243
244			var target = this.assemblies.FirstOrDefault(asm => file.Equals(asm.FileName, StringComparison.OrdinalIgnoreCase));
245			if (target == null)
246				return null;
247
248			return ReloadAssembly(target);
249		}
250
251		public LoadedAssembly ReloadAssembly(LoadedAssembly target)
252		{
253			var index = this.assemblies.IndexOf(target);
254			var newAsm = new LoadedAssembly(this, target.FileName);
255			newAsm.IsAutoLoaded = target.IsAutoLoaded;
256			newAsm.PdbFileOverride = target.PdbFileOverride;
257			lock (assemblies) {
258				this.assemblies.Remove(target);
259				this.assemblies.Insert(index, newAsm);
260			}
261			return newAsm;
262		}
263
264		public void Unload(LoadedAssembly assembly)
265		{
266			App.Current.Dispatcher.VerifyAccess();
267			lock (assemblies) {
268				assemblies.Remove(assembly);
269			}
270			RequestGC();
271		}
272		
273		static bool gcRequested;
274		
275		void RequestGC()
276		{
277			if (gcRequested) return;
278			gcRequested = true;
279			App.Current.Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(
280				delegate {
281					gcRequested = false;
282					GC.Collect();
283				}));
284		}
285		
286		public void Sort(IComparer<LoadedAssembly> comparer)
287		{
288			Sort(0, int.MaxValue, comparer);
289		}
290		
291		public void Sort(int index, int count, IComparer<LoadedAssembly> comparer)
292		{
293			App.Current.Dispatcher.VerifyAccess();
294			lock (assemblies) {
295				List<LoadedAssembly> list = new List<LoadedAssembly>(assemblies);
296				list.Sort(index, Math.Min(count, list.Count - index), comparer);
297				assemblies.Clear();
298				assemblies.AddRange(list);
299			}
300		}
301	}
302}