Why I get IllegalArgumentException( Argument for @NotNull parameter 'manager' of com/intellij/psi/impl/source/tree/JavaSourceUtil.addParenthToReplacedChild must not be null)?
I'm currently developing a plugin that search variables with "public" accessor and make them private and replace all references to getter / setter of variables.
e.g.
public int foo = 1;
and there is reference "println(foo)"
then simply add @Getter to variable declaration statement with changing public accessor to private accesor and replace println(foo) to println(getFoo())
Result:
@Getter private int foo = 1;
println(getFoo());
To achieve this, I wrote those codes:
package io.github.singlerr.gsetterizer.actions
import com.intellij.find.findUsages.JavaFindUsagesHelper
import com.intellij.find.findUsages.JavaVariableFindUsagesOptions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.util.ProgressWindow
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.ProjectCoreUtil
import com.intellij.psi.*
import com.intellij.psi.impl.PsiJavaParserFacadeImpl
import com.intellij.psi.impl.PsiManagerEx
import com.intellij.psi.impl.source.tree.TreeElement
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.elementType
import io.github.singlerr.gsetterizer.utils.generateGetter
import io.github.singlerr.gsetterizer.utils.generateSetter
import io.github.singlerr.gsetterizer.utils.walk
import io.github.singlerr.gsetterizer.visitor.JavaRecursiveElementWalkingAccumulator
import io.github.singlerr.gsetterizer.visitor.JavaReferenceInfo
import io.github.singlerr.gsetterizer.visitor.JavaVariableInfo
const val PROJECT_SCOPE = "ProjectViewPopup"
const val EDITOR_SCOPE = "EditorPopup"
class GSetterizeAction : AnAction() {
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = !(e.place != PROJECT_SCOPE || e.place != EDITOR_SCOPE) || e.project != null
}
/**
* Implement this method to provide your action handler.
*
* @param e Carries information on the invocation place
*/
override fun actionPerformed(e: AnActionEvent) {
val manager = PsiManager.getInstance(e.project!!)
val currentFile = e.dataContext.getData(PlatformDataKeys.VIRTUAL_FILE)!!
/***
* <Executed in ReadAction>
* Visit variable references and save to container
*/
val visitor = object : JavaRecursiveElementWalkingAccumulator() {
override fun visitVariable(variable: PsiVariable?) {
if (variable?.parent !is PsiClass) return
val accessor = variable.children.find { e -> e.elementType?.debugName == "MODIFIER_LIST" } ?: return
if (!accessor.text.contains("public") || accessor.text.contains("final")) return
val progressWindow = ProgressWindow(false, e.project)
progressWindow.title = "Processing"
val containingClass = variable.context as PsiClass
val javaVariableInfo = JavaVariableInfo(variable, HashSet(), accessor, variable.type == PsiType.BOOLEAN)
ProgressManager.getInstance().runProcess({
DumbService.getInstance(e.project!!).runReadActionInSmartMode {
/**
* Find write access references and add to container.
*/
JavaFindUsagesHelper.processElementUsages(
variable,
JavaVariableFindUsagesOptions(e.project!!).apply {
isReadAccess = false
isWriteAccess = true
}) { usageInfo ->
println("Found : ${usageInfo.element?.text}")
if (usageInfo.element?.parent is PsiAssignmentExpression)
javaVariableInfo.references.add(
JavaReferenceInfo(
usageInfo.element!!,
false,
usageInfo.element?.parent!!
)
)
true
}
/**
* Find read access references and add to container.
*/
JavaFindUsagesHelper.processElementUsages(
variable,
JavaVariableFindUsagesOptions(e.project!!).apply {
isReadAccess = true
isWriteAccess = false
}) { usageInfo ->
println("Found : ${usageInfo.element?.text}")
javaVariableInfo.references.add(
JavaReferenceInfo(
usageInfo.element!!,
true,
usageInfo.element?.parent!!,
usageInfo.element?.children?.find { e ->
e is PsiIdentifier && e.text.equals(
variable.name
)
} as PsiIdentifier?
)
)
true
}
}
}, progressWindow)
visitVariable(containingClass, javaVariableInfo)
}
}
/**
* Lambda for adding @Getter annotation to variable declaration statement.
*/
val addGetter: (impl: PsiJavaParserFacade, javaVariable: JavaVariableInfo) -> Unit = { impl, variable ->
variable.accessor.replace(
impl.createStatementFromText(
variable.accessor.text.replace("public", "private"), variable.psiVariable
)
)
variable.psiVariable.addBefore(
impl.createAnnotationFromText("@Getter", variable.psiVariable),
variable.psiVariable.children.first()
)
}
/**
* Lambda for adding @Setter annotation to variable declaration statement.
*/
val addSetter: (impl: PsiJavaParserFacade, javaVariable: JavaVariableInfo) -> Unit = { impl, variable ->
variable.accessor.replace(
impl.createStatementFromText(
variable.accessor.text.replace("public", "private"), variable.psiVariable
)
)
variable.psiVariable.addBefore(
impl.createAnnotationFromText("@Setter", variable.psiVariable),
variable.psiVariable.children.first()
)
}
/**
* Iterate all variable references and replace expression(direct field access) to getter / setter expression.
*/
val gSetterize: (psiClass: PsiClass, javaVariable: JavaVariableInfo) -> Unit = { psiClass, variable ->
val impl = PsiJavaParserFacadeImpl(e.project!!)
/**
* Replace read access to getter
*/
val resolveGetter: (ref: JavaReferenceInfo) -> Unit = { ref ->
val getterExpressionRaw = generateGetter(
ref.identifier!!.text,
variable.isBoolean
).plus(if (ref.element !is PsiImportStaticReferenceElement) "()" else "")
val newExpression = impl.createExpressionFromText(getterExpressionRaw, psiClass)
PsiDocumentManager.getInstance(e.project!!).commitAllDocuments()
/**
* Target that will be replaced to getter
*/
/***
* ERROR OCCURRED
* Argument for @NotNull parameter 'manager' of com/intellij/psi/impl/source/tree/JavaSourceUtil.addParenthToReplacedChild must not be null
*/
ref.identifier.replace(newExpression)
variable.needGetter = true
}
/**
* Replace write access to setter
*/
val resolveSetter: (ref: JavaReferenceInfo) -> Unit = { ref ->
val parentExpression = ref.parent as PsiAssignmentExpression
/**
* Target that will be replaced to setter
*/
val identifier = parentExpression.lExpression.children.find { e -> e is PsiIdentifier } as PsiIdentifier
/**
* Generate expression text.
*/
val setterExpressionRaw =
generateSetter(identifier.text, variable.isBoolean).plus("(${parentExpression.rExpression?.text})")
/**
* In shape "foo = bar"
* It will remove operationSign(=) and rExpression
*/
parentExpression.operationSign.delete()
parentExpression.rExpression?.delete()
val newExpression = impl.createExpressionFromText(setterExpressionRaw, variable.psiVariable)
identifier.replace(newExpression)
variable.needSetter = true
}
variable.references.forEach { ref ->
DumbService.getInstance(e.project!!).smartInvokeLater {
WriteCommandAction.runWriteCommandAction(e.project!!) {
/**
* Import @Getter,@Setter annotation class
*/
val getterClass = JavaPsiFacade.getInstance(e.project!!)
.findClass("lombok.Getter", GlobalSearchScope.allScope(e.project!!))
val setterClass = JavaPsiFacade.getInstance(e.project!!)
.findClass("lombok.Setter", GlobalSearchScope.allScope(e.project!!))
if (getterClass != null && setterClass != null) {
val javaFile = psiClass.containingFile as PsiJavaFile?
javaFile?.importClass(getterClass)
javaFile?.importClass(setterClass)
}
/**
* If a reference is read access then resolveGetter else resolveSetter
*/
if (ref.valueRead) resolveGetter(ref)
else resolveSetter(ref)
}
}
}
/***
* Add @Getter @Setter annotation to variable declaration
*/
DumbService.getInstance(e.project!!).smartInvokeLater {
WriteCommandAction.runWriteCommandAction(e.project!!) {
if (variable.needGetter)
addGetter(impl, variable)
if (variable.needSetter)
addSetter(impl, variable)
}
}
}
ApplicationManager.getApplication().executeOnPooledThread {
if (e.place == PROJECT_SCOPE) {
/**
* Iterate all java source files
*/
val srcFiles = walk(currentFile) {
it.name.endsWith(".java")
}
/**
* Convert virtual file to psi file.
*/
val psiFiles = srcFiles.mapNotNull {
ApplicationManager.getApplication().runReadAction<PsiFile> {
manager.findFile(it)
}
}
psiFiles.forEach {
ApplicationManager.getApplication().runReadAction {
it.accept(visitor)
}
}
visitor.visitedVariables.forEach { (_, classInfo) ->
classInfo.variables.forEach { variable -> gSetterize(classInfo.psiClass, variable) }
}
} else {
val psiFile = ApplicationManager.getApplication().runReadAction<PsiFile> {
manager.findFile(currentFile)
}
ApplicationManager.getApplication().runReadAction {
psiFile.accept(visitor)
}
visitor.visitedVariables.forEach { (_, classInfo) ->
classInfo.variables.forEach { variable -> gSetterize(classInfo.psiClass, variable) }
}
}
}
}
}
package io.github.singlerr.gsetterizer.visitor
import com.intellij.psi.JavaRecursiveElementWalkingVisitor
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiIdentifier
import com.intellij.psi.PsiReference
import com.intellij.psi.PsiVariable
abstract class JavaRecursiveElementWalkingAccumulator : JavaRecursiveElementWalkingVisitor(){
val visitedVariables = HashMap<String,JavaClassInfo>()
fun visitVariable(psiClass:PsiClass, variable:JavaVariableInfo){
if(visitedVariables.containsKey(psiClass.qualifiedName!!))
visitedVariables[psiClass.qualifiedName!!]?.variables?.add(variable)
else
visitedVariables[psiClass.qualifiedName!!] = JavaClassInfo(psiClass,HashSet()).apply {
variables.add(variable)
}
}
}
data class JavaClassInfo(val psiClass:PsiClass, val variables:MutableSet<JavaVariableInfo>)
data class JavaVariableInfo(val psiVariable: PsiVariable,val references:MutableSet<JavaReferenceInfo>, val accessor:PsiElement, val isBoolean:Boolean, var needGetter:Boolean = false, var needSetter:Boolean = false)
data class JavaReferenceInfo(val element: PsiElement, val valueRead:Boolean, val parent:PsiElement,val identifier:PsiIdentifier? = null)
But I got exception randomly:
2022-08-03 15:31:05,348 [ 41928] SEVERE - #c.i.o.a.i.FlushQueue - Argument for @NotNull parameter 'manager' of com/intellij/psi/impl/source/tree/JavaSourceUtil.addParenthToReplacedChild must not be null
java.lang.IllegalArgumentException: Argument for @NotNull parameter 'manager' of com/intellij/psi/impl/source/tree/JavaSourceUtil.addParenthToReplacedChild must not be null
at com.intellij.psi.impl.source.tree.JavaSourceUtil.$$$reportNull$$$0(JavaSourceUtil.java)
at com.intellij.psi.impl.source.tree.JavaSourceUtil.addParenthToReplacedChild(JavaSourceUtil.java)
at com.intellij.psi.impl.source.tree.java.ExpressionPsiElement.replaceChildInternal(ExpressionPsiElement.java:20)
at com.intellij.psi.impl.source.tree.SharedImplUtil.doReplace(SharedImplUtil.java:196)
at com.intellij.psi.impl.source.tree.LeafPsiElement.replace(LeafPsiElement.java:198)
at io.github.singlerr.gsetterizer.actions.GSetterizeAction$actionPerformed$gSetterize$1$resolveGetter$1.invoke(GSetterizeAction.kt:145)
at io.github.singlerr.gsetterizer.actions.GSetterizeAction$actionPerformed$gSetterize$1$resolveGetter$1.invoke(GSetterizeAction.kt:138)
at io.github.singlerr.gsetterizer.actions.GSetterizeAction$actionPerformed$gSetterize$1.invoke$lambda-2$lambda-1$lambda-0(GSetterizeAction.kt:178)
at com.intellij.openapi.command.WriteCommandAction.lambda$runWriteCommandAction$4(WriteCommandAction.java:362)
at com.intellij.openapi.command.WriteCommandAction$BuilderImpl.lambda$doRunWriteCommandAction$1(WriteCommandAction.java:150)
at com.intellij.openapi.application.impl.ApplicationImpl.runWriteAction(ApplicationImpl.java:1015)
at com.intellij.openapi.command.WriteCommandAction$BuilderImpl.lambda$doRunWriteCommandAction$2(WriteCommandAction.java:148)
at com.intellij.openapi.command.impl.CoreCommandProcessor.executeCommand(CoreCommandProcessor.java:219)
at com.intellij.openapi.command.impl.CoreCommandProcessor.executeCommand(CoreCommandProcessor.java:184)
at com.intellij.openapi.command.WriteCommandAction$BuilderImpl.doRunWriteCommandAction(WriteCommandAction.java:157)
at com.intellij.openapi.command.WriteCommandAction$BuilderImpl.run(WriteCommandAction.java:124)
at com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction(WriteCommandAction.java:362)
at com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction(WriteCommandAction.java:350)
at io.github.singlerr.gsetterizer.actions.GSetterizeAction$actionPerformed$gSetterize$1.invoke$lambda-2$lambda-1(GSetterizeAction.kt:167)
at com.intellij.openapi.project.DumbServiceImpl.lambda$smartInvokeLater$9(DumbServiceImpl.java:533)
at com.intellij.openapi.application.TransactionGuardImpl.runWithWritingAllowed(TransactionGuardImpl.java:215)
at com.intellij.openapi.application.TransactionGuardImpl.access$100(TransactionGuardImpl.java:22)
at com.intellij.openapi.application.TransactionGuardImpl$1.run(TransactionGuardImpl.java:197)
at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:873)
at com.intellij.openapi.application.impl.ApplicationImpl$3.run(ApplicationImpl.java:511)
at com.intellij.openapi.application.impl.FlushQueue.doRun(FlushQueue.java:69)
at com.intellij.openapi.application.impl.FlushQueue.runNextEvent(FlushQueue.java:112)
at com.intellij.openapi.application.impl.FlushQueue.flushNow(FlushQueue.java:42)
at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:776)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:727)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:746)
at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:898)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:746)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$6(IdeEventQueue.java:439)
at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:803)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$7(IdeEventQueue.java:438)
at com.intellij.openapi.application.TransactionGuardImpl.performActivity(TransactionGuardImpl.java:106)
at com.intellij.ide.IdeEventQueue.performActivity(IdeEventQueue.java:604)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$8(IdeEventQueue.java:436)
at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:873)
at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:484)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:207)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:128)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:117)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:105)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:92)
2022-08-03 15:31:05,352 [ 41932] SEVERE - #c.i.o.a.i.FlushQueue - IntelliJ IDEA 2022.1 Build #IC-221.5080.210
2022-08-03 15:31:05,352 [ 41932] SEVERE - #c.i.o.a.i.FlushQueue - JDK: 11.0.14.1; VM: OpenJDK 64-Bit Server VM; Vendor: JetBrains s.r.o.
2022-08-03 15:31:05,352 [ 41932] SEVERE - #c.i.o.a.i.FlushQueue - OS: Windows 10
2022-08-03 15:31:05,353 [ 41933] SEVERE - #c.i.o.a.i.FlushQueue - Plugin to blame: GSetterizer version: 1.2-SNAPSHOT
2022-08-03 15:41:02,349 [ 638929] WARN - #c.i.u.x.Binding - no accessors for org.jetbrains.idea.perforce.perforce.ConnectionId
Not all of references throw this exception, but some references throw it, even though they are pointing the same variable.
And references throwing this exception change everytime I test the plugin
I researched a lot about this exception, but I couldn't find nothing. What's wrong with those codes?
Please sign in to leave a comment.
Hi Ryu,
It is hard to say what the reason is as the code is pretty complex and hard to analyze.
From the stacktrace, it seems that com.intellij.psi.impl.source.tree.TreeElement#getManager() returns null for the manager and this is the direct reason. The actual reason may be that you use PSI elements that are outdated because files were reparsed between the read/write action sessions. I suggest trying to use less granular read/write actions and split them when it actually causes performance issues.
Do you mean that when I execute write command to commit psi element replacement, then psi elements in the same java file that stored in my container class(JavaReferenceInfo class above) become outdated?
If correct, then after excuting write command, is necessary executing read action again to update psi elements?
Thank you for replying.
Yes, this is what I mean, but I'm not sure about it as it is difficult to understand the code flow. You can verify it by checking what ref.identifier.isValid() returns before the replacement.
Then there is a way to update psi elements? Just run JavaRecursiveElementWalkingVisitor again?
I would try with one of the:
Thank you for help!