Skip to content

Instantly share code, notes, and snippets.

@citizenmatt
Created June 16, 2014 09:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save citizenmatt/a9dcf3872c81d7495639 to your computer and use it in GitHub Desktop.
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
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