Created
June 16, 2014 09:01
-
-
Save citizenmatt/a9dcf3872c81d7495639 to your computer and use it in GitHub Desktop.
Simple abstract base class to provide a boilerplate implemention of ReSharper's ICache
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using JetBrains.Application; | |
using JetBrains.Application.Progress; | |
using JetBrains.DocumentManagers.impl; | |
using JetBrains.ProjectModel; | |
using JetBrains.ReSharper.Psi; | |
using JetBrains.ReSharper.Psi.Caches; | |
using JetBrains.ReSharper.Psi.Tree; | |
using JetBrains.ReSharper.Psi.Util.Caches; | |
using JetBrains.Util; | |
namespace Foo | |
{ | |
public abstract class SimpleCacheBase<TCacheData> : ICache | |
where TCacheData : class | |
{ | |
private readonly ISolution solution; | |
private readonly int cacheFormatVersion; | |
private readonly string cacheName; | |
private readonly IShellLocks shellLocks; | |
private readonly IPsiConfiguration psiConfiguration; | |
private readonly IPersistentIndexManager persistentIndexManager; | |
private readonly object myLock = new object(); | |
private readonly JetHashSet<IPsiSourceFile> dirtyFiles = new JetHashSet<IPsiSourceFile>(); | |
private SimplePersistentCache<TCacheData> persistentCache; | |
protected SimpleCacheBase(ISolution solution, IShellLocks shellLocks, | |
IPsiConfiguration psiConfiguration, IPersistentIndexManager persistentIndexManager, | |
int cacheFormatVersion, string cacheName) | |
{ | |
this.solution = solution; | |
this.cacheFormatVersion = cacheFormatVersion; | |
this.cacheName = cacheName; | |
this.shellLocks = shellLocks; | |
this.psiConfiguration = psiConfiguration; | |
this.persistentIndexManager = persistentIndexManager; | |
} | |
public void MarkAsDirty(IPsiSourceFile sourceFile) | |
{ | |
lock (myLock) | |
dirtyFiles.Add(sourceFile); | |
} | |
public object Load(IProgressIndicator progress, bool enablePersistence) | |
{ | |
if (!enablePersistence || cacheFormatVersion < 0) | |
return null; | |
Assertion.Assert(persistentCache == null, "persistentCache == null"); | |
persistentCache = new SimplePersistentCache<TCacheData>(shellLocks, | |
cacheFormatVersion, cacheName, psiConfiguration); | |
if (persistentCache.Load(progress, persistentIndexManager, ReadData, Merge) != LoadResult.OK) | |
{ | |
lock (myLock) | |
{ | |
ClearCache(); | |
return null; | |
} | |
} | |
// Any returned value will be passed to MergeLoaded, but we've already | |
// merged as part of the load above | |
return null; | |
} | |
public void MergeLoaded(object data) | |
{ | |
// Can be called to merge a data item returned from Load. We don't return | |
// anything, but handle merging as part of loading. So this shouldn't | |
// get called. Instead, we mark the file as dirty for ALL caches | |
// TODO: I don't know why we do this (copied from ConditionalNamesCache) | |
foreach (var sourceFile in dirtyFiles) | |
solution.GetPsiServices().Caches.MarkAsDirty(sourceFile); | |
} | |
public void Save(IProgressIndicator progress, bool enablePersistence) | |
{ | |
if (!enablePersistence || cacheFormatVersion < 0) | |
return; | |
Assertion.Assert(persistentCache != null, "persistentCache != null"); | |
lock (myLock) | |
{ | |
persistentCache.Save(progress, persistentIndexManager, WriteData); | |
} | |
// Apparently, Save also implies throws away | |
// TODO: Find out why | |
persistentCache.Dispose(); | |
persistentCache = null; | |
} | |
public bool UpToDate(IPsiSourceFile sourceFile) | |
{ | |
if (!Accepts(sourceFile)) | |
return true; | |
lock (myLock) | |
return !dirtyFiles.Contains(sourceFile) && IsCached(sourceFile); | |
} | |
public object Build(IPsiSourceFile sourceFile, bool isStartup) | |
{ | |
return BuildData(sourceFile); | |
} | |
public void Merge(IPsiSourceFile sourceFile, object builtPart) | |
{ | |
var data = (TCacheData) builtPart; | |
lock (myLock) | |
{ | |
AddFileToCache(sourceFile, data); | |
if (persistentCache != null) | |
persistentCache.AddDataToSave(sourceFile, data); | |
dirtyFiles.Remove(sourceFile); | |
} | |
} | |
public void Drop(IPsiSourceFile sourceFile) | |
{ | |
lock (myLock) | |
{ | |
RemoveFileFromCache(sourceFile); | |
if (persistentCache != null) | |
persistentCache.MarkDataToDelete(sourceFile); | |
} | |
} | |
public void OnPsiChange(ITreeNode elementContainingChanges, PsiChangedElementType type) | |
{ | |
if (elementContainingChanges != null) | |
{ | |
var sourceFile = elementContainingChanges.GetSourceFile(); | |
if (sourceFile != null) | |
MarkAsDirty(sourceFile); | |
} | |
} | |
public void OnDocumentChange(IPsiSourceFile sourceFile, ProjectFileDocumentCopyChange change) | |
{ | |
MarkAsDirty(sourceFile); | |
} | |
public void SyncUpdate(bool underTransaction) | |
{ | |
if (underTransaction) | |
return; | |
lock (myLock) | |
{ | |
foreach (var sourceFile in dirtyFiles.ToList()) | |
{ | |
if(sourceFile.IsValid()) | |
Merge(sourceFile, BuildData(sourceFile)); | |
} | |
dirtyFiles.Clear(); | |
} | |
} | |
public bool HasDirtyFiles | |
{ | |
get | |
{ | |
lock (myLock) | |
return !dirtyFiles.IsEmpty(); | |
} | |
} | |
protected virtual bool Accepts(IPsiSourceFile sourceFile) | |
{ | |
using(ReadLockCookie.Create()) | |
{ | |
var projectFile = sourceFile.ToProjectFile(); | |
if (projectFile == null) return false; | |
return sourceFile.Properties.ShouldBuildPsi; | |
} | |
} | |
protected abstract TCacheData BuildData(IPsiSourceFile sourceFile); | |
protected abstract TCacheData ReadData(IPsiSourceFile sourceFile, BinaryReader reader); | |
protected abstract void WriteData(BinaryWriter writer, IPsiSourceFile sourceFile, TCacheData data); | |
protected abstract void AddFileToCache(IPsiSourceFile sourceFile, TCacheData data); | |
protected abstract void RemoveFileFromCache(IPsiSourceFile sourceFile); | |
protected abstract bool IsCached(IPsiSourceFile sourceFile); | |
protected abstract void ClearCache(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment