diff --git a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml
index 9e9625689bef..43a137e106fa 100644
--- a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml
+++ b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml
@@ -741,7 +741,7 @@
@@ -934,6 +934,7 @@
+
diff --git a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties
index 56162f80ece3..2cf3d2fc440f 100644
--- a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties
+++ b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties
@@ -384,6 +384,8 @@ action.ShowHelpPageById.dialog.enter.help.id=Enter Help ID:
action.ShowHelpPageById.dialog.title=Show Help Page
+action.BFLReqsAction.text=BulkFileListener EDT Checker
+
action.ShowSerializedXml.text=Show Serialized XML for Class
action.ShowSerializedXml.message.caret.must.be.at.class.identifier=Caret should be positioned at the name of the class
action.ShowSerializedXml.message.cannot.find.class=Cannot find class ''{0}''
diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzer.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzer.kt
index 596ee7556104..c875d59f345b 100644
--- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzer.kt
+++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzer.kt
@@ -10,9 +10,9 @@ class LockReqsAnalyzer(private val detector: LockReqsDetector = LockReqsDetector
private data class TraversalContext(
val config: AnalysisConfig,
- val paths: MutableList = mutableListOf(),
val currentPath: MutableList = mutableListOf(),
val visited: MutableSet = mutableSetOf(),
+ val paths: MutableSet = mutableSetOf(),
val messageBusTopics: MutableSet = mutableSetOf(),
val swingComponents: MutableSet = mutableSetOf(),
)
@@ -33,7 +33,7 @@ class LockReqsAnalyzer(private val detector: LockReqsDetector = LockReqsDetector
val annotationRequirements = detector.findAnnotationRequirements(method)
annotationRequirements.forEach { context.paths.add(ExecutionPath(context.currentPath.toList(), it)) }
processMethodBody(method, context)
- context.currentPath.removeAt(context.currentPath.lastIndex)
+ context.currentPath.removeLast()
}
@@ -46,16 +46,17 @@ class LockReqsAnalyzer(private val detector: LockReqsDetector = LockReqsDetector
super.visitMethodCallExpression(expression)
val resolvedMethod = expression.resolveMethod() ?: return
- localRequirements += detector.findBodyRequirements(resolvedMethod, expression)
- if (localRequirements.any { it.requirementReason == RequirementReason.SWING_COMPONENT }) context.swingComponents.add(MethodSignature.fromMethod(resolvedMethod))
-
+ localRequirements += detector.findBodyRequirements(resolvedMethod)
+ if (localRequirements.any { it.requirementReason == RequirementReason.SWING_COMPONENT }) {
+ context.swingComponents.add(MethodSignature.fromMethod(resolvedMethod))
+ }
handleMethodCall(resolvedMethod, expression, context)
}
override fun visitMethodReferenceExpression(expression: PsiMethodReferenceExpression) {
super.visitMethodReferenceExpression(expression)
(expression.resolve() as? PsiMethod)?.let { resolvedMethod ->
- localRequirements += detector.findBodyRequirements(resolvedMethod, expression)
+ localRequirements += detector.findBodyRequirements(resolvedMethod)
if (!detector.isAsyncBoundary(resolvedMethod)) traverseMethod(resolvedMethod, context)
}
}
@@ -69,7 +70,8 @@ class LockReqsAnalyzer(private val detector: LockReqsDetector = LockReqsDetector
})
localRequirements.forEach { requirement ->
- val path = ExecutionPath(context.currentPath.toList(), requirement, isSpeculative = context.currentPath.any { it.isPolymorphic || it.isMessageBusCall })
+ val path = ExecutionPath(context.currentPath.toList(), requirement,
+ context.currentPath.any { it.isPolymorphic || it.isMessageBusCall })
context.paths.add(path)
}
}
diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsDetector.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsDetector.kt
index 20d314d941b6..d450049e0607 100644
--- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsDetector.kt
+++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsDetector.kt
@@ -20,14 +20,14 @@ class LockReqsDetector(private val patterns: LockReqsPatterns = DefaultLockReqsP
return requirements
}
- fun findBodyRequirements(method: PsiMethod, source: PsiElement): List {
+ fun findBodyRequirements(method: PsiMethod): List {
val requirements = mutableListOf()
val className = method.containingClass?.qualifiedName
val methodName = method.name
patterns.assertionMethods[className]?.get(methodName)?.let { lockType ->
- requirements.add(LockRequirement(source, lockType, RequirementReason.ASSERTION))
+ requirements.add(LockRequirement(method, lockType, RequirementReason.ASSERTION))
}
- if (isSwingMethod(method)) requirements.add(LockRequirement(source, LockType.EDT, RequirementReason.SWING_COMPONENT))
+ if (isSwingMethod(method)) requirements.add(LockRequirement(method, LockType.EDT, RequirementReason.SWING_COMPONENT))
return requirements
}
diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsModel.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsModel.kt
index bacb0f8280fb..50f1a7e8ada5 100644
--- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsModel.kt
+++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsModel.kt
@@ -46,7 +46,7 @@ data class ExecutionPath(
data class AnalysisResult(
val method: PsiMethod,
- val paths: List,
+ val paths: Set,
val messageBusTopics: Set = emptySet(),
val swingComponents: Set = emptySet(),
)
diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsObsolete.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsObsolete.kt
deleted file mode 100644
index c888d87210f2..000000000000
--- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsObsolete.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
-package org.jetbrains.idea.devkit.threadingModelHelper
-
-import com.intellij.psi.PsiMethod
-import com.intellij.psi.PsiMethodCallExpression
-import com.intellij.psi.JavaRecursiveElementVisitor
-import com.intellij.psi.PsiMethodReferenceExpression
-
-
-@Deprecated("LockReqsAnalyzer")
-class LockReqsObsolete {
-
- companion object {
-
- private const val ASSERT_READ_ACCESS_METHOD = "assertReadAccess"
- private const val THREADING_ASSERTIONS_CLASS = "com.intellij.util.concurrency.ThreadingAssertions"
- private const val REQUIRES_READ_LOCK_ANNOTATION = "com.intellij.util.concurrency.annotations.RequiresReadLock"
- private const val MAX_PATH_DEPTH = 1000
-
- private val ASYNC_METHODS = setOf("invokeLater", "invokeAndWait", "runInEdt")
-
- enum class LockCheckType {
- ANNOTATION, ASSERTION
- }
-
- data class LockRequirement(
- val type: LockCheckType,
- val method: PsiMethod,
- )
-
- data class ExecutionPath(
- val methodChain: List,
- val lockRequirement: LockRequirement,
- ) {
- val pathString: String
- get() = buildString {
- append(methodChain.joinToString(" -> ") {
- "${it.containingClass?.name}.${it.name}"
- })
- append(" -> ")
- when (lockRequirement.type) {
- LockCheckType.ANNOTATION -> append("@RequiresReadLock")
- LockCheckType.ASSERTION -> append("ThreadingAssertions.assertReadAccess()")
- }
- }
- }
-
- data class AnalysisResult(
- val method: PsiMethod,
- val paths: List,
- )
- }
-
-
- private val processed = mutableSetOf()
-
- fun analyzeMethod(method: PsiMethod): List {
- processed.clear()
- val paths = mutableListOf()
- val currentPath = mutableListOf()
- processMethodDFS(method, currentPath, paths)
- return paths
- }
-
- private fun processMethodDFS(
- method: PsiMethod,
- currentPath: MutableList,
- paths: MutableList,
- ) {
-
- if (method in processed || currentPath.size > MAX_PATH_DEPTH) return
- processed.add(method)
- currentPath.add(method)
- findLockChecks(method).forEach { check ->
- paths.add(ExecutionPath(currentPath.toList(), LockRequirement(check, method)))
- }
- getMethodCallees(method).forEach {
- if (!isAsyncMethod(it)) {
- processMethodDFS(it, currentPath, paths)
- }
- }
- currentPath.removeAt(currentPath.lastIndex)
- }
-
- private fun isAsyncMethod(method: PsiMethod): Boolean {
- return method.name in ASYNC_METHODS
- }
-
- private fun getMethodCallees(method: PsiMethod): List {
- val callees = mutableListOf()
- method.body?.accept(object : JavaRecursiveElementVisitor() {
- override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
- super.visitMethodCallExpression(expression)
- expression.resolveMethod()?.let { callees.add(it) }
- }
-
- override fun visitMethodReferenceExpression(expression: PsiMethodReferenceExpression) {
- super.visitMethodReferenceExpression(expression)
- (expression.resolve() as? PsiMethod)?.let { callees.add(it) }
- }
- })
- return callees
- }
-
- private fun findLockChecks(method: PsiMethod): List {
- return buildList {
- if (hasRequiresReadLockAnnotation(method)) add(LockCheckType.ANNOTATION)
- if (hasAssertReadAccessCall(method)) add(LockCheckType.ASSERTION)
- }
- }
-
- private fun hasRequiresReadLockAnnotation(method: PsiMethod): Boolean {
- return method.hasAnnotation(REQUIRES_READ_LOCK_ANNOTATION)
- }
-
- private fun isAssertReadAccess(expression: PsiMethodCallExpression): Boolean {
- return ASSERT_READ_ACCESS_METHOD == expression.methodExpression.referenceName &&
- THREADING_ASSERTIONS_CLASS == expression.resolveMethod()?.containingClass?.qualifiedName
- }
-
- private fun hasAssertReadAccessCall(method: PsiMethod): Boolean {
- var found = false
- method.body?.accept(object : JavaRecursiveElementVisitor() {
- override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
- if (!found) {
- super.visitMethodCallExpression(expression)
- if (isAssertReadAccess(expression)) found = true
- }
- }
- })
- return found
- }
-}
diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsPatterns.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsPatterns.kt
index 2d9fba42f8c9..c5a2d418d237 100644
--- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsPatterns.kt
+++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsPatterns.kt
@@ -38,7 +38,6 @@ class DefaultLockReqsPatterns : LockReqsPatterns {
"com.intellij.util.concurrency.annotations.RequiresEdt" to LockType.EDT,
"com.intellij.util.concurrency.annotations.RequiresBackgroundThread" to LockType.BGT,
"com.intellij.util.concurrency.annotations.RequiresReadLockAbsence" to LockType.NO_READ,
- "org.jetbrains.annotations.RequiresEdt" to LockType.EDT
)
override val edtRequiredPackages: Set = setOf(
diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactoryCompose.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactory.kt
similarity index 87%
rename from plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactoryCompose.kt
rename to plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactory.kt
index 272c7485735e..c58947258fdf 100644
--- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactoryCompose.kt
+++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactory.kt
@@ -1,7 +1,6 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.threadingModelHelper
-import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
@@ -9,7 +8,7 @@ import com.intellij.ui.content.ContentFactory
import org.jetbrains.idea.devkit.threadingModelHelper.ui.LockReqsToolWindow
import org.jetbrains.jewel.bridge.JewelComposePanel
-class LockReqsToolWindowFactoryCompose : ToolWindowFactory, DumbAware {
+class LockReqsToolWindowFactory : ToolWindowFactory {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
val contentFactory = ContentFactory.getInstance()
val panel = JewelComposePanel { LockReqsToolWindow(project) }
diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactorySwing.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactorySwing.kt
deleted file mode 100644
index 25013c08d647..000000000000
--- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactorySwing.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
-package org.jetbrains.idea.devkit.threadingModelHelper
-
-import com.intellij.openapi.components.service
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.wm.ToolWindow
-import com.intellij.openapi.wm.ToolWindowFactory
-import com.intellij.ui.components.JBScrollPane
-import com.intellij.ui.table.JBTable
-import javax.swing.JPanel
-import javax.swing.table.DefaultTableModel
-import java.awt.BorderLayout
-
-@Deprecated("Use LockReqsToolWindowFactoryCompose instead")
-class LockReqsToolWindowFactorySwing : ToolWindowFactory {
-
- override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
- val service = project.service()
-
- val tableModel = DefaultTableModel(arrayOf("Execution Path"), 0)
- val panel = JPanel(BorderLayout())
- val table = JBTable(tableModel)
- val scrollPane = JBScrollPane(table)
- panel.add(scrollPane)
-
- // service.onResultsUpdated = { loadData(service.currentResult.paths.map{it.pathString}, tableModel) }
- val factory = toolWindow.contentManager.factory
- val content = factory.createContent(panel, null, false)
- toolWindow.contentManager.addContent(content)
- }
-
- private fun loadData(results: List, tableModel: DefaultTableModel) {
- tableModel.rowCount = 0
- results.forEach { tableModel.addRow(arrayOf(it)) }
- }
-}
-
diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/ui/LockReqsToolWindow.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/ui/LockReqsToolWindow.kt
index c7b747ada263..dd9ec56540bc 100644
--- a/plugins/devkit/devkit-core/src/threadingModelHelper/ui/LockReqsToolWindow.kt
+++ b/plugins/devkit/devkit-core/src/threadingModelHelper/ui/LockReqsToolWindow.kt
@@ -33,7 +33,7 @@ internal fun LockReqsToolWindow(project: Project) {
Column(
modifier = Modifier.fillMaxSize()
) {
- val paths = analysisResult?.paths ?: emptyList()
+ val paths = analysisResult?.paths?.toList() ?: emptyList()
ToolWindowHeader(
pathsCount = paths.size,
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/AnnotationInChain.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/AnnotationInChain.java
index 1a64a226f9b2..f63adbf93693 100644
--- a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/AnnotationInChain.java
+++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/AnnotationInChain.java
@@ -1,8 +1,5 @@
-import testutils.RequiresReadLock;
-import testutils.ExpectedPath
+import com.intellij.util.concurrency.annotations.RequiresReadLock;
-@ExpectedPath("AnnotationInChain.testMethod -> AnnotationInChain.intermediateMethod -> " +
- "AnnotationInChain.targetMethod -> @RequiresReadLock")
class AnnotationInChain {
void testMethod() {
intermediateMethod();
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/AssertionInNestedBlock.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/AssertionInNestedBlock.java
index ce381f8ebbd4..8b25adff68b1 100644
--- a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/AssertionInNestedBlock.java
+++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/AssertionInNestedBlock.java
@@ -1,8 +1,6 @@
-import testutils.ThreadingAssertions;
-import testutils.ExpectedPath
+import com.intellij.util.concurrency.ThreadingAssertions;
-@ExpectedPath("AnnotationInChain.testMethod -> @RequiresReadLock")
-class AnnotationInChain {
+class AssertionInNestedBlock {
void testMethod() {
if (true) {
for (int i = 0; i < 10; i++) {
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/BothAnnotationAndAssertion.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/BothAnnotationAndAssertion.java
index 6376efec1815..e0fcd942301e 100644
--- a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/BothAnnotationAndAssertion.java
+++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/BothAnnotationAndAssertion.java
@@ -1,12 +1,9 @@
-import testutils.RequiresReadLock;
-import testutils.ThreadingAssertions;
-import test.ExpectedPath
+import com.intellij.util.concurrency.ThreadingAssertions;
+import com.intellij.util.concurrency.annotations.RequiresWriteLock;
-@ExpectedPath("BothAnnotationAndAssertion.testMethod -> @RequiresReadLock")
-@ExpectedPath("BothAnnotationAndAssertion.testMethod -> ThreadingAssertions.assertReadAccess()")
class BothAnnotationAndAssertion {
- @RequiresReadLock
+ @RequiresWriteLock
void testMethod() {
- ThreadingAssertions.assertReadAccess();
+ ThreadingAssertions.assertBackgroundThread();
}
}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/CyclicRecursiveCalls.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/CyclicRecursiveCalls.java
index 4aceeecfa9ff..dffeb1edf9ca 100644
--- a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/CyclicRecursiveCalls.java
+++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/CyclicRecursiveCalls.java
@@ -1,7 +1,6 @@
-import testutils.RequiresReadLock;
-import testutils.ExpectedPath
+import com.intellij.util.concurrency.annotations.RequiresReadLock;
+import com.intellij.util.concurrency.ThreadingAssertions;
-@ExpectedPath("CyclicRecursiveCalls.testMethod -> CyclicRecursiveCalls.methodB -> @RequiresReadLock")
class CyclicRecursiveCalls {
void testMethod() {
methodB();
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/DifferentClassesMethods.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/DifferentClassesMethods.java
new file mode 100644
index 000000000000..0c242c35ab2a
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/DifferentClassesMethods.java
@@ -0,0 +1,24 @@
+import com.intellij.util.concurrency.annotations.RequiresEdt;
+import com.intellij.util.concurrency.ThreadingAssertions;
+
+class DifferentClassesMethods {
+ void testMethod() {
+ Helper helper = new Helper();
+ helper.helperMethod();
+ }
+
+ class Helper {
+ void helperMethod() {
+ Service service = new Service();
+ service.serviceMethod();
+ }
+ }
+
+ class Service {
+ @RequiresEdt
+ void serviceMethod() {
+ ThreadingAssertions.assertWriteAccess();
+ }
+ }
+}
+
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/EDTSwingComponent.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/EDTSwingComponent.java
new file mode 100644
index 000000000000..d325573a19fd
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/EDTSwingComponent.java
@@ -0,0 +1,18 @@
+import javax.swing.JPanel;
+import javax.swing.JButton;
+
+public class EDTSwingComponent {
+ public void testMethod() {
+ JPanel panel = new JPanel();
+ panel.setVisible(true); // Requires EDT
+ panel.repaint(); // Safe method - no requirement
+
+ setupButton();
+ }
+
+ private void setupButton() {
+ JButton button = new JButton("Click");
+ button.setText("Updated");
+ button.revalidate();
+ }
+}
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/LambdaWithMethodReference.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/LambdaWithMethodReference.java
index 15aa6b5f406a..13cc6394eee9 100644
--- a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/LambdaWithMethodReference.java
+++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/LambdaWithMethodReference.java
@@ -1,9 +1,7 @@
-import testutils.ThreadingAssertions;
-import testutils.ExpectedPath
+import com.intellij.util.concurrency.ThreadingAssertions;
import java.util.Arrays;
import java.util.List;
-@ExpectedPath("LambdaWithMethodReference.testMethod -> LambdaWithMethodReference.processItem -> ThreadingAssertions.assertReadAccess()")
class LambdaWithMethodReference {
void testMethod() {
List items = Arrays.asList("a", "b", "c");
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/MessageBusDetection.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/MessageBusDetection.java
index 4e2ba4706c4a..237a88142e6d 100644
--- a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/MessageBusDetection.java
+++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/MessageBusDetection.java
@@ -1,4 +1,3 @@
-package test;
public class C {
public void testMessageBusDetection() {
com.intellij.util.messages.MessageBus mb = null;
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/MethodsInDifferentClasses.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/MethodsInDifferentClasses.java
deleted file mode 100644
index 185d7811ad96..000000000000
--- a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/MethodsInDifferentClasses.java
+++ /dev/null
@@ -1,27 +0,0 @@
-import testutils.RequiresReadLock;
-import testutils.ThreadingAssertions;
-import testutils.ExpectedPath;
-
-@ExpectedPath("MethodsInDifferentClasses.testMethod -> Helper.helperMethod -> Service.serviceMethod -> @RequiresReadLock")
-@ExpectedPath("MethodsInDifferentClasses.testMethod -> Helper.helperMethod -> Service.serviceMethod -> ThreadingAssertions.assertReadAccess()")
-class MethodsInDifferentClasses {
- void testMethod() {
- Helper helper = new Helper();
- helper.helperMethod();
- }
-
- class Helper {
- void helperMethod() {
- Service service = new Service();
- service.serviceMethod();
- }
- }
-
- class Service {
- @RequiresReadLock
- void serviceMethod() {
- ThreadingAssertions.assertReadAccess();
- }
- }
-}
-
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/MultipleAssertionsInMethod.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/MultipleAssertionsInMethod.java
index 5de0f9993415..ba1c9196ca6a 100644
--- a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/MultipleAssertionsInMethod.java
+++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/MultipleAssertionsInMethod.java
@@ -1,7 +1,5 @@
-import testutils.ThreadingAssertions;
-import testutils.ExpectedPath
+import com.intellij.util.concurrency.ThreadingAssertions;
-@ExpectedPath("MultipleAssertionsInMethod.testMethod -> ThreadingAssertions.assertReadAccess()")
class MultipleAssertionsInMethod {
void testMethod() {
ThreadingAssertions.assertReadAccess();
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/PolymorphicCallHandling.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/PolymorphicCallHandling.java
deleted file mode 100644
index 4047d69cf1ba..000000000000
--- a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/PolymorphicCallHandling.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package test;
-
-import com.intellij.util.concurrency.annotations.RequiresReadLock;
-
-public class PolymorphicTest {
- public void testMethod() {
- Base base = new Derived();
- base.polymorphicMethod();
- }
-}
-
-abstract class Base {
- public abstract void polymorphicMethod();
-}
-
-class Derived extends Base {
- @Override
- @RequiresReadLock
- public void polymorphicMethod() {}
-}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/SimpleSwingComponent.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/SimpleSwingComponent.java
deleted file mode 100644
index 91965ef10618..000000000000
--- a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/SimpleSwingComponent.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package test;
-
-import javax.swing.JPanel;
-
-public class SwingTest {
- public void testMethod() {
- JPanel panel = new JPanel();
- panel.paint();
- panel.repaint();
- }
-}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/SubtypingPolymorphism.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/SubtypingPolymorphism.java
new file mode 100644
index 000000000000..b86cc429970f
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/SubtypingPolymorphism.java
@@ -0,0 +1,35 @@
+import com.intellij.util.concurrency.annotations.RequiresReadLock;
+import com.intellij.util.concurrency.ThreadingAssertions;
+
+public class SubtypingPolymorphism {
+ public void testMethod() {
+ Service[] services = {new FileService(), new NetworkService(), new DatabaseService()};
+ for (Service service : services) {
+ service.execute();
+ }
+ }
+}
+
+interface Service {
+ void execute();
+}
+
+class FileService implements Service {
+ @Override
+ @RequiresReadLock
+ public void execute() { }
+}
+
+class UIService implements Service {
+ @Override
+ public void execute() {
+ ThreadingAssertions.assertEventDispatchThread();
+ }
+}
+
+class DBService implements Service {
+ @Override
+ public void execute() {
+ ThreadingAssertions.assertReadAccess();
+ }
+}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/threadingModelHelper/LockReqsUnitTest.kt b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/threadingModelHelper/LockReqsUnitTest.kt
index 9f54eaeed1d2..c34041ff8fed 100644
--- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/threadingModelHelper/LockReqsUnitTest.kt
+++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/threadingModelHelper/LockReqsUnitTest.kt
@@ -15,68 +15,120 @@ class LockReqsUnitTest : BasePlatformTestCase() {
override fun setUp() {
super.setUp()
analyzer = LockReqsAnalyzer()
- myFixture.addFileToProject("mock/RequiresReadLock.java", """
- package mock;
+ myFixture.addFileToProject("com/intellij/util/concurrency/annotations.java", """
+ package com.intellij.util.concurrency.annotations;
public @interface RequiresReadLock {}
+ public @interface RequiresWriteLock {}
+ public @interface RequiresEdt {}
+ public @interface RequiresBackgroundThread {}
+ public @interface RequiresReadLockAbsence {}
""".trimIndent())
- myFixture.addFileToProject("mock/ThreadingAssertions.java", """
- package mock;
+ myFixture.addFileToProject("com/intellij/util/concurrency/ThreadingAssertions.java", """
+ package com.intellij.util.concurrency;
public class ThreadingAssertions {
public static void assertReadAccess() {}
+ public static void assertWriteAccess() {}
+ public static void assertWriteIntentReadAccess() {}
+ public static void assertEventDispatchThread() {}
+ public static void assertBackgroundThread() {}
+ }
+ """.trimIndent())
+ myFixture.addFileToProject("mock/MessageBus.java", """
+ package com.intellij.util.concurrency;
+ public interface MessageBus {
+ L syncPublisher(Class topic);
}
""".trimIndent())
}
override fun getBasePath() = DevkitJavaTestsUtil.TESTDATA_PATH + "threadingModelHelper/"
- /*
fun testNoLockRequirements() {
- doTest("NoLockRequirements", "testMethod", emptyList())
- }
-
- fun testAnnotationInChain() {
- doTest()
+ val result = doTest("NoLockRequirements", "testMethod")
+ val actualPaths = formatResult(result)
+ assertTrue(actualPaths.isEmpty())
}
fun testAssertionInNestedBlock() {
- doTest()
+ val result = doTest("AssertionInNestedBlock", "testMethod")
+ val expectedPaths = listOf("AssertionInNestedBlock.testMethod => READ.ASSERTION")
+ val actualPaths = formatResult(result)
+ assertEquals(expectedPaths.sorted(), actualPaths.sorted())
+ }
+
+ fun testAnnotationInChain() {
+ val result = doTest("AnnotationInChain", "testMethod")
+ val expectedPaths = listOf("AnnotationInChain.testMethod -> AnnotationInChain.intermediateMethod" +
+ " -> AnnotationInChain.targetMethod => READ.ANNOTATION")
+ val actualPaths = formatResult(result)
+ assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
fun testBothAnnotationAndAssertion() {
- doTest()
+ val result = doTest("BothAnnotationAndAssertion", "testMethod")
+ val expectedPaths = listOf("BothAnnotationAndAssertion.testMethod => WRITE.ANNOTATION",
+ "BothAnnotationAndAssertion.testMethod => BGT.ASSERTION")
+ val actualPaths = formatResult(result)
+ assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
fun testCyclicRecursiveCalls() {
- doTest()
+ val result = doTest("CyclicRecursiveCalls", "testMethod")
+ val expectedPaths = listOf("CyclicRecursiveCalls.testMethod -> CyclicRecursiveCalls.methodB => READ.ANNOTATION")
+ val actualPaths = formatResult(result)
+ assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
- fun testMethodsInDifferentClasses() {
- doTest()
+ fun testDifferentClassesMethods() {
+ val result = doTest("DifferentClassesMethods", "testMethod")
+ val expectedPaths = listOf("DifferentClassesMethods.testMethod -> Helper.helperMethod -> Service.serviceMethod => EDT.ANNOTATION",
+ "DifferentClassesMethods.testMethod -> Helper.helperMethod -> Service.serviceMethod => WRITE.ASSERTION")
+ val actualPaths = formatResult(result)
+ assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
fun testMultipleAssertionsInMethod() {
- doTest()
+ val result = doTest("MultipleAssertionsInMethod", "testMethod")
+ val expectedPaths = listOf("MultipleAssertionsInMethod.testMethod => READ.ASSERTION")
+ val actualPaths = formatResult(result)
+ assertEquals(1, actualPaths.size)
+ assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
- fun testLambdaWithMethodReference() {
- doTest()
- }*/
- private fun doTest(className: String, methodName: String, expectedPaths: List) {
+ fun testLambdaWithMethodReference() {
+ val result = doTest("LambdaWithMethodReference", "testMethod")
+ val expectedPaths = listOf("LambdaWithMethodReference.testMethod -> LambdaWithMethodReference.processItem => READ.ASSERTION")
+ val actualPaths = formatResult(result)
+ assertEquals(expectedPaths.sorted(), actualPaths.sorted())
+
+ }
+
+ fun testSubtypingPolymorphism() {
+ val result = doTest("SubtypingPolymorphism", "testMethod")
+ val expectedPaths = listOf(
+ "SubtypingPolymorphism.testMethod -> FileService.execute => READ.ANNOTATION",
+ "SubtypingPolymorphism.testMethod -> UIService.execute => EDT.ASSERTION",
+ "SubtypingPolymorphism.testMethod -> DBService.execute => READ.ASSERTION"
+ )
+ val actualPaths = formatResult(result)
+ assertEquals(expectedPaths.sorted(), actualPaths.sorted())
+ }
+
+ private fun doTest(className: String, methodName: String): AnalysisResult {
val fileName = "${getTestName(false)}.java"
val psiJavaFile = myFixture.configureByFile(fileName) as PsiJavaFile
val targetClass = psiJavaFile.classes.find { it.name == className } ?: error("Could not find class $className")
- val targetMethod = targetClass.methods.find { it.name == className } ?: error("Could not find method $methodName")
+ val targetMethod = targetClass.methods.find { it.name == methodName } ?: error("Could not find method $methodName")
+ return analyzer.analyzeMethod(targetMethod)
+ }
- val result = analyzer.analyzeMethod(targetMethod)
+ private fun formatResult(result: AnalysisResult): List {
val actualPaths = result.paths.map { path ->
- buildString {
- path.methodChain.joinToString(separator = " -> ", postfix = " -> ") {
- "${it.method.containingClass?.name}.${it.method.name}"
- }
- "@${path.lockRequirement.lockType}"
- }
+ val chain = path.methodChain.joinToString(" -> ") { "${it.method.containingClass?.name}.${it.method.name}" }
+ val requirement = "${path.lockRequirement.lockType.name}.${path.lockRequirement.requirementReason.name}"
+ "$chain => $requirement"
}
- assertEquals(expectedPaths, actualPaths)
+ return actualPaths
}
}