// // TypeSystemService.cs // // Author: // Mike Krüger // // Copyright (c) 2011 Mike Krüger // // 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.Generic; using System.Linq; using System.IO; using MonoDevelop.Projects; using ICSharpCode.NRefactory.TypeSystem.Implementation; using ICSharpCode.NRefactory.TypeSystem; using Mono.Addins; using MonoDevelop.Core; using MonoDevelop.Ide; using Mono.TextEditor; using System.Threading; using MonoDevelop.Core.ProgressMonitoring; using System.Xml; using ICSharpCode.NRefactory.Utils; using ICSharpCode.NRefactory; using System.Threading.Tasks; using ICSharpCode.NRefactory.Documentation; using ICSharpCode.NRefactory.CSharp; using MonoDevelop.Ide.Extensions; using MonoDevelop.Core.Assemblies; using System.Text; using ICSharpCode.NRefactory.Completion; using System.Diagnostics; using MonoDevelop.Projects.SharedAssetsProjects; using MonoDevelop.Ide.Templates; namespace MonoDevelop.Ide.TypeSystem { public static class TypeSystemServiceExt { /// /// Tries to the get source project for a given type definition. This operation may fall if it was called on an outdated /// compilation unit or the correspondening project was unloaded. /// /// true, if get source project was found, false otherwise. /// The type definition. /// The project or null if it wasn't found. public static bool TryGetSourceProject (this ITypeDefinition type, out Project project) { var location = type.Compilation.MainAssembly.UnresolvedAssembly.Location; if (string.IsNullOrEmpty (location)) { project = null; return false; } project = TypeSystemService.GetProject (location); return project != null; } /// /// Tries to the get source project for a given type. This operation may fall if it was called on an outdated /// compilation unit or the correspondening project was unloaded. /// /// true, if get source project was found, false otherwise. /// The type. /// The project or null if it wasn't found. public static bool TryGetSourceProject (this IType type, out Project project) { var def = type.GetDefinition (); if (def == null) { project = null; return false; } return def.TryGetSourceProject (out project); } internal static Project GetProjectWhereTypeIsDefined (this ITypeDefinition type) { var location = type.ParentAssembly.UnresolvedAssembly.Location; if (string.IsNullOrEmpty (location)) return null; return TypeSystemService.GetProject (location); } internal static Project GetProjectWhereTypeIsDefined (this IType type) { Project project; TryGetSourceProject (type, out project); return project; } public static TextLocation GetLocation (this IType type) { return type.GetDefinition ().Region.Begin; } public static bool IsBaseType (this IType type, IType potentialBase) { return type.GetAllBaseTypes ().Any (t => t.Equals (potentialBase)); } public static bool IsObsolete (this IEntity member) { return member != null && member.Attributes.Any (a => a.AttributeType.FullName == "System.ObsoleteAttribute"); } public static bool IsObsolete (this IEntity member, out string reason) { if (member == null) { reason = null; return false; } var attr = member.Attributes.FirstOrDefault (a => a.AttributeType.FullName == "System.ObsoleteAttribute"); if (attr == null) { reason = null; return false; } reason = attr.PositionalArguments.Count > 0 ? attr.PositionalArguments [0].ConstantValue.ToString () : null; return true; } public static IType Resolve (this IUnresolvedTypeDefinition def, Project project) { if (project == null) throw new ArgumentNullException ("project"); var compilation = TypeSystemService.GetCompilation (project); var ctx = new SimpleTypeResolveContext (compilation.MainAssembly); var resolvedType = def.Resolve (ctx); return resolvedType; } } /// /// The folding parser is used for generating a preliminary parsed document that does not /// contain a full dom - only some basic lexical constructs like comments or pre processor directives. /// /// This is useful for opening a document the first time to have some folding regions as start that are folded by default. /// Otherwise an irritating screen update will occur. /// public interface IFoldingParser { ParsedDocument Parse (string fileName, string content); } public static class TypeSystemService { const string CurrentVersion = "1.1.8"; static readonly List parsers; static string[] filesSkippedInParseThread = new string[0]; static IEnumerable Parsers { get { return parsers; } } public static void RemoveSkippedfile (FilePath fileName) { filesSkippedInParseThread = filesSkippedInParseThread.Where (f => f != fileName).ToArray (); } public static void AddSkippedFile (FilePath fileName) { if (filesSkippedInParseThread.Any (f => f == fileName)) return; filesSkippedInParseThread = filesSkippedInParseThread.Concat (new string[] { fileName }).ToArray (); } static TypeSystemService () { parsers = new List (); AddinManager.AddExtensionNodeHandler ("/MonoDevelop/TypeSystem/Parser", delegate (object sender, ExtensionNodeEventArgs args) { switch (args.Change) { case ExtensionChange.Add: parsers.Add ((TypeSystemParserNode)args.ExtensionNode); break; case ExtensionChange.Remove: parsers.Remove ((TypeSystemParserNode)args.ExtensionNode); break; } }); AddinManager.AddExtensionNodeHandler ("/MonoDevelop/TypeSystem/OutputTracking", delegate (object sender, ExtensionNodeEventArgs args) { var node = (TypeSystemOutputTrackingNode)args.ExtensionNode; switch (args.Change) { case ExtensionChange.Add: outputTrackedProjects.Add (node); break; case ExtensionChange.Remove: outputTrackedProjects.Remove (node); break; } }); FileService.FileChanged += delegate(object sender, FileEventArgs e) { if (!TrackFileChanges) return; foreach (var file in e) { // Open documents are handled by the Document class itself. if (IdeApp.Workbench != null && IdeApp.Workbench.GetDocument (file.FileName) != null) continue; // lock (projectWrapperUpdateLock) { foreach (var wrapper in projectContents.Values) { var projectFile = wrapper.Project.Files.GetFile (file.FileName); if (projectFile != null) QueueParseJob (wrapper, new [] { projectFile }); } UnresolvedAssemblyProxy ctx; if (cachedAssemblyContents.TryGetValue (file.FileName, out ctx)) CheckModifiedFile (ctx); } } foreach (var content in projectContents.Values.ToArray ()) { var files = new List (); foreach (var file in e) { var f = content.Project.GetProjectFile (file.FileName); if (f == null || f.BuildAction == BuildAction.None) continue; files.Add (f); } if (files.Count > 0) QueueParseJob (content, files); } }; if (IdeApp.ProjectOperations != null) { IdeApp.ProjectOperations.EndBuild += HandleEndBuild; } if (IdeApp.Workspace != null) { IdeApp.Workspace.ActiveConfigurationChanged += HandleActiveConfigurationChanged; } } static void HandleActiveConfigurationChanged (object sender, EventArgs e) { foreach (var pr in projectContents.ToArray ()) { var project = pr.Key as DotNetProject; if (project != null) CheckProjectOutput (project, true); pr.Value.referencesConnected = false; } } static readonly List outputTrackedProjects = new List (); static bool IsOutputTracked (DotNetProject project) { foreach (var projectType in project.GetProjectTypes ()) { if (outputTrackedProjects.Any (otp => otp.ProjectType != null && string.Equals (otp.ProjectType, projectType, StringComparison.OrdinalIgnoreCase))) { return true; } } return outputTrackedProjects.Any (otp => otp.LanguageName != null && string.Equals (otp.LanguageName, project.LanguageName, StringComparison.OrdinalIgnoreCase)); } static void CheckProjectOutput (DotNetProject project, bool autoUpdate) { if (project == null) throw new ArgumentNullException ("project"); if (IsOutputTracked (project)) { var fileName = project.GetOutputFileName (IdeApp.Workspace.ActiveConfiguration); var wrapper = GetProjectContentWrapper (project); if (wrapper == null) return; bool update = wrapper.UpdateTrackedOutputAssembly (fileName); if (autoUpdate && update) { wrapper.ReconnectAssemblyReferences (); // update documents foreach (var openDocument in IdeApp.Workbench.Documents) { openDocument.ReparseDocument (); } } } } static void HandleEndBuild (object sender, BuildEventArgs args) { var project = args.SolutionItem as DotNetProject; if (project == null) return; CheckProjectOutput (project, true); } public static TypeSystemParser GetParser (string mimeType, string buildAction = BuildAction.Compile) { var n = GetTypeSystemParserNode (mimeType, buildAction); return n != null ? n.Parser : null; } static TypeSystemParserNode GetTypeSystemParserNode (string mimeType, string buildAction) { foreach (var mt in DesktopService.GetMimeTypeInheritanceChain (mimeType)) { var provider = Parsers.FirstOrDefault (p => p.CanParse (mt, buildAction)); if (provider != null) return provider; } return null; } static List foldingParsers; static IEnumerable FoldingParsers { get { if (foldingParsers == null) { foldingParsers = new List (); AddinManager.AddExtensionNodeHandler ("/MonoDevelop/TypeSystem/FoldingParser", delegate (object sender, ExtensionNodeEventArgs args) { switch (args.Change) { case ExtensionChange.Add: foldingParsers.Add ((MimeTypeExtensionNode)args.ExtensionNode); break; case ExtensionChange.Remove: foldingParsers.Remove ((MimeTypeExtensionNode)args.ExtensionNode); break; } }); } return foldingParsers; } } public static IFoldingParser GetFoldingParser (string mimeType) { foreach (var mt in DesktopService.GetMimeTypeInheritanceChain (mimeType)) { var node = FoldingParsers.FirstOrDefault (n => n.MimeType == mt); if (node != null) return node.CreateInstance () as IFoldingParser; } return null; } public static ParsedDocument ParseFile (Project project, string fileName) { string text; try { if (!File.Exists (fileName)) return null; text = Mono.TextEditor.Utils.TextFileUtility.ReadAllText (fileName); } catch (Exception) { return null; } return ParseFile (project, fileName, DesktopService.GetMimeTypeForUri (fileName), text); } static readonly object projectWrapperUpdateLock = new object (); public static ParsedDocument ParseFile (Project project, string fileName, string mimeType, string content) { if (fileName == null) throw new ArgumentNullException ("fileName"); var parser = GetParser (mimeType); if (parser == null) return null; var t = Counters.ParserService.FileParsed.BeginTiming (fileName); try { var result = parser.Parse (true, fileName, new StringReader (content), project); lock (projectWrapperUpdateLock) { ProjectContentWrapper wrapper; if (project != null) { projectContents.TryGetValue (project, out wrapper); } else { wrapper = null; } if (wrapper != null && (result.Flags & ParsedDocumentFlags.NonSerializable) != ParsedDocumentFlags.NonSerializable) { var oldFile = wrapper._content.GetFile (fileName); wrapper.UpdateContent (c => c.AddOrUpdateFiles (result.ParsedFile)); UpdateProjectCommentTasks (wrapper, result); if (oldFile != null) wrapper.InformFileRemoved (new ParsedFileEventArgs (oldFile)); wrapper.InformFileAdded (new ParsedFileEventArgs (result.ParsedFile)); } // The parsed file could be included in other projects as well, therefore // they need to be updated. foreach (var cnt in projectContents.ToArray ()) { if (cnt.Key == project) continue; // Use the project context because file lookup is faster there than in the project class. var pcnt = cnt.Value; var file = pcnt._content.GetFile (fileName); if (file != null) { var newResult = parser.Parse (false, fileName, new StringReader (content), pcnt.Project); if ((newResult.Flags & ParsedDocumentFlags.NonSerializable) != ParsedDocumentFlags.NonSerializable) { pcnt.UpdateContent (c => c.AddOrUpdateFiles (newResult.ParsedFile)); pcnt.InformFileRemoved (new ParsedFileEventArgs (file)); pcnt.InformFileAdded (new ParsedFileEventArgs (newResult.ParsedFile)); } } } } return result; } catch (Exception e) { LoggingService.LogError ("Exception while parsing: " + e); return null; } finally { t.Dispose (); } } public static ParsedDocument ParseFile (Project project, string fileName, string mimeType, TextReader content) { return ParseFile (project, fileName, mimeType, content.ReadToEnd ()); } public static ParsedDocument ParseFile (Project project, TextEditorData data) { return ParseFile (project, data.FileName, data.MimeType, data.Text); } public static ParsedDocument ParseFile (string fileName, string mimeType, string text, ProjectContentWrapper wrapper = null) { using (var reader = new StringReader (text)) return ParseFile (fileName, mimeType, reader, wrapper); } public static ParsedDocument ParseFile (string fileName, string mimeType, TextReader content, ProjectContentWrapper wrapper = null) { var parser = GetParser (mimeType); if (parser == null) return null; var t = Counters.ParserService.FileParsed.BeginTiming (fileName); try { var result = parser.Parse (true, fileName, content); lock (projectWrapperUpdateLock) { if (wrapper != null && (result.Flags & ParsedDocumentFlags.NonSerializable) != ParsedDocumentFlags.NonSerializable) { var oldFile = wrapper._content.GetFile (fileName); wrapper.UpdateContent (c => c.AddOrUpdateFiles (result.ParsedFile)); UpdateProjectCommentTasks (wrapper, result); if (oldFile != null) wrapper.InformFileRemoved (new ParsedFileEventArgs (oldFile)); wrapper.InformFileAdded (new ParsedFileEventArgs (result.ParsedFile)); } } return result; } catch (Exception e) { LoggingService.LogError ("Exception while parsing :" + e); return null; } finally { t.Dispose (); } } public static event EventHandler ParseOperationStarted; internal static void StartParseOperation () { if ((parseStatus++) == 0) { if (ParseOperationStarted != null) ParseOperationStarted (null, EventArgs.Empty); } } public static event EventHandler ParseOperationFinished; internal static void EndParseOperation () { if (parseStatus == 0) return; if (--parseStatus == 0) { if (ParseOperationFinished != null) ParseOperationFinished (null, EventArgs.Empty); } } #region Parser Database Handling static string GetCacheDirectory (TargetFramework framework) { var derivedDataPath = UserProfile.Current.CacheDir.Combine ("DerivedData"); var name = new StringBuilder (); foreach (var ch in framework.Name) { if (char.IsLetterOrDigit (ch)) { name.Append (ch); } else { name.Append ('_'); } } string result = derivedDataPath.Combine (name.ToString ()); try { if (!Directory.Exists (result)) Directory.CreateDirectory (result); } catch (Exception e) { LoggingService.LogError ("Error while creating derived data directories.", e); } return result; } static string InternalGetCacheDirectory (FilePath filename) { CanonicalizePath (ref filename); var assemblyCacheRoot = GetAssemblyCacheRoot (filename); try { if (!Directory.Exists (assemblyCacheRoot)) return null; foreach (var dir in Directory.EnumerateDirectories (assemblyCacheRoot)) { string result; if (CheckCacheDirectoryIsCorrect (filename, dir, out result)) return result; } } catch (Exception e) { LoggingService.LogError ("Error while getting derived data directories.", e); } return null; } /// /// Gets the cache directory for a projects derived data cache directory. /// If forceCreation is set to false the method may return null, if the cache doesn't exist. /// /// The cache directory. /// The project to get the cache for. /// If set to true the creation is forced and the method doesn't return null. public static string GetCacheDirectory (Project project, bool forceCreation = false) { if (project == null) throw new ArgumentNullException ("project"); return GetCacheDirectory (project.FileName, forceCreation); } static readonly Dictionary cacheLocker = new Dictionary (); /// /// Gets the cache directory for arbitrary file names. /// If forceCreation is set to false the method may return null, if the cache doesn't exist. /// /// The cache directory. /// The file name to get the cache for. /// If set to true the creation is forced and the method doesn't return null. public static string GetCacheDirectory (string fileName, bool forceCreation = false) { if (fileName == null) throw new ArgumentNullException ("fileName"); object locker; bool newLock; lock (cacheLocker) { if (!cacheLocker.TryGetValue (fileName, out locker)) { cacheLocker [fileName] = locker = new object (); newLock = true; } else { newLock = false; } } lock (locker) { var result = InternalGetCacheDirectory (fileName); if (newLock && result != null) TouchCache (result); if (forceCreation && result == null) result = CreateCacheDirectory (fileName); return result; } } struct CacheDirectoryInfo { public static readonly CacheDirectoryInfo Empty = new CacheDirectoryInfo (); public string FileName { get; set; } public string Version { get; set; } } static readonly Dictionary cacheDirectoryCache = new Dictionary (); static void CanonicalizePath (ref FilePath fileName) { try { // There are some situations where that may cause an exception. fileName = fileName.CanonicalPath; } catch (Exception) { // Fallback string fp = fileName; if (fp.Length > 0 && fp [fp.Length - 1] == Path.DirectorySeparatorChar) fileName = fp.TrimEnd (Path.DirectorySeparatorChar); if (fp.Length > 0 && fp [fp.Length - 1] == Path.AltDirectorySeparatorChar) fileName = fp.TrimEnd (Path.AltDirectorySeparatorChar); } } static bool CheckCacheDirectoryIsCorrect (FilePath filename, FilePath candidate, out string result) { CanonicalizePath (ref filename); CanonicalizePath (ref candidate); lock (cacheDirectoryCache) { CacheDirectoryInfo info; if (!cacheDirectoryCache.TryGetValue (candidate, out info)) { var dataPath = candidate.Combine ("data.xml"); try { if (!File.Exists (dataPath)) { result = null; return false; } using (var reader = XmlReader.Create (dataPath)) { while (reader.Read ()) { if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "File") { info.Version = reader.GetAttribute ("version"); info.FileName = reader.GetAttribute ("name"); } } } cacheDirectoryCache [candidate] = info; } catch (Exception e) { LoggingService.LogError ("Error while reading derived data file " + dataPath, e); } } if (info.Version == CurrentVersion && info.FileName == filename) { result = candidate; return true; } result = null; return false; } } static string GetAssemblyCacheRoot (string filename) { string derivedDataPath = UserProfile.Current.CacheDir.Combine ("DerivedData"); string name = Path.GetFileName (filename); return Path.Combine (derivedDataPath, name + "-" + GetStableHashCode(name).ToString ("x")); } /// /// Retrieves a hash code for the specified string that is stable across /// .NET upgrades. /// /// Use this method instead of the normal string.GetHashCode if the hash code /// is persisted to disk. /// static int GetStableHashCode(string text) { unchecked { int h = 0; foreach (char c in text) { h = (h << 5) - h + c; } return h; } } static IEnumerable GetPossibleCacheDirNames (string baseName) { int i = 0; while (i < 4096) { yield return Path.Combine (baseName, i.ToString ()); i++; } throw new Exception ("Too many cache directories"); } static string CreateCacheDirectory (FilePath fileName) { CanonicalizePath (ref fileName); try { string cacheRoot = GetAssemblyCacheRoot (fileName); string cacheDir = GetPossibleCacheDirNames (cacheRoot).First (d => !Directory.Exists (d)); Directory.CreateDirectory (cacheDir); File.WriteAllText ( Path.Combine (cacheDir, "data.xml"), string.Format ("", fileName, CurrentVersion) ); return cacheDir; } catch (Exception e) { LoggingService.LogError ("Error creating cache for " + fileName, e); return null; } } static readonly FastSerializer sharedSerializer = new FastSerializer (); static T DeserializeObject (string path) where T : class { var t = Counters.ParserService.ObjectDeserialized.BeginTiming (path); try { using (var fs = new FileStream (path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan)) { using (var reader = new BinaryReaderWith7BitEncodedInts (fs)) { lock (sharedSerializer) { return (T)sharedSerializer.Deserialize (reader); } } } } catch (Exception e) { LoggingService.LogError ("Error while trying to deserialize " + typeof(T).FullName + ". stack trace:" + Environment.StackTrace, e); return default(T); } finally { t.Dispose (); } } static void SerializeObject (string path, object obj) { if (obj == null) throw new ArgumentNullException ("obj"); var t = Counters.ParserService.ObjectSerialized.BeginTiming (path); try { using (var fs = new FileStream (path, FileMode.Create, FileAccess.Write)) { using (var writer = new BinaryWriterWith7BitEncodedInts (fs)) { lock (sharedSerializer) { sharedSerializer.Serialize (writer, obj); } } } } catch (Exception e) { Console.WriteLine ("-----------------Serialize stack trace:"); Console.WriteLine (Environment.StackTrace); LoggingService.LogError ("Error while writing type system cache. (object:" + obj.GetType () + ")", e); } finally { t.Dispose (); } } /// /// Removes all cache directories which are older than 30 days. /// static void CleanupCache () { string derivedDataPath = UserProfile.Current.CacheDir.Combine ("DerivedData"); string[] subDirs; try { if (!Directory.Exists (derivedDataPath)) return; subDirs = Directory.GetDirectories (derivedDataPath); } catch (Exception e) { LoggingService.LogError ("Error while getting derived data directories.", e); return; } foreach (var subDir in subDirs) { try { var days = Math.Abs ((DateTime.Now - Directory.GetLastWriteTime (subDir)).TotalDays); if (days > 30) Directory.Delete (subDir, true); } catch (Exception e) { LoggingService.LogError ("Error while removing outdated cache " + subDir, e); } } } static void RemoveCache (string cacheDir) { try { Directory.Delete (cacheDir, true); } catch (Exception e) { LoggingService.LogError ("Error while removing cache " + cacheDir, e); } } static void TouchCache (string cacheDir) { try { Directory.SetLastWriteTime (cacheDir, DateTime.Now); } catch (Exception e) { LoggingService.LogError ("Error while touching cache directory " + cacheDir, e); } } static void StoreExtensionObject (string cacheDir, object extensionObject) { if (cacheDir == null) throw new ArgumentNullException ("cacheDir"); if (extensionObject == null) throw new ArgumentNullException ("extensionObject"); var fileName = Path.GetTempFileName (); SerializeObject (fileName, extensionObject); var cacheFile = Path.Combine (cacheDir, extensionObject.GetType ().FullName + ".cache"); try { if (File.Exists (cacheFile)) File.Delete (cacheFile); File.Move (fileName, cacheFile); } catch (Exception e) { LoggingService.LogError ("Error whil saving cache " + cacheFile + " for extension object:" + extensionObject, e); } } static void StoreProjectCache (Project project, ProjectContentWrapper wrapper) { if (!wrapper.WasChanged) return; string cacheDir = GetCacheDirectory (project, true); string fileName = Path.GetTempFileName (); SerializeObject (fileName, wrapper.Content.RemoveAssemblyReferences (wrapper.Content.AssemblyReferences)); string cacheFile = Path.Combine (cacheDir, "completion.cache"); try { if (File.Exists (cacheFile)) File.Delete (cacheFile); File.Move (fileName, cacheFile); } catch (Exception e) { LoggingService.LogError ("Error whil saving cache " + cacheFile, e); } foreach (var extensionObject in wrapper.ExtensionObjects) { StoreExtensionObject (cacheDir, extensionObject); } } #endregion #region Project loading public static void Load (WorkspaceItem item) { using (Counters.ParserService.WorkspaceItemLoaded.BeginTiming ()) { InternalLoad (item); CleanupCache (); } } static CancellationTokenSource loadCancellationSource = new CancellationTokenSource (); static bool loadWs = false; static void InternalLoad (WorkspaceItem item) { var ws = item as Workspace; if (ws != null) { loadWs = true; loadCancellationSource.Cancel (); loadCancellationSource = new CancellationTokenSource (); foreach (WorkspaceItem it in ws.Items) InternalLoad (it); ws.ItemAdded += OnWorkspaceItemAdded; ws.ItemRemoved += OnWorkspaceItemRemoved; } else { if (!loadWs) { loadCancellationSource.Cancel (); loadCancellationSource = new CancellationTokenSource (); } var solution = item as Solution; if (solution != null) { Parallel.ForEach (solution.GetAllProjects (), project => LoadProject (project)); var list = projectContents.Values.ToList (); Task.Factory.StartNew (delegate { foreach (var wrapper in list) { CheckModifiedFiles (wrapper.Project, wrapper.Project.Files.ToArray (), wrapper, loadCancellationSource.Token); } }); solution.SolutionItemAdded += OnSolutionItemAdded; solution.SolutionItemRemoved += OnSolutionItemRemoved; OnSolutionLoaded (new SolutionEventArgs (solution)); } } } public static event EventHandler SolutionLoaded; static void OnSolutionLoaded (SolutionEventArgs e) { var handler = SolutionLoaded; if (handler != null) handler (null, e); } [Serializable] public class UnresolvedAssemblyDecorator : IUnresolvedAssembly { readonly ProjectContentWrapper wrapper; IUnresolvedAssembly assembly { get { if (wrapper.OutputAssembly != null) return wrapper.OutputAssembly; return wrapper.Compilation.MainAssembly.UnresolvedAssembly; } } public UnresolvedAssemblyDecorator (ProjectContentWrapper wrapper) { this.wrapper = wrapper; } #region IUnresolvedAssembly implementation public string AssemblyName { get { return assembly.AssemblyName; } } public string FullAssemblyName { get { return assembly.FullAssemblyName; } } public string Location { get { return assembly.Location; } } public IEnumerable AssemblyAttributes { get { return assembly.AssemblyAttributes; } } public IEnumerable ModuleAttributes { get { return assembly.ModuleAttributes; } } public IEnumerable TopLevelTypeDefinitions { get { return assembly.TopLevelTypeDefinitions; } } #endregion #region IAssemblyReference implementation public IAssembly Resolve (ITypeResolveContext context) { return assembly.Resolve (context); } #endregion } [Serializable] public class ProjectContentWrapper { readonly Dictionary extensionObjects = new Dictionary (); List referencedWrappers = new List(); List referencedAssemblies = new List(); internal IProjectContent _content; internal bool referencesConnected; /* static bool GetReferencesConnected (ProjectContentWrapper pcw, HashSet wrapper) { if (wrapper.Contains (pcw)) return true; wrapper.Add (pcw); return pcw.referencesConnected && pcw.referencedWrappers.All (w => GetReferencesConnected (w, wrapper)); }*/ public IProjectContent Content { get { if (!referencesConnected) { EnsureReferencesAreLoaded (); } return _content; } private set { if (value == null) throw new InvalidOperationException ("Project content can't be null"); _content = value; } } /// /// Gets the extension objects attached to the content wrapper. /// public IEnumerable ExtensionObjects { get { return extensionObjects.Values; } } /// /// Updates an extension object for the wrapper. Note that only one extension object of a certain /// type may be stored inside the project content wrapper. /// /// The extension objects need to be serializable and are stored in the project cache on project unload. /// public void UpdateExtensionObject (object ext) { if (ext == null) throw new ArgumentNullException ("ext"); extensionObjects [ext.GetType ()] = ext; } /// /// Gets a specific extension object. This may lazy load an existing extension object from disk, /// if called the first time and a serialized extension object exists. /// /// /// The extension object. Or null, if no extension object of the specified type was registered. /// /// /// The type of the extension object. /// public T GetExtensionObject () where T : class { object result; if (extensionObjects.TryGetValue (typeof(T), out result)) return (T)result; string cacheDir = GetCacheDirectory (Project); if (cacheDir == null) return default(T); try { string fileName = Path.Combine (cacheDir, typeof(T).FullName + ".cache"); if (File.Exists (fileName)) { var deserialized = DeserializeObject (fileName); extensionObjects [typeof(T)] = deserialized; return deserialized; } } catch (Exception) { Console.WriteLine ("Can't deserialize :" + typeof(T).FullName); } return default (T); } List> loadActions = new List> (); public void RunWhenLoaded (Action act) { lock (updateContentLock) { var lazyProjectLoader = _content as LazyProjectLoader; if (loadActions != null) { lock (loadActions) { if (lazyProjectLoader != null && !lazyProjectLoader.ContextTask.IsCompleted) { loadActions.Add (act); return; } } } } act (Content); } void ClearCachedCompilations () { // Need to clear this compilation & all compilations that reference this directly or indirectly var stack = new Stack (); stack.Push (this); var cleared = new HashSet (); while (stack.Count > 0) { var cur = stack.Pop (); if (cleared.Contains (cur)) continue; cleared.Add (cur); cur.compilation = null; foreach (var project in cur.ReferencedProjects) { var projectContentWrapper = GetProjectContentWrapper (project); if (projectContentWrapper != null) stack.Push (projectContentWrapper); } } } readonly object updateContentLock = new object (); void RunLoadActions () { if (loadActions == null) return; Action[] actions; lock (loadActions) { actions = loadActions.ToArray (); loadActions = null; } foreach (var action in actions) action (Content); } public void UpdateContent (Func updateFunc) { LazyProjectLoader lazyProjectLoader; lock (updateContentLock) { lazyProjectLoader = _content as LazyProjectLoader; if (lazyProjectLoader != null) { lazyProjectLoader.ContextTask.Wait (); } _content = updateFunc (_content); ClearCachedCompilations (); WasChanged = true; if (lazyProjectLoader != null && !(_content is LazyProjectLoader)) { RunLoadActions (); } } } public void InformFileRemoved (ParsedFileEventArgs e) { var handler = FileRemoved; if (handler != null) handler (this, e); } public void InformFileAdded (ParsedFileEventArgs e) { var handler = FileAdded; if (handler != null) handler (this, e); } public EventHandler FileAdded; public EventHandler FileRemoved; public bool WasChanged; [NonSerialized] ICompilation compilation; object compilationContentLock = new object (); public ICompilation Compilation { get { lock (compilationContentLock) { if (compilation == null) { compilation = Content.CreateCompilation (); } return compilation; } } } public Project Project { get; private set; } [NonSerialized] int loadOperationDepth = 0; [NonSerialized] readonly object loadOperationLocker = new object (); internal void BeginLoadOperation () { lock (loadOperationLocker) { loadOperationDepth++; if (loadOperationDepth == 1) UpdateLoadState (); } } internal void EndLoadOperation () { lock (loadOperationLocker) { if (loadOperationDepth > 0) { loadOperationDepth--; } if (loadOperationDepth == 0) UpdateLoadState (); } } bool isLoaded; public bool IsLoaded { get { return isLoaded; } } [NonSerialized] CancellationTokenSource src; internal void CancelLoad () { if (src != null) src.Cancel (); } void UpdateLoadState () { bool wasLoaded = isLoaded; isLoaded = loadOperationDepth == 0 && referencesConnected && !referencedAssemblies.Any (a => a.InLoad); if (isLoaded && !wasLoaded) OnLoad (EventArgs.Empty); } internal void RequestLoad () { BeginLoadOperation (); EnsureReferencesAreLoaded (); CancelLoad (); src = new CancellationTokenSource (); var token = src.Token; //Task.Factory.StartNew (delegate { try { foreach (var asm in referencedAssemblies.ToArray ()) { if (token.IsCancellationRequested) break; var ctxLoader = asm.CtxLoader; if (ctxLoader != null) ctxLoader.EnsureAssemblyLoaded (); } } finally { EndLoadOperation (); } //}); } public event EventHandler Loaded; protected virtual void OnLoad (EventArgs e) { var handler = Loaded; if (handler != null) handler (this, e); } [NonSerialized] internal LazyAssemblyLoader OutputAssembly; internal bool UpdateTrackedOutputAssembly (FilePath fileName) { if (File.Exists (fileName)) { OutputAssembly = new LazyAssemblyLoader (fileName, null); // a clean operation could remove the assembly, thefore we need to load it. OutputAssembly.EnsureAssemblyLoaded (); return true; } return false; } public ProjectContentWrapper (Project project) { if (project == null) throw new ArgumentNullException ("project"); this.Project = project; var lazyProjectLoader = new LazyProjectLoader (this); this.Content = lazyProjectLoader; } public IEnumerable ReferencedProjects { get { return Project.GetReferencedItems (ConfigurationSelector.Default).OfType (); } } class LazyProjectLoader : IProjectContent { readonly ProjectContentWrapper wrapper; readonly Task contextTask; public Task ContextTask { get { return contextTask; } } object contentLock = new object (); IProjectContent contentWithReferences; public IProjectContent Content { get { lock (contentLock) { if (References != null) { return contentWithReferences ?? (contentWithReferences = contextTask.Result.AddAssemblyReferences (References)); } return contextTask.Result; } } } List references; public List References { get { return references; } set { lock (contentLock) { references = value; } } } public LazyProjectLoader (ProjectContentWrapper wrapper) { this.wrapper = wrapper; contextTask = Task.Factory.StartNew (delegate { try { this.wrapper.BeginLoadOperation (); var p = this.wrapper.Project; var context = LoadProjectCache (p); var assemblyName = p.ParentSolution != null ? p.GetOutputFileName (p.ParentSolution.DefaultConfigurationSelector).FileNameWithoutExtension : p.Name; if (string.IsNullOrEmpty (assemblyName)) assemblyName = p.Name; if (context != null) { return context.SetAssemblyName (assemblyName) ?? context; } context = new MonoDevelopProjectContent (p); context = context.SetLocation (p.FileName); context = context.SetAssemblyName (assemblyName); QueueParseJob (this.wrapper); return context; } finally { this.wrapper.EndLoadOperation (); } }); } static IProjectContent LoadProjectCache (Project project) { string cacheDir = GetCacheDirectory (project); if (cacheDir == null) return null; var cacheFile = Path.Combine (cacheDir, "completion.cache"); if (!File.Exists (cacheFile)) return null; try { var cache = DeserializeObject (cacheFile); var monoDevelopProjectContent = cache as MonoDevelopProjectContent; if (monoDevelopProjectContent != null) monoDevelopProjectContent.Project = project; return cache; } catch (Exception e) { LoggingService.LogWarning ("Error while reading completion cache, regenerating", e); Directory.Delete (cacheDir, true); return null; } } #region IAssemblyReference implementation IAssembly IAssemblyReference.Resolve (ITypeResolveContext context) { return Content.Resolve (context); } #endregion #region IUnresolvedAssembly implementation string IUnresolvedAssembly.AssemblyName { get { return Content.AssemblyName; } } string IUnresolvedAssembly.FullAssemblyName { get { return Content.FullAssemblyName; } } string IUnresolvedAssembly.Location { get { return Content.Location; } } IEnumerable IUnresolvedAssembly.AssemblyAttributes { get { return Content.AssemblyAttributes; } } IEnumerable IUnresolvedAssembly.ModuleAttributes { get { return Content.ModuleAttributes; } } IEnumerable IUnresolvedAssembly.TopLevelTypeDefinitions { get { return Content.TopLevelTypeDefinitions; } } #endregion #region IProjectContent implementation string IProjectContent.ProjectFileName { get { return Content.ProjectFileName; } } IUnresolvedFile IProjectContent.GetFile (string fileName) { return Content.GetFile (fileName); } ICompilation IProjectContent.CreateCompilation () { return Content.CreateCompilation (); } public ICompilation CreateCompilation (ISolutionSnapshot solutionSnapshot) { return Content.CreateCompilation (solutionSnapshot); } IProjectContent IProjectContent.SetAssemblyName (string newAssemblyName) { return Content.SetAssemblyName (newAssemblyName); } IProjectContent IProjectContent.SetLocation (string newLocation) { return Content.SetLocation (newLocation); } IProjectContent IProjectContent.AddAssemblyReferences (IEnumerable references) { return Content.AddAssemblyReferences (references); } IProjectContent IProjectContent.AddAssemblyReferences (params IAssemblyReference[] references) { return Content.AddAssemblyReferences (references); } IProjectContent IProjectContent.RemoveAssemblyReferences (IEnumerable references) { return Content.RemoveAssemblyReferences (references); } IProjectContent IProjectContent.RemoveAssemblyReferences (params IAssemblyReference[] references) { return Content.RemoveAssemblyReferences (references); } #pragma warning disable 618 IProjectContent IProjectContent.UpdateProjectContent (IUnresolvedFile oldFile, IUnresolvedFile newFile) { return Content.UpdateProjectContent (oldFile, newFile); } public IProjectContent UpdateProjectContent (IEnumerable oldFiles, IEnumerable newFiles) { return Content.UpdateProjectContent (oldFiles, newFiles); } #pragma warning restore 618 public IProjectContent AddOrUpdateFiles (IEnumerable newFiles) { return Content.AddOrUpdateFiles (newFiles); } public IProjectContent AddOrUpdateFiles (params IUnresolvedFile[] newFiles) { return Content.AddOrUpdateFiles (newFiles); } IEnumerable IProjectContent.Files { get { return Content.Files; } } IEnumerable IProjectContent.AssemblyReferences { get { return Content.AssemblyReferences; } } IProjectContent IProjectContent.SetProjectFileName (string newProjectFileName) { return Content.SetProjectFileName (newProjectFileName); } IProjectContent IProjectContent.RemoveFiles (IEnumerable fileNames) { return Content.RemoveFiles (fileNames); } IProjectContent IProjectContent.RemoveFiles (params string[] fileNames) { return Content.RemoveFiles (fileNames); } #endregion object compilerSettings; public IProjectContent SetCompilerSettings (object compilerSettings) { this.compilerSettings = compilerSettings; return this; } public object CompilerSettings { get { return compilerSettings; } } } bool HasCyclicRefs (ProjectContentWrapper wrapper, HashSet nonCyclicCache) { if (nonCyclicCache.Contains (wrapper.Project)) return false; nonCyclicCache.Add (wrapper.Project); foreach (var referencedProject in wrapper.ReferencedProjects) { ProjectContentWrapper w; if (referencedProject == Project || referencedProject == wrapper.Project || projectContents.TryGetValue (referencedProject, out w) && HasCyclicRefs (w, nonCyclicCache)) { return true; } } return false; } List LoadReferencedAssemblies (DotNetProject netProject) { var newReferencedAssemblies = new List (); try { foreach (string file in netProject.GetReferencedAssemblies (IdeApp.IsInitialized ? IdeApp.Workspace.ActiveConfiguration : ConfigurationSelector.Default, false)) { string fileName; if (!Path.IsPathRooted (file)) { fileName = Path.Combine (Path.GetDirectoryName (netProject.FileName), file); } else { fileName = Path.GetFullPath (file); } var ctx = LoadAssemblyContext (fileName); if (ctx != null) { newReferencedAssemblies.Add (ctx); ctx.Loaded += HandleReferencedProjectInLoadChange; } else { LoggingService.LogWarning ("TypeSystemService: Can't load assembly context for:" + file); } } } catch (Exception e) { LoggingService.LogError ("Error while getting assembly references", e); } return newReferencedAssemblies; } public void EnsureReferencesAreLoaded () { lock (projectContentLock) { if (referencesConnected) return; compilation = null; referencesConnected = true; var netProject = Project as DotNetProject; if (netProject == null) return; try { var contexts = new List (); var nonCyclicCache = new HashSet (); foreach (var referencedWrapper in referencedWrappers) { referencedWrapper.Loaded -= HandleReferencedProjectInLoadChange; } var newReferencedWrappers = new List (); foreach (var referencedProject in ReferencedProjects) { ProjectContentWrapper wrapper; if (projectContents.TryGetValue (referencedProject, out wrapper)) { if (HasCyclicRefs (wrapper, nonCyclicCache)) continue; wrapper.Loaded += HandleReferencedProjectInLoadChange; newReferencedWrappers.Add (wrapper); contexts.Add (new UnresolvedAssemblyDecorator (wrapper)); } } this.referencedWrappers = newReferencedWrappers; UnresolvedAssemblyProxy ctx; // Add mscorlib reference var config = IdeApp.Workspace != null ? netProject.GetConfiguration (IdeApp.Workspace.ActiveConfiguration) as DotNetProjectConfiguration : null; bool noStdLib = false; if (config != null) { var parameters = config.CompilationParameters as DotNetConfigurationParameters; if (parameters != null) { noStdLib = parameters.NoStdLib; } } if (!noStdLib && netProject.TargetRuntime != null && netProject.TargetRuntime.AssemblyContext != null) { var corLibRef = netProject.TargetRuntime.AssemblyContext.GetAssemblyForVersion (typeof(object).Assembly.FullName, null, netProject.TargetFramework); if (corLibRef != null) { ctx = LoadAssemblyContext (corLibRef.Location); if (ctx != null) contexts.Add (ctx); } } // Get the assembly references throught the project, since it may have custom references foreach (var asm in referencedAssemblies) { asm.Loaded += HandleReferencedProjectInLoadChange; } var newReferencedAssemblies = LoadReferencedAssemblies (netProject); contexts.AddRange (newReferencedAssemblies); referencedAssemblies = newReferencedAssemblies; bool changed = WasChanged; var lazyProjectLoader = Content as LazyProjectLoader; if (lazyProjectLoader != null) { lazyProjectLoader.References = contexts; } else { UpdateContent (c => c.RemoveAssemblyReferences (Content.AssemblyReferences).AddAssemblyReferences (contexts)); } WasChanged = changed; } catch (Exception e) { if (netProject.TargetRuntime == null) { LoggingService.LogError ("Target runtime was null: " + Project.Name); } else if (netProject.TargetRuntime.AssemblyContext == null) { LoggingService.LogError ("Target runtime assembly context was null: " + Project.Name); } LoggingService.LogError ("Error while reloading all references of project: " + Project.Name, e); } finally { UpdateLoadState (); } } } static readonly object reconnectLock = new object(); public void ReconnectAssemblyReferences () { var netProject = Project as DotNetProject; if (netProject == null) return; lock (reconnectLock) { CancelLoad (); this.referencesConnected = false; RequestLoad (); } } void HandleReferencedProjectInLoadChange (object sender, EventArgs e) { UpdateLoadState (); } internal void Unload () { CancelLoad (); foreach (var asm in referencedAssemblies) { asm.Loaded -= HandleReferencedProjectInLoadChange; } foreach (var wrapper in referencedWrappers) { wrapper.Loaded -= HandleReferencedProjectInLoadChange; } loadActions = null; foreach (var wrapper in referencedWrappers) { wrapper.Loaded -= HandleReferencedProjectInLoadChange; } referencedWrappers.Clear (); referencedAssemblies.Clear (); Loaded = null; Content = new CSharpProjectContent (); } } static readonly object projectContentLock = new object (); static readonly Dictionary projectContents = new Dictionary (); public static ProjectContentWrapper LoadProject (Project project) { if (IncLoadCount (project) != 1) return null; lock (projectContentLock) { if (projectContents.ContainsKey (project)) return null; try { Counters.ParserService.ProjectsLoaded++; ProjectContentWrapper wrapper; projectContents [project] = wrapper = new ProjectContentWrapper (project); var dotNetProject = project as DotNetProject; if (dotNetProject != null) CheckProjectOutput (dotNetProject, false); project.FileAddedToProject += OnFileAdded; project.FileRemovedFromProject += OnFileRemoved; project.FileRenamedInProject += OnFileRenamed; project.Modified += OnProjectModified; if (dotNetProject != null) { StartFrameworkLookup (dotNetProject); } return wrapper; } catch (Exception ex) { LoggingService.LogError ("Parser database for project '" + project.Name + " could not be loaded", ex); } return null; } } public static Project GetProject (IEntity entity) { if (entity == null) throw new ArgumentNullException ("entity"); return GetProject (entity.ParentAssembly.UnresolvedAssembly.Location); } public static Project GetProject (string location) { foreach (var wrapper in projectContents) if (wrapper.Value.Compilation.MainAssembly.UnresolvedAssembly.Location == location) return wrapper.Key; return null; } #region Project modification handlers static void OnFileAdded (object sender, ProjectFileEventArgs args) { var project = (Project)sender; foreach (ProjectFileEventInfo fargs in args) { QueueParseJob (projectContents [project], new [] { fargs.ProjectFile }); } } static void OnFileRemoved (object sender, ProjectFileEventArgs args) { var project = (Project)sender; foreach (ProjectFileEventInfo fargs in args) { var wrapper = projectContents [project]; var fileName = fargs.ProjectFile.Name; var file = wrapper._content.GetFile (fileName); if (file == null) continue; wrapper.UpdateContent (c => c.RemoveFiles (fileName)); wrapper.InformFileRemoved (new ParsedFileEventArgs (file)); var tags = wrapper.GetExtensionObject (); if (tags != null) tags.RemoveFile (wrapper.Project, fileName); } } static void OnFileRenamed (object sender, ProjectFileRenamedEventArgs args) { var project = (Project)sender; foreach (ProjectFileRenamedEventInfo fargs in args) { var content = projectContents [project]; var file = content._content.GetFile (fargs.OldName); if (file == null) continue; content.UpdateContent (c => c.RemoveFiles (fargs.OldName)); content.InformFileRemoved (new ParsedFileEventArgs (file)); var tags = content.GetExtensionObject (); if (tags != null) tags.RemoveFile (project, fargs.OldName); QueueParseJob (content, new [] { fargs.ProjectFile }); } } static void OnProjectModified (object sender, SolutionItemModifiedEventArgs args) { if (!args.Any (x => x.Hint == "TargetFramework" || x.Hint == "References")) return; var project = (Project)sender; ProjectContentWrapper wrapper; projectContents.TryGetValue (project, out wrapper); if (wrapper == null) return; wrapper.ReconnectAssemblyReferences (); } #endregion internal static void Unload (WorkspaceItem item) { var ws = item as Workspace; TrackFileChanges = false; loadCancellationSource.Cancel (); if (ws != null) { foreach (WorkspaceItem it in ws.Items) Unload (it); ws.ItemAdded -= OnWorkspaceItemAdded; ws.ItemRemoved -= OnWorkspaceItemRemoved; projectContents.Clear (); loadWs = false; } else { var solution = item as Solution; if (solution != null) { foreach (var project in solution.GetAllProjects ()) { UnloadProject (project); } solution.SolutionItemAdded -= OnSolutionItemAdded; solution.SolutionItemRemoved -= OnSolutionItemRemoved; } } cachedAssemblyContents.Clear (); lock (parseQueueLock) { parseQueueIndex.Clear (); parseQueue.Clear (); } TrackFileChanges = true; } internal static void UnloadProject (Project project, bool skipProjectSerialization = false) { if (DecLoadCount (project) != 0) return; Counters.ParserService.ProjectsLoaded--; project.FileAddedToProject -= OnFileAdded; project.FileRemovedFromProject -= OnFileRemoved; project.FileRenamedInProject -= OnFileRenamed; project.Modified -= OnProjectModified; ProjectContentWrapper wrapper; lock (projectWrapperUpdateLock) { if (!projectContents.TryGetValue (project, out wrapper)) return; projectContents.Remove (project); } if (!skipProjectSerialization) StoreProjectCache (project, wrapper); OnProjectUnloaded (new ProjectUnloadEventArgs (project, wrapper)); wrapper.Unload (); } public static event EventHandler ProjectUnloaded; static void OnProjectUnloaded (ProjectUnloadEventArgs e) { var handler = ProjectUnloaded; if (handler != null) handler (null, e); } static void OnWorkspaceItemAdded (object s, WorkspaceItemEventArgs args) { Load (args.Item); } static void OnWorkspaceItemRemoved (object s, WorkspaceItemEventArgs args) { Unload (args.Item); } static void OnSolutionItemAdded (object sender, SolutionItemChangeEventArgs args) { var project = args.SolutionItem as Project; if (project != null) { var wrapper = LoadProject (project); if (wrapper != null) { var files = wrapper.Project.Files.ToArray (); Task.Factory.StartNew (delegate { CheckModifiedFiles (wrapper.Project, files, wrapper); wrapper.RequestLoad (); }); } } } static void OnSolutionItemRemoved (object sender, SolutionItemChangeEventArgs args) { var project = args.SolutionItem as Project; if (project != null) UnloadProject (project); } #endregion #region Reference Counting static readonly Dictionary loadCount = new Dictionary (); static readonly object rwLock = new object (); static int DecLoadCount (Project ob) { lock (rwLock) { int c; if (loadCount.TryGetValue (ob, out c)) { c--; if (c == 0) loadCount.Remove (ob); else loadCount [ob] = c; return c; } LoggingService.LogError ("DecLoadCount: Object not registered."); return 0; } } static int IncLoadCount (Project ob) { lock (rwLock) { int c; if (loadCount.TryGetValue (ob, out c)) { c++; loadCount [ob] = c; return c; } loadCount [ob] = 1; return 1; } } #endregion static bool GetXml (string baseName, TargetRuntime runtime, out FilePath xmlFileName) { try { xmlFileName = LookupLocalizedXmlDoc (baseName); if (!xmlFileName.IsNull) return true; } catch (Exception e) { LoggingService.LogError ("Error while looking up XML docs.", e); } if (MonoDevelop.Core.Platform.IsWindows) { string windowsFileName = FindWindowsXmlDocumentation (baseName, runtime); if (File.Exists (windowsFileName)) { xmlFileName = windowsFileName; return true; } } xmlFileName = ""; return false; } #region Lookup XML documentation // ProgramFilesX86 is broken on 32-bit WinXP, this is a workaround static string GetProgramFilesX86 () { return Environment.GetFolderPath (IntPtr.Size == 8 ? Environment.SpecialFolder.ProgramFilesX86 : Environment.SpecialFolder.ProgramFiles); } static readonly string referenceAssembliesPath = Path.Combine (GetProgramFilesX86 (), @"Reference Assemblies\Microsoft\\Framework"); static readonly string frameworkPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Windows), @"Microsoft.NET\Framework"); static string FindWindowsXmlDocumentation (string assemblyFileName, TargetRuntime runtime) { string fileName; ClrVersion version = runtime != null && runtime.CustomFrameworks.Any () ? runtime.CustomFrameworks.First ().ClrVersion : ClrVersion.Default; switch (version) { // case "1.0": // fileName = LookupLocalizedXmlDoc (Path.Combine (frameworkPath, "v1.0.3705", assemblyFileName)); // break; case ClrVersion.Net_1_1: fileName = LookupLocalizedXmlDoc (Path.Combine (frameworkPath, "v1.1.4322", assemblyFileName)); break; case ClrVersion.Net_2_0: case ClrVersion.Clr_2_1: fileName = LookupLocalizedXmlDoc (Path.Combine (frameworkPath, "v2.0.50727", assemblyFileName)) ?? LookupLocalizedXmlDoc (Path.Combine (referenceAssembliesPath, "v3.5")) ?? LookupLocalizedXmlDoc (Path.Combine (referenceAssembliesPath, "v3.0")) ?? LookupLocalizedXmlDoc (Path.Combine (referenceAssembliesPath, @".NETFramework\v3.5\Profile\Client")); break; default: fileName = LookupLocalizedXmlDoc (Path.Combine (referenceAssembliesPath, @".NETFramework\v4.0", assemblyFileName)) ?? LookupLocalizedXmlDoc (Path.Combine (frameworkPath, "v4.0.30319", assemblyFileName)); break; } return fileName; } static string LookupLocalizedXmlDoc (string fileName) { return XmlDocumentationProvider.LookupLocalizedXmlDoc (fileName); } #endregion class UnresolvedAssemblyProxy : IUnresolvedAssembly { public readonly string FileName; internal LazyAssemblyLoader CtxLoader; public IUnresolvedAssembly Ctx { get { return CtxLoader; } } public bool InLoad { get { return CtxLoader == null || CtxLoader.InLoad; } } public event EventHandler Loaded { add { var ctxLoader = CtxLoader; if (ctxLoader != null) ctxLoader.Loaded += value; } remove { var ctxLoader = CtxLoader; if (ctxLoader != null) ctxLoader.Loaded -= value; } } public UnresolvedAssemblyProxy (string fileName) { if (fileName == null) throw new ArgumentNullException ("fileName"); this.FileName = fileName; } #region IUnresolvedAssembly implementation string IUnresolvedAssembly.AssemblyName { get { return Ctx.AssemblyName; } } string IUnresolvedAssembly.FullAssemblyName { get { return Ctx.FullAssemblyName; } } string IUnresolvedAssembly.Location { get { return Ctx.Location; } } IEnumerable IUnresolvedAssembly.AssemblyAttributes { get { return Ctx.AssemblyAttributes; } } IEnumerable IUnresolvedAssembly.ModuleAttributes { get { return Ctx.ModuleAttributes; } } IEnumerable IUnresolvedAssembly.TopLevelTypeDefinitions { get { return Ctx.TopLevelTypeDefinitions; } } #endregion #region IAssemblyReference implementation IAssembly IAssemblyReference.Resolve (ITypeResolveContext context) { var ctx = Ctx; if (ctx == null) return null; return ctx.Resolve (context); } #endregion public override string ToString () { return string.Format ("[UnresolvedAssemblyProxy: FileName={0}]", FileName); } } internal class LazyAssemblyLoader : IUnresolvedAssembly { class LazyAssembly : IAssembly { readonly LazyAssemblyLoader loader; readonly ITypeResolveContext context; IAssembly assembly; IAssembly Assembly { get { lock (loader) { if (assembly == null) { loader.EnsureAssemblyLoaded (); assembly = loader.assembly.Resolve (context); } return assembly; } } } public LazyAssembly (LazyAssemblyLoader loader, ITypeResolveContext context) { this.loader = loader; this.context = context; } #region IAssembly implementation bool IAssembly.InternalsVisibleTo (IAssembly assembly) { return Assembly.InternalsVisibleTo (assembly); } ITypeDefinition IAssembly.GetTypeDefinition (TopLevelTypeName typeName) { return Assembly.GetTypeDefinition (typeName); } IUnresolvedAssembly IAssembly.UnresolvedAssembly { get { return Assembly.UnresolvedAssembly; } } bool IAssembly.IsMainAssembly { get { return Assembly.IsMainAssembly; } } string IAssembly.AssemblyName { get { return Assembly.AssemblyName; } } string IAssembly.FullAssemblyName { get { return Assembly.FullAssemblyName; } } IList IAssembly.AssemblyAttributes { get { return Assembly.AssemblyAttributes; } } IList IAssembly.ModuleAttributes { get { return Assembly.ModuleAttributes; } } INamespace IAssembly.RootNamespace { get { return Assembly.RootNamespace; } } IEnumerable IAssembly.TopLevelTypeDefinitions { get { return Assembly.TopLevelTypeDefinitions; } } #endregion #region ICompilationProvider implementation ICompilation ICompilationProvider.Compilation { get { return Assembly.Compilation; } } #endregion } #region IAssemblyReference implementation IAssembly IAssemblyReference.Resolve (ITypeResolveContext context) { if (assembly != null) return assembly.Resolve (context); return new LazyAssembly (this, context); } #endregion #region IUnresolvedAssembly implementation readonly object assemblyLock = new object (); string IUnresolvedAssembly.AssemblyName { get { lock (assemblyLock) { EnsureAssemblyLoaded (); return assembly.AssemblyName; } } } string IUnresolvedAssembly.FullAssemblyName { get { lock (assemblyLock) { EnsureAssemblyLoaded (); return assembly.FullAssemblyName; } } } string IUnresolvedAssembly.Location { get { lock (assemblyLock) { EnsureAssemblyLoaded (); return assembly.Location; } } } IEnumerable IUnresolvedAssembly.AssemblyAttributes { get { lock (assemblyLock) { EnsureAssemblyLoaded (); return assembly.AssemblyAttributes; } } } IEnumerable IUnresolvedAssembly.ModuleAttributes { get { lock (assemblyLock) { EnsureAssemblyLoaded (); return assembly.ModuleAttributes; } } } IEnumerable IUnresolvedAssembly.TopLevelTypeDefinitions { get { lock (assemblyLock) { EnsureAssemblyLoaded (); return assembly.TopLevelTypeDefinitions; } } } #endregion readonly string fileName; readonly string cache; IUnresolvedAssembly assembly; readonly object asmLocker = new object (); internal void EnsureAssemblyLoaded () { lock (asmLocker) { if (assembly != null) return; var loadedAssembly = LoadAssembly (); if (loadedAssembly == null) { LoggingService.LogWarning ("Assembly " + fileName + " could not be loaded cleanly."); assembly = new DefaultUnresolvedAssembly (fileName); } else { assembly = loadedAssembly; } OnLoad (EventArgs.Empty); } } public override string ToString () { return string.Format ("[LazyAssemblyLoader: fileName={0}, assembly={1}]", fileName, assembly); } public bool InLoad { get { return assembly == null; } } public event EventHandler Loaded; protected virtual void OnLoad (EventArgs e) { var handler = Loaded; if (handler != null) handler (this, e); } public LazyAssemblyLoader (string fileName, string cache) { this.fileName = fileName; this.cache = cache; } IUnresolvedAssembly LoadAssembly () { var assemblyPath = cache != null ? Path.Combine (cache, "assembly.data") : null; var assemblyTag = cache != null ? Path.Combine (cache, "assembly.tag") : null; try { if (assemblyPath != null && assemblyTag != null && File.Exists (assemblyPath) && File.Exists (assemblyTag)) { var deserializedAssembly = DeserializeObject (assemblyPath); if (deserializedAssembly != null) { return deserializedAssembly; } } } catch (Exception) { } IUnresolvedAssembly result; try { var loader = new IkvmLoader (); loader.IncludeInternalMembers = true; loader.DocumentationProvider = new CombinedDocumentationProvider (fileName); result = loader.LoadAssemblyFile (fileName); } catch (Exception e) { LoggingService.LogError ("Can't convert assembly: " + fileName, e); return null; } if (cache != null) { var writeTime = File.GetLastWriteTimeUtc (fileName); SerializeObject (assemblyPath, result); SerializeObject (assemblyTag, new AssemblyTag (writeTime)); } return result; } } [Serializable] class CombinedDocumentationProvider : IDocumentationProvider { readonly string fileName; [NonSerialized] IDocumentationProvider baseProvider; public IDocumentationProvider BaseProvider { get { if (baseProvider == null) { FilePath xmlDocFile; if (GetXml (fileName, null, out xmlDocFile)) { try { baseProvider = new XmlDocumentationProvider (xmlDocFile); } catch (Exception ex) { LoggingService.LogWarning ("Ignoring error while reading xml doc from " + xmlDocFile, ex); } } if (baseProvider == null) baseProvider = new MonoDocDocumentationProvider (); } return baseProvider; } } public CombinedDocumentationProvider (string fileName) { this.fileName = fileName; } #region IDocumentationProvider implementation public DocumentationComment GetDocumentation (IEntity entity) { var provider = BaseProvider; return provider != null ? provider.GetDocumentation (entity) : null; } #endregion } static readonly object assemblyContextLock = new object (); static UnresolvedAssemblyProxy LoadAssemblyContext (FilePath fileName) { CanonicalizePath (ref fileName); UnresolvedAssemblyProxy loadedContext; if (cachedAssemblyContents.TryGetValue (fileName, out loadedContext)) { return loadedContext; } if (!File.Exists (fileName)) return null; lock (assemblyContextLock) { if (cachedAssemblyContents.TryGetValue (fileName, out loadedContext)) { CheckModifiedFile (loadedContext); return loadedContext; } string cache = GetCacheDirectory (fileName, true); try { var result = new UnresolvedAssemblyProxy (fileName); result.CtxLoader = new LazyAssemblyLoader (fileName, cache); CheckModifiedFile (result); var newcachedAssemblyContents = new Dictionary (cachedAssemblyContents); newcachedAssemblyContents [fileName] = result; cachedAssemblyContents = newcachedAssemblyContents; OnAssemblyLoaded (new AssemblyLoadedEventArgs (result.CtxLoader)); return result; } catch (Exception ex) { LoggingService.LogError ("Error loading assembly " + fileName, ex); return null; } } } internal static event EventHandler AssemblyLoaded; static void OnAssemblyLoaded (AssemblyLoadedEventArgs e) { var handler = AssemblyLoaded; if (handler != null) handler (null, e); } public static IUnresolvedAssembly LoadAssemblyContext (TargetRuntime runtime, TargetFramework fx, string fileName) { if (File.Exists (fileName)) return LoadAssemblyContext (fileName); var corLibRef = runtime.AssemblyContext.GetAssemblyForVersion (fileName, null, fx); return corLibRef == null ? null : LoadAssemblyContext (corLibRef.Location); } public static IProjectContent GetProjectContext (Project project) { if (project == null) throw new ArgumentNullException ("project"); var content = GetProjectContentWrapper (project); if (content == null) return null; return content.Content; } public static ICompilation GetCompilation (Project project) { if (project == null) throw new ArgumentNullException ("project"); var content = GetProjectContentWrapper (project); if (content == null) return null; return content.Compilation; } public static ICompilation GetCompilation (SystemAssembly assembly, ICompilation compilation) { var ctx = LoadAssemblyContext (assembly.Location); var list = compilation.ReferencedAssemblies.Select (r => r.UnresolvedAssembly).ToList (); list.Add (compilation.MainAssembly.UnresolvedAssembly); var result = new SimpleCompilation (ctx, list); return result; } static IEnumerable GetFrameworkAssemblies (DotNetProject netProject) { var assemblies = new Dictionary (); foreach (var assembly in netProject.AssemblyContext.GetAssemblies ()) { SystemAssembly existing; if (assemblies.TryGetValue (assembly.Name, out existing)) { Version v1, v2; if (!Version.TryParse (existing.Version, out v1)) continue; if (!Version.TryParse (assembly.Version, out v2)) continue; if (v1 > v2) continue; } assemblies [assembly.Name] = assembly; } return assemblies.Values; } class FrameworkTask { public int RetryCount { get; set; } public Task Task { get; set; } } readonly static Dictionary frameworkLookup = new Dictionary (); static void StartFrameworkLookup (DotNetProject netProject) { if (netProject == null) throw new ArgumentNullException ("netProject"); lock (frameworkLookup) { FrameworkTask result; if (netProject.TargetFramework == null) return; var frameworkName = netProject.TargetFramework.Name; if (!frameworkLookup.TryGetValue (frameworkName, out result)) frameworkLookup [frameworkName] = result = new FrameworkTask (); if (result.Task != null) return; result.Task = Task.Factory.StartNew (delegate { return GetFrameworkLookup (netProject); }); } } public static bool TryGetFrameworkLookup (DotNetProject project, out FrameworkLookup lookup) { lock (frameworkLookup) { FrameworkTask result; if (frameworkLookup.TryGetValue (project.TargetFramework.Name, out result)) { if (!result.Task.IsCompleted) { lookup = null; return false; } lookup = result.Task.Result; return true; } } lookup = null; return false; } public static bool RecreateFrameworkLookup (DotNetProject netProject) { lock (frameworkLookup) { FrameworkTask result; var frameworkName = netProject.TargetFramework.Name; if (!frameworkLookup.TryGetValue (frameworkName, out result)) return false; if (result.RetryCount > 5) { LoggingService.LogError ("Can't create framework lookup for:" + frameworkName); return false; } result.RetryCount++; LoggingService.LogInfo ("Trying to recreate framework lookup for {0}, try {1}.", frameworkName, result.RetryCount); result.Task = null; StartFrameworkLookup (netProject); return true; } } static FrameworkLookup GetFrameworkLookup (DotNetProject netProject) { FrameworkLookup result; string fileName; var cache = GetCacheDirectory (netProject.TargetFramework); fileName = Path.Combine (cache, "FrameworkLookup_" + FrameworkLookup.CurrentVersion + ".dat"); try { if (File.Exists (fileName)) { result = FrameworkLookup.Load (fileName); if (result != null) { return result; } } } catch (Exception e) { LoggingService.LogWarning ("Can't read framework cache - recreating...", e); } try { using (var creator = FrameworkLookup.Create (fileName)) { foreach (var assembly in GetFrameworkAssemblies (netProject)) { var ctx = LoadAssemblyContext (assembly.Location); foreach (var type in ctx.Ctx.GetAllTypeDefinitions ()) { if (!type.IsPublic) continue; creator.AddLookup (assembly.Package.Name, assembly.FullName, type); } } } } catch (Exception e) { LoggingService.LogError ("Error while storing framework lookup", e); return FrameworkLookup.Empty; } try { result = FrameworkLookup.Load (fileName); return result; } catch (Exception e) { LoggingService.LogError ("Error loading framework lookup", e); return FrameworkLookup.Empty; } } public static ProjectContentWrapper GetProjectContentWrapper (Project project) { if (project == null) throw new ArgumentNullException ("project"); ProjectContentWrapper content; if (projectContents.TryGetValue (project, out content)) return content; // in case of outdated projects try to get the most recent project wrapper. foreach (var cnt in projectContents) { if (cnt.Key.FileName == project.FileName) return cnt.Value; } return null; } public static IProjectContent GetContext (FilePath file, string mimeType, string text) { using (var reader = new StringReader (text)) { var parsedDocument = ParseFile (file, mimeType, reader); var content = new CSharpProjectContent (); return content.AddOrUpdateFiles (parsedDocument.ParsedFile); } } static Dictionary cachedAssemblyContents = new Dictionary (); /// /// Force the update of a project context. Note: This method blocks the thread. /// It was just implemented for use inside unit tests. /// public static void ForceUpdate (ProjectContentWrapper context) { CheckModifiedFiles (); while (!context.IsLoaded) { Thread.Sleep (10); } } #region Parser queue static bool threadRunning; public static IProgressMonitorFactory ParseProgressMonitorFactory { get; set; } class InternalProgressMonitor : NullProgressMonitor { public InternalProgressMonitor () { StartParseOperation (); } public override void Dispose () { EndParseOperation (); } } internal static IProgressMonitor GetParseProgressMonitor () { var mon = ParseProgressMonitorFactory != null ? ParseProgressMonitorFactory.CreateProgressMonitor () : new NullProgressMonitor (); return new AggregatedProgressMonitor (mon, new InternalProgressMonitor ()); } static readonly Queue parseQueue = new Queue (); class ParsingJob { public ProjectContentWrapper Context; public IEnumerable FileList; // public Action ParseCallback; public void Run (IProgressMonitor monitor, CancellationToken token) { TypeSystemParserNode node = null; TypeSystemParser parser = null; var tags = Context.GetExtensionObject (); try { Context.BeginLoadOperation (); var parsedFiles = new List> (); foreach (var file in (FileList ?? Context.Project.Files)) { if (token.IsCancellationRequested) return; var fileName = file.FilePath; if (filesSkippedInParseThread.Any (f => f == fileName)) { continue; } if (node == null || !node.CanParse (fileName, file.BuildAction)) { var newNode = GetTypeSystemParserNode (DesktopService.GetMimeTypeForUri (fileName), file.BuildAction); var newParser = newNode != null ? newNode.Parser : null; if (newParser == null) continue; node = newNode; parser = newParser; } if (parser == null || !File.Exists (fileName)) continue; ParsedDocument parsedDocument; try { parsedDocument = parser.Parse (false, fileName, Context.Project); } catch (Exception e) { LoggingService.LogError ("Error while parsing " + fileName, e); continue; } if (token.IsCancellationRequested) return; if (tags != null) tags.UpdateTags (Context.Project, parsedDocument.FileName, parsedDocument.TagComments); if (token.IsCancellationRequested) return; parsedFiles.Add (Tuple.Create (parsedDocument, Context._content.GetFile (fileName))); } Context.UpdateContent (c => c.AddOrUpdateFiles (parsedFiles.Where (f => (f.Item1.Flags & ParsedDocumentFlags.NonSerializable) != ParsedDocumentFlags.NonSerializable).Select (p => p.Item1.ParsedFile))); foreach (var file in parsedFiles) { if (token.IsCancellationRequested) return; if (file.Item2 != null) Context.InformFileRemoved (new ParsedFileEventArgs (file.Item2)); var parsedDocument = file.Item1; if ((parsedDocument.Flags & ParsedDocumentFlags.NonSerializable) != ParsedDocumentFlags.NonSerializable) Context.InformFileAdded (new ParsedFileEventArgs (parsedDocument.ParsedFile)); } } finally { Context.EndLoadOperation (); } } } static void UpdateProjectCommentTasks (ProjectContentWrapper context, ParsedDocument parsedDocument) { var tags = context.GetExtensionObject (); if (tags != null) // When tags are not there they're updated first time the tasks are requested. tags.UpdateTags (context.Project, parsedDocument.FileName, parsedDocument.TagComments); } // public static event EventHandler FileParsed; static readonly object parseQueueLock = new object (); static readonly AutoResetEvent parseEvent = new AutoResetEvent (false); static readonly ManualResetEvent queueEmptied = new ManualResetEvent (true); static bool trackingFileChanges; public static bool TrackFileChanges { get { return trackingFileChanges; } set { lock (parseQueueLock) { if (value != trackingFileChanges) { trackingFileChanges = value; if (value) StartParserThread (); } } } } static int parseStatus; public static bool IsParsing { get { return parseStatus > 0; } } static readonly Dictionary parseQueueIndex = new Dictionary (); internal static int PendingJobCount { get { lock (parseQueueLock) { return parseQueueIndex.Count; } } } static void QueueParseJob (ProjectContentWrapper context, IEnumerable fileList = null) { var job = new ParsingJob { Context = context, FileList = fileList }; lock (parseQueueLock) { RemoveParseJob (context); context.BeginLoadOperation (); parseQueueIndex [context] = job; parseQueue.Enqueue (job); parseEvent.Set (); if (parseQueueIndex.Count == 1) queueEmptied.Reset (); } } static bool WaitForParseJob (int timeout = 5000) { return parseEvent.WaitOne (timeout, true); } static ParsingJob DequeueParseJob () { lock (parseQueueLock) { if (parseQueue.Count > 0) { var job = parseQueue.Dequeue (); parseQueueIndex.Remove (job.Context); return job; } return null; } } internal static void WaitForParseQueue () { queueEmptied.WaitOne (); } static void RemoveParseJob (ProjectContentWrapper project) { lock (parseQueueLock) { ParsingJob job; if (parseQueueIndex.TryGetValue (project, out job)) { parseQueueIndex.Remove (project); project.EndLoadOperation (); } } } static void StartParserThread () { lock (parseQueueLock) { if (!threadRunning) { threadRunning = true; var t = new Thread (new ThreadStart (ParserUpdateThread)); t.Name = "Background parser"; t.IsBackground = true; t.Priority = ThreadPriority.AboveNormal; t.Start (); } } } static void ParserUpdateThread () { try { while (trackingFileChanges) { WaitForParseJob (); // CheckModifiedFiles (); if (trackingFileChanges) ConsumeParsingQueue (); } } catch (Exception ex) { LoggingService.LogError ("Unhandled error in parsing thread", ex); } lock (parseQueueLock) { threadRunning = false; if (trackingFileChanges) StartParserThread (); } } static bool IsFileModified (ProjectFile file, IUnresolvedFile parsedFile) { if (parsedFile == null || !parsedFile.LastWriteTime.HasValue) return true; try { return File.GetLastWriteTimeUtc (file.FilePath) != parsedFile.LastWriteTime; } catch (Exception) { return true; } } static void CheckModifiedFiles (Project project, ProjectFile[] projectFiles, ProjectContentWrapper content, CancellationToken token = default (CancellationToken)) { if (token.IsCancellationRequested) { return; } // Console.WriteLine ("add modified file check for :" + project.Name); content.RunWhenLoaded (delegate(IProjectContent cnt) { try { // Console.WriteLine ("check for " + project.Name); content.BeginLoadOperation (); var modifiedFiles = new List (); var oldFileNewFile = new List> (); foreach (var file in projectFiles) { if (token.IsCancellationRequested) { return; } if (file.BuildAction == null) continue; // if the file is already inside the content a parser exists for it, if not check if it can be parsed. var oldFile = cnt.GetFile (file.Name); oldFileNewFile.Add (Tuple.Create (file, oldFile)); } // This is disk intensive and slow oldFileNewFile.RemoveAll (t => !IsFileModified (t.Item1, t.Item2)); foreach (var v in oldFileNewFile) { var file = v.Item1; var oldFile = v.Item2; if (oldFile == null) { var parser = TypeSystemService.GetParser (DesktopService.GetMimeTypeForUri (file.Name), file.BuildAction); if (parser == null) continue; } modifiedFiles.Add (file); } var tags = content.GetExtensionObject (); // check if file needs to be removed from project content foreach (var file in cnt.Files) { if (token.IsCancellationRequested) { return; } if (project.GetProjectFile (file.FileName) == null) { content.UpdateContent (c => c.RemoveFiles (file.FileName)); content.InformFileRemoved (new ParsedFileEventArgs (file)); if (tags != null) tags.RemoveFile (project, file.FileName); } } if (token.IsCancellationRequested) { return; } if (modifiedFiles.Count > 0) { QueueParseJob (content, modifiedFiles); WaitForParseJob (); } } catch (Exception e) { LoggingService.LogError ("Exception in check modified files.", e); } finally { content.EndLoadOperation (); } }); } /// /// Used to store meta data information about the assembly. /// [Serializable] class AssemblyTag { public DateTime LastWriteTimeUTC { get; set; } public AssemblyTag (DateTime lastWriteTimeUTC) { this.LastWriteTimeUTC = lastWriteTimeUTC; } } static void CheckModifiedFile (UnresolvedAssemblyProxy context) { try { string cache = GetCacheDirectory (context.FileName); if (cache == null) return; var assemblyDataDirectory = Path.Combine (cache, "assembly.tag"); var writeTime = File.GetLastWriteTimeUtc (context.FileName); var cacheTime = File.Exists (assemblyDataDirectory) ? DeserializeObject (assemblyDataDirectory) : new AssemblyTag (writeTime); if (writeTime != cacheTime.LastWriteTimeUTC) { cache = GetCacheDirectory (context.FileName); if (cache != null) { try { // Files will be reloaded by the lazy loader File.Delete (assemblyDataDirectory); File.Delete (Path.Combine (cache, "assembly.data")); } catch { } context.CtxLoader = new LazyAssemblyLoader (context.FileName, cache); } } } catch (Exception e) { LoggingService.LogError ("Error while updating assembly " + context.FileName, e); } } static void CheckModifiedFiles () { Queue> list; lock (projectContentLock) { list = new Queue> (projectContents); } while (list.Count > 0) { var readydb = list.Dequeue (); var files = readydb.Key.Files.ToArray (); CheckModifiedFiles (readydb.Key, files, readydb.Value); } var assemblyList = new Queue> (cachedAssemblyContents); while (assemblyList.Count > 0) { var readydb = assemblyList.Dequeue (); CheckModifiedFile (readydb.Value); } } static void ConsumeParsingQueue () { int pending = 0; IProgressMonitor monitor = null; var token = loadCancellationSource.Token; StartParseOperation (); try { do { if (pending > 5 && monitor == null) { monitor = GetParseProgressMonitor (); monitor.BeginTask (GettextCatalog.GetString ("Generating database"), 0); } var job = DequeueParseJob (); if (job != null) { try { job.Run (monitor, token); } catch (Exception ex) { if (monitor == null) monitor = GetParseProgressMonitor (); monitor.ReportError (null, ex); } finally { job.Context.EndLoadOperation (); } } if (token.IsCancellationRequested) break; pending = PendingJobCount; } while (pending > 0); queueEmptied.Set (); } finally { if (monitor != null) monitor.Dispose (); EndParseOperation (); } } #endregion } sealed class AssemblyLoadedEventArgs : EventArgs { public readonly TypeSystemService.LazyAssemblyLoader Assembly; public AssemblyLoadedEventArgs (TypeSystemService.LazyAssemblyLoader assembly) { this.Assembly = assembly; } } public sealed class ProjectUnloadEventArgs : EventArgs { public readonly Project Project; public readonly TypeSystemService.ProjectContentWrapper Wrapper; public ProjectUnloadEventArgs (Project project, TypeSystemService.ProjectContentWrapper wrapper) { this.Project = project; this.Wrapper = wrapper; } } }