Invalid PsiJavaFile in BulkFileListener.after() after file deletion event
I have an implementation of BulkFileListener in which I listen for deletion events in order to:
1. Find all files that reference classes defined in the file being deleted (let's call them "affected sources"). I achieve this by analysing the file in before(), which works fine.
2. Process affected sources in after(), where I do get the exception.
Let's go into more details about stage 2. Say I'm processing one of affected sources that contains a field of a type which was defined in the deleted file. Assume the following files exist and A.java gets deleted:
// A.java (will be deleted)
class A extends X {}
// B.java (affected by A.java)
class B { A a; }
My processing logic scans class fields and analyses their types. In this case it would look at field `a` and presumably encounter a reference to a type that doesn't exist anymore, because A.java was deleted. When I conduct this experiment in a state where A.java doesn't exist apriori the following happens:
PsiField a = ...; // field 'a' in class B
PsiType A = a.getType() // returns PsiClassReferenceType that is valid (isValid() returns true)
((PsiClassReferenceType) A).resolve() // returns null
As you can see it works as expected: resolve() returns null because PsiClass for A doesn't exist.
However, when I do the same just after deleting A.java - when my BulkFileListener reacts to the deletion event - this happens:
PsiField a = ...; // field 'a' in class B
PsiType A = a.getType(); // returns PsiClassReferenceType that is valid (isValid() returns true)
PsiClass classA = ((PsiClassRefernceType) A).resolve(); // returns PsiClass for A
PsiFile fileA = PsiClass.getContainingFile(); // returns PsiJavaFile for A.java
fileA.isValid(); // false
Remember that this is happening in BulkFileListener.after() so I would expect that A.java has been deleted already. PsiInvalidElementAccessException occurs because my processing logic assumes that all PsiClass instances it works with come from valid PsiJavaFile-s, so when it gets to analyse supertypes of A the exception is thrown.
Also, I should mention that code in after() is running on a pooled thread, but I'm not sure if that's significant.
Given the above, how do I make sure that the PSI is in a consistent state when I'm inside the after() method to avoid dealing with invalidated PsiJavaFile instances? I suspect that they could still be cached somewhere, but that's just a guess.
请先登录再写评论。
I found a workaround. It invloves reparsing PsiJavaFile-s (affected sources mentioned in the OP) in after() using FileContentUtil.reparseFiles() and then refetching them by their corresponding VirtualFile using PsiManager.findFile(). The refetched instances behave as expected: from the example in the OP - type A of field a resolves to null.
As a sidenote, I tried storing VirtualFile-s themselves instead of PsiJavaFile-s suspecting that existing references to PsiJavaFile-s might be the culprit but to no avail. Unless explicitly reparsed the PSI remains in uncosistent state.
Could you please explain what is the use-case of this detection? Maybe there's more appropriate API than using plain VFS - which is _very_ low-level and needs careful implementation, as can be seen from the issues you encountered.
I'm creating a plugin for annotation processing. It's essential that annotation processing kicks in immediatly after a file is saved/moved/renamed/deleted. For this reason the standard annotation processing that comes with javac does not suffice.
The specific use case of the detection describe in the OP is to cover the "delete" file event, which involves looking up sources affected by the deleted file (inspired by annotation processing in Eclipse). I would be glad to know if there indeed was a more appropriate API than using plain VFS.
One more thing regarding the BulkFileListener: is it possible to receive file events in groups? From what I've seen events always arrive one at a time, even when they are logically related. For example, the action of renaming a file, which involves a "Refactor" action, leads to that file and its usages being refactored but not saved yet, the user must explicitly perform the save action, which would lead to all of them being saved. Although there is a logical grouping - the renamed file and its usages (affected files), BulkFileListener receives the events one by one.
It would be very convenient to receive such events in groups. Currently, I'm managing to perform the grouping manually by employing the use of RefactoringEventListener and some complicated stateful algorithms.
I’m not aware of better alternatives really.
BulkFileListenerdoes not understand semantics like this, anyway please also consider switching tocom.intellij.openapi.vfs.AsyncFileListener.