private void AddOrUpdate(IEnumerable <IncludeDefinition> includes, IIncludeCacheTransaction cacheTransaction, HashSet <string> parsedIncludes, CppCodeModel codeModel, List <CodeSpecificException> codeSpecificExceptions, IncludeManagerParameter parameter)
        {
            Stack <IncludeDefinition> unvisited = new Stack <IncludeDefinition>(includes);

            while (unvisited.Any())
            {
                IncludeDefinition include = unvisited.Pop();
                if (TryFindInclude(include, out IncludeFindResult findResult, parsedIncludes, parameter))
                {
                    IEnumerable <IncludeDefinition> childDefinitions;
                    if (findResult.IsInCache)
                    {
                        VirtualFile      cacheSourceFile    = fileSystem.GetFile(findResult.IncludeCacheEntry.File);
                        VirtualDirectory cacheBaseDirectory = fileSystem.GetDirectory(findResult.IncludeCacheEntry.BaseDirectory);
                        parsedIncludes.Add(cacheSourceFile.GetRelativePath(cacheBaseDirectory));
                        childDefinitions = findResult.IncludeCacheEntry.Includes.Select(i => new IncludeDefinition(i, cacheSourceFile, cacheBaseDirectory));

                        codeModel.AddDefineStatements(findResult.IncludeCacheEntry.DefineStatements);
                    }
                    else
                    {
                        parsedIncludes.Add(findResult.IncludeFile.GetRelativePath(findResult.IncludeBaseDirectory));
                        childDefinitions = ProcessInclude(findResult.IncludeFile, findResult.IncludeBaseDirectory, codeSpecificExceptions, cacheTransaction, codeModel);
                    }

                    foreach (IncludeDefinition childDefinition in childDefinitions)
                    {
                        unvisited.Push(childDefinition);
                    }
                }
            }
        }
        private bool TryFindInclude(IncludeDefinition current, out IncludeFindResult result, HashSet <string> parsedIncludes, IncludeManagerParameter parameter)
        {
            if (current.Include.Trim().StartsWith("<", StringComparison.Ordinal))
            {
                //system includes in form <algorithms> are ignored
                result = null;
                return(false);
            }

            //Try parse relative to source
            if (current.DefinitionSourceFile.Parent.TryGetFileFromPath(current.Include, out VirtualFile file))
            {
                if (parsedIncludes.Contains(file.GetRelativePath(current.DefinitionSourceBaseDirectory)))
                {
                    result = null;
                    return(false);
                }

                result = includeCache.TryGetCacheEntry(file.FullName, out IncludeCacheEntry cacheEntry)
                             ? new IncludeFindResult(cacheEntry)
                             : new IncludeFindResult(file, current.DefinitionSourceBaseDirectory);
                return(true);
            }

            //Parse relative to include path
            foreach (VirtualDirectory includeDirectory in parameter.IncludeDirectories.Select(x => x.Directory).Where(v => v != null))
            {
                if (includeDirectory.TryGetFileFromPath(current.Include, out file))
                {
                    if (parsedIncludes.Contains(file.GetRelativePath(includeDirectory)))
                    {
                        result = null;
                        return(false);
                    }

                    result = includeCache.TryGetCacheEntry(file.FullName, out IncludeCacheEntry cacheEntry)
                                 ? new IncludeFindResult(cacheEntry)
                                 : new IncludeFindResult(file, includeDirectory);
                    return(true);
                }
            }

            log.LogWarning($"Could not find include {current.Include}. Possible types from these files will not be parsed.");
            result = null;
            return(false);
        }
        public IEnumerable <CodeSpecificException> InitializeCodeModel(CppCodeModel codeModel, IncludeManagerParameter parameter)
        {
            HashSet <string>             parsedIncludes         = new HashSet <string>(parameter.KnownIncludes);
            List <CodeSpecificException> codeSpecificExceptions = new List <CodeSpecificException>();
            VirtualFile cacheFile = fileSystem.GetFile(IncludeFilePath.CleanPath().ResolvePathName(environmentService.PathNames));

            includeCache.LoadCache(cacheFile);

            UpdateCache();

            codeModel.IncludeDirectories = parameter.IncludeDirectories;

            codeModel.RegisterIncludeTypeFinder(s => FindIncludeType(s, parameter.IncludeDirectories));
            this.codeModel = codeModel;

            return(codeSpecificExceptions);

            void UpdateCache()
            {
                log.LogVerbose("Start updating the include cache.");
                Stopwatch loadWatch = new Stopwatch();

                loadWatch.Start();
                using (IIncludeCacheTransaction cacheTransaction = includeCache.StartTransaction())
                {
                    Stopwatch checkWatch = new Stopwatch();
                    checkWatch.Start();
                    foreach (IncludeCacheEntry staleEntry in FindStaleAndOutdatedEntries().ToArray())
                    {
                        cacheTransaction.DeleteEntry(staleEntry);
                    }
                    checkWatch.Stop();
                    log.LogVerbose($"Checked files in {checkWatch.ElapsedMilliseconds} ms.");

                    AddOrUpdate(parameter.IncludeDefinitions, cacheTransaction);
                }
                loadWatch.Stop();
                log.LogVerbose($"Finished updating the include cache in {loadWatch.ElapsedMilliseconds} ms.");

                IEnumerable <IncludeCacheEntry> FindStaleAndOutdatedEntries()
                {
                    return(includeCache.Entries.Where(e => !fileSystem.FileExists(e.File) ||
                                                      fileSystem.GetLastWriteTime(e.File) != e.LastWriteTime));
                }

                void AddOrUpdate(IEnumerable <IncludeDefinition> includes, IIncludeCacheTransaction cacheTransaction)
                {
                    Stack <IncludeDefinition> unvisited = new Stack <IncludeDefinition>(includes);

                    while (unvisited.Any())
                    {
                        IncludeDefinition include = unvisited.Pop();
                        if (TryFindInclude(include, out IncludeFindResult findResult))
                        {
                            IEnumerable <IncludeDefinition> childDefinitions;
                            if (findResult.IsInCache)
                            {
                                VirtualFile      cacheSourceFile    = fileSystem.GetFile(findResult.IncludeCacheEntry.File);
                                VirtualDirectory cacheBaseDirectory = fileSystem.GetDirectory(findResult.IncludeCacheEntry.BaseDirectory);
                                parsedIncludes.Add(cacheSourceFile.GetRelativePath(cacheBaseDirectory));
                                childDefinitions = findResult.IncludeCacheEntry.Includes
                                                   .Select(i => new IncludeDefinition(i, cacheSourceFile,
                                                                                      cacheBaseDirectory));
                            }
                            else
                            {
                                parsedIncludes.Add(findResult.IncludeFile.GetRelativePath(findResult.IncludeBaseDirectory));
                                childDefinitions = ProcessInclude(findResult.IncludeFile, findResult.IncludeBaseDirectory);
                            }

                            foreach (IncludeDefinition childDefinition in childDefinitions)
                            {
                                unvisited.Push(childDefinition);
                            }
                        }
                    }

                    IEnumerable <IncludeDefinition> ProcessInclude(VirtualFile findResultIncludeFile, VirtualDirectory findResultIncludeBaseDirectory)
                    {
                        ParserResult parserResult = fileParser.Parse(findResultIncludeFile);

                        if (!parserResult.Success)
                        {
                            codeSpecificExceptions.AddRange(parserResult.Exceptions);
                            cacheTransaction.AddEntry(new IncludeCacheEntry(findResultIncludeFile.FullName,
                                                                            false,
                                                                            findResultIncludeFile.LastWriteTime,
                                                                            findResultIncludeBaseDirectory.FullName,
                                                                            Enumerable.Empty <string>(),
                                                                            Enumerable.Empty <string>()));
                            return(Enumerable.Empty <IncludeDefinition>());
                        }

                        foreach (IType type in parserResult.Types.Keys)
                        {
                            codeModel.AddType(type, findResultIncludeFile, findResultIncludeBaseDirectory);
                        }

                        codeSpecificExceptions.AddRange(parserResult.Exceptions);
                        cacheTransaction.AddEntry(new IncludeCacheEntry(findResultIncludeFile.FullName,
                                                                        true,
                                                                        findResultIncludeFile.LastWriteTime,
                                                                        findResultIncludeBaseDirectory.FullName,
                                                                        parserResult.Types.Keys.Select(t => t.FullName),
                                                                        parserResult.Includes));
                        return(parserResult.Includes.Select(
                                   i => new IncludeDefinition(
                                       i, findResultIncludeFile, findResultIncludeBaseDirectory)));
                    }

                    bool TryFindInclude(IncludeDefinition current, out IncludeFindResult result)
                    {
                        if (current.Include.Trim().StartsWith("<", StringComparison.Ordinal))
                        {
                            //system includes in form <algorithms> are ignored
                            result = null;
                            return(false);
                        }

                        //Try parse relative to source
                        if (current.DefinitionSourceFile.Parent.TryGetFileFromPath(current.Include, out VirtualFile file))
                        {
                            if (parsedIncludes.Contains(file.GetRelativePath(current.DefinitionSourceBaseDirectory)))
                            {
                                result = null;
                                return(false);
                            }

                            result = includeCache.TryGetCacheEntry(file.FullName, out IncludeCacheEntry cacheEntry)
                                         ? new IncludeFindResult(cacheEntry)
                                         : new IncludeFindResult(file, current.DefinitionSourceBaseDirectory);
                            return(true);
                        }

                        //Parse relative to include path
                        foreach (VirtualDirectory includeDirectory in parameter.IncludeDirectories.Values.Where(v => v != null))
                        {
                            if (includeDirectory.TryGetFileFromPath(current.Include, out file))
                            {
                                if (parsedIncludes.Contains(file.GetRelativePath(includeDirectory)))
                                {
                                    result = null;
                                    return(false);
                                }

                                result = includeCache.TryGetCacheEntry(file.FullName, out IncludeCacheEntry cacheEntry)
                                             ? new IncludeFindResult(cacheEntry)
                                             : new IncludeFindResult(file, includeDirectory);
                                return(true);
                            }
                        }

                        log.LogWarning($"Could not find include {current.Include}. Possible types from these files will not be parsed.");
                        result = null;
                        return(false);
                    }
                }
            }
        }