What is the best strategy to cache PsiClass objects ?

Hello community,

I am working on a plugin to make IDEA aware of classes and interfaces generated by an annotation processor without building the project.

I already implemented the basic required classes to do that (thanks to Colin Fleming reply to my previous post).

Now, I would like to cache the generated PsiClass because they do not change as long as the file/class from the which the generated PsiClass does not change either.

Filebased-indexes seems to be the right tool for the job as they already implement the logic of refreshing their content when a VirtualFile changes. But having PsiClass as value requires to be able to serialize them to a binary format. Is it a good idea at all (serializing a PsiClass)? Does a PsiClass serializer implementation already exist somewhere?

I had a look at stub indexes but after looking at the doc and deeply at the source code I figured I cannot use one because my generated classes/interfaces are not backed by their own VirtualFile.

I thought about implementing a handradfted cache as Project component but, looking at the code of the StubIndexImpl class, I see that keeping a cache up to date with a VirtualFile content is a tricky job. I would rather leverage the existing solutions.

Thank you in advance for any insight.

Sébastien Lesaint

4 comments
Comment actions Permalink

Hi Sébastien,

Your best bet would be to use the CachedValuesManager for this. You can create a cached value and attach it either to the PsiFile or to a PsiElement which represents your class. Make sure you add the dependencies correctly to your Result so that the cache is invalidated at the correct times.

BTW another class that's very useful for implementing PsiClasses is ClassInnerStuffCache - this will internally cache a lot of the objects required (fields, methods etc) and has utility methods to implement a lot of the required methods of PsiClass. Highly recommended.

Cheers,
Colin

1
Comment actions Permalink

See also this previous thread about caching: http://devnet.jetbrains.com/message/5492020


0
Comment actions Permalink

Hi Colin,

Thanks for your reply. I'm going to look into CachedValuesManager and will let you know how it works for me.

Regarding ClassInnerStuffCache, as I am currently not programmatically creating my PsiClass instances but using PsiFileFactory.getInstance(project).createFileFromText, I don't think I can use it.

I guess using PsiFileFactory.getInstance(project).createFileFromText is not the most efficient way of creating PsiClass objects but as for know it is a very convenient way of using the exact same code as the Annotation Processor for which I am creating the plugin.

Cheers,

Sébastien Lesaint

0
Comment actions Permalink

Hi Colin,

CachedValuesManager was indeed what I needed and using it wasn't obvious at first but it turned out quite easy after I found several examples in IDEA's and Kotlin source code.

Basically, here is how I used it, very little code needed:

I'm using the CachedValuesManager to cache a list of the PsiClass of the classes/interfaces generated by the AnnotationProcessor for each PsiClass of a class annoted with a specific annotation.

I went for a ParameterizedCachedValue instead of regular CachedValue because the value depends on the PsiClass of the annoted class. I disabled tracking because the generated classes can not change on their own.

  private static final Key<ParameterizedCachedValue<List<PsiClass>, PsiClass>> DAMAPPING_GENERATED_CLASSES_KEY = Key.create("DAMAPPING_GENERATED_CLASSES");

  @NotNull
  public List<PsiClass> getGeneratedPsiClasses(@NotNull PsiClass psiClass, @NotNull GlobalSearchScope scope) {
    CachedValuesManager manager = CachedValuesManager.getManager(scope.getProject());

    List<PsiClass> res = manager.getParameterizedCachedValue(psiClass,
           DAMAPPING_GENERATED_CLASSES_KEY, new GeneratedPsiClassCachedValueProvider(), false, psiClass
    );
    return res;

  }

  private class GeneratedPsiClassCachedValueProvider implements ParameterizedCachedValueProvider<List<PsiClass>, PsiClass> {
    @Nullable
    @Override
    public CachedValueProvider.Result<List<PsiClass>> compute(PsiClass param) {
      Optional<GenerationContext> generationContext = computeGenerationContext(param);
      if (!generationContext.isPresent()) {
        return CachedValueProvider.Result.create(Collections.<PsiClass>emptyList(), param);
      }


      Set<String> keys = generationContext.get().getDescriptorKeys();
      List<PsiClass> res = new ArrayList<PsiClass>(keys.size());
      for (String key : keys) {
        Optional<PsiClass> psiClass1 = getGeneratedPsiClass(generationContext.get(), key, param.getProject());
        if (psiClass1.isPresent()) {
          res.add(psiClass1.get());
        }
      }

      return CachedValueProvider.Result.create(res, param);
    }
  }


Thanks again Colin for your help,

Sébastien Lesaint

0

Please sign in to leave a comment.