/// <summary>
        /// Creates a new instance
        /// </summary>
        /// <param name="rootProjectPath">Full file path to client project to use as root.</param>
        /// <param name="historyFilePath">Full file path to file to read and write in-memory cache.</param>
        /// <param name="logger">Instance of an <see cref="ILogger"/> to receive messages.</param>
        /// <param name="projectFileReader">Instance to use to read the project files.</param>
        internal LinkedServerProjectCache(string rootProjectPath, string historyFilePath, ILogger logger, ProjectFileReader projectFileReader)
        {
            if (string.IsNullOrEmpty(rootProjectPath))
            {
                throw new ArgumentNullException("rootProjectPath");
            }

            if (string.IsNullOrEmpty(historyFilePath))
            {
                throw new ArgumentNullException("historyFilePath");
            }

            if (logger == null)
            {
                throw new ArgumentNullException("logger");
            }

            if (projectFileReader == null)
            {
                throw new ArgumentNullException("projectFileReader");
            }

            this._rootProjectPath = rootProjectPath;
            this._historyFilePath = historyFilePath;
            this._logger = logger;
            this._projectFileReader = projectFileReader;
        }
        /// <summary>
        /// Releases all resources acquired by this instance.
        /// </summary>
        /// <remarks>
        /// This method returns all fields to their original uninitialized
        /// state and disposes any disposable objects that were lazily created.
        /// It may be called multiple times and will always leave the current
        /// instance in a valid state for reuse.  Note that this method does
        /// not affect any fields that are required for output items returned
        /// by this custom task, because they will be consumed by MSBuild after
        /// the task has executed.
        /// </remarks>
        private void ReleaseResources()
        {
            this._linkedServerProjectCache = null;
            this._serverProjectSourceFileCache = null;

            // The ProjectFileReader is IDisposable and requires
            // explicit disposal.  Because it is created lazily
            // there is no scope for a traditional 'using' block.
            if (this._projectFileReader != null)
            {
                this._projectFileReader.Dispose();
                this._projectFileReader = null;
            }
            }
        /// <summary>
        /// Sole constructor
        /// </summary>
        /// <param name="rootProjectPath">Full path to the root project file.</param>
        /// <param name="historyFilePath">Full path to the file where we are allowed to write results between builds.</param>
        /// <param name="logger">The <see cref="ILogger"/> to use for warnings and errors.</param>
        /// <param name="projectFileReader">Instance to use to read the project files.</param>
        internal ProjectSourceFileCache(string rootProjectPath, string historyFilePath, ILogger logger, ProjectFileReader projectFileReader)
        {
            if (string.IsNullOrEmpty(rootProjectPath))
            {
                throw new ArgumentNullException(nameof(rootProjectPath));
            }

            if (string.IsNullOrEmpty(historyFilePath))
            {
                throw new ArgumentNullException(nameof(historyFilePath));
            }

            if (logger == null)
            {
                throw new ArgumentNullException(nameof(logger));
            }

            if (projectFileReader == null)
            {
                throw new ArgumentNullException(nameof(projectFileReader));
            }

            this._rootProjectPath   = rootProjectPath;
            this._historyFilePath   = historyFilePath;
            this._projectFileReader = projectFileReader;
            this._logger            = logger;
        }
        /// <summary>
        /// Loads the internal state from the breadcrumb file passed to the ctor.
        /// </summary>
        /// <remarks>
        /// If the root project has been modified since the breadcrumb file
        /// was written, the entire cache is considered invalid and <c>false</c> is returned.
        /// If any cached project has been touched since the cache was last written, just
        /// is portion of the cache will be reloaded from the project file.
        /// </remarks>
        /// <returns>A <c>true</c> means the cache was loaded from the breadcrumb file successfully.
        /// If we detect the cache is out of date or does not exist, a <c>false</c> is returned.
        /// </returns>
        internal bool LoadCacheFromFile()
        {
            this.IsFileCacheCurrent = false;

            if (!File.Exists(this._historyFilePath))
            {
                return(false);
            }

            // If the root project itself has been touched since the
            // time we wrote the file, we can't trust anything in our cache
            DateTime projectWriteTime    = File.GetLastWriteTime(this._rootProjectPath);
            DateTime breadCrumbWriteTime = File.GetLastWriteTime(this._historyFilePath);

            if (projectWriteTime.CompareTo(breadCrumbWriteTime) > 0)
            {
                return(false);
            }

            // Read the breadcrumb file.
            // Format is:
            //  One line per project: path, lastWriteTime, list of source files separated by commas
            string fileContents = File.ReadAllText(this._historyFilePath);

            string[] projectEntries = fileContents.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string projectEntry in projectEntries)
            {
                string[] projectItems = projectEntry.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

                // Fewer than 2 is formatting problem -- maybe the file is corrupt.  Just discard cache and rebuild.
                if (projectItems.Length < 2)
                {
                    // Always clear out any partial results when returning false
                    this.SourceFilesByProject.Clear();
                    return(false);
                }
                string projectPath = projectItems[0];

                // If that project no longer exists, remove it from the cache but keep running
                if (!File.Exists(projectPath))
                {
                    continue;
                }

                List <string> files = new List <string>();

                // Projects with no source files have only the first 2 items (name and timestamp).
                // Those will be added to our cache with an empty list to show that we know they
                // have no source files (otherwise, we would need to open them again to know).
                // Any project with some number of source files falls into the body of this 'if'
                if (projectItems.Length >= 3)
                {
                    // Check whether the project file was touched since the last time
                    // we analyzed it.  Failure to parse last write time or discovery
                    // the project has been touched more recently causes us to reload
                    // just that project.  Incidentally, the use of Ticks here is more
                    // reliable than DateTime.ToString() which does not round-trip accurately.
                    projectWriteTime = File.GetLastWriteTime(projectPath);
                    long breadCrumbWriteTimeTicks = 0;
                    if (!long.TryParse(projectItems[1], out breadCrumbWriteTimeTicks) || projectWriteTime.Ticks > breadCrumbWriteTimeTicks)
                    {
                        // Go load from the project file and ignore what we had cached
                        files.AddRange(this._projectFileReader.LoadSourceFilesFromProject(projectPath));
                    }
                    else
                    {
                        // The last write time shows the project has not changed since
                        // we cached the results, so extract them from the text
                        for (int i = 2; i < projectItems.Length; ++i)
                        {
                            string file = projectItems[i];
                            if (string.IsNullOrEmpty(file))
                            {
                                continue;
                            }

                            // We write relative paths, so convert back to full
                            string fullFilePath = ProjectFileReader.ConvertToFullPath(file, projectPath);

                            // If the file has ceased to exist, but the project says it
                            // does, we do not add it to our internal lists
                            if (File.Exists(fullFilePath))
                            {
                                files.Add(fullFilePath);
                            }
                        }
                    }
                }
                this[projectPath] = files;
            }
            this.IsFileCacheCurrent = true;
            return(true);
        }
        /// <summary>
        /// Creates a new instance
        /// </summary>
        /// <param name="rootProjectPath">Full file path to client project to use as root.</param>
        /// <param name="historyFilePath">Full file path to file to read and write in-memory cache.</param>
        /// <param name="logger">Instance of an <see cref="ILogger"/> to receive messages.</param>
        /// <param name="projectFileReader">Instance to use to read the project files.</param>
        internal LinkedServerProjectCache(string rootProjectPath, string historyFilePath, ILogger logger, ProjectFileReader projectFileReader)
        {
            if (string.IsNullOrEmpty(rootProjectPath))
            {
                throw new ArgumentNullException("rootProjectPath");
            }

            if (string.IsNullOrEmpty(historyFilePath))
            {
                throw new ArgumentNullException("historyFilePath");
            }

            if (logger == null)
            {
                throw new ArgumentNullException("logger");
            }

            if (projectFileReader == null)
            {
                throw new ArgumentNullException("projectFileReader");
            }

            this._rootProjectPath   = rootProjectPath;
            this._historyFilePath   = historyFilePath;
            this._logger            = logger;
            this._projectFileReader = projectFileReader;
        }