[java] Don't use project as disposable

Introduces `JavaPluginDisposable` and migrates existing project disposables to the new project level service. #IDEA-383890 Fixed

GitOrigin-RevId: 762429b8f44959e127324d19053a8b05a818f414
This commit is contained in:
Bart van Helvert
2026-01-07 11:33:05 +00:00
committed by intellij-monorepo-bot
parent f1811ad257
commit b9b383c76e
22 changed files with 79 additions and 25 deletions
@@ -7,6 +7,7 @@ import com.intellij.compiler.impl.javaCompiler.eclipse.EclipseCompiler;
import com.intellij.compiler.impl.javaCompiler.javac.JavacCompiler;
import com.intellij.compiler.server.BuildManager;
import com.intellij.compiler.server.CompilerConfigurationUtils;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.compiler.JavaCompilerBundle;
@@ -135,12 +136,12 @@ public final class CompilerConfigurationImpl extends CompilerConfiguration imple
}
BackendCompiler.EP_NAME.getPoint(project).addChangeListener(() -> {
myRegisteredCompilers = collectCompilers();
}, project);
}, JavaPluginDisposable.getInstance(project));
}
private static @NotNull ExcludedEntriesConfiguration createExcludedEntriesConfiguration(@NotNull Project project) {
final ExcludedEntriesConfiguration cfg = new ExcludedEntriesConfiguration(project.getMessageBus().syncPublisher(ExcludedEntriesListener.TOPIC));
Disposer.register(project, cfg);
Disposer.register(JavaPluginDisposable.getInstance(project), cfg);
project.getMessageBus().connect().subscribe(ExcludedEntriesListener.TOPIC, new ExcludedEntriesListener() {
@Override
public void onEntryAdded(@NotNull ExcludeEntryDescription description) {
@@ -7,6 +7,7 @@ import com.intellij.compiler.server.BuildManager;
import com.intellij.execution.process.ProcessIOExecutorService;
import com.intellij.execution.wsl.WSLDistribution;
import com.intellij.ide.IdleTracker;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.compiler.*;
@@ -85,7 +86,7 @@ public class CompilerManagerImpl extends CompilerManager {
myEventPublisher = project.getMessageBus().syncPublisher(CompilerTopics.COMPILATION_STATUS);
// predefined compilers
for (ProjectExtensionPointName<?> ep : Arrays.asList(COMPILABLE_TYPE_EP, BackendCompiler.EP_NAME)) {
ep.addChangeListener(project, () -> {myCachedCompilableTypes = null;}, project);
ep.addChangeListener(project, () -> {myCachedCompilableTypes = null;}, JavaPluginDisposable.getInstance(project));
}
COMPILER_FACTORY_EP.getPoint(project).addExtensionPointListener(new ExtensionPointListener<>() {
@Override
@@ -118,7 +119,7 @@ public class CompilerManagerImpl extends CompilerManager {
projectGeneratedSrcRoot.mkdirs();
final LocalFileSystem lfs = LocalFileSystem.getInstance();
myWatchRoots = lfs.addRootsToWatch(Collections.singletonList(FileUtil.toCanonicalPath(projectGeneratedSrcRoot.getPath())), true);
Disposer.register(project, () -> {
Disposer.register(JavaPluginDisposable.getInstance(project), () -> {
final ExternalJavacManager manager = myExternalJavacManager;
myExternalJavacManager = null;
if (manager != null) {
@@ -2,6 +2,7 @@
package com.intellij.compiler.impl;
import com.intellij.compiler.CompilerConfiguration;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
@@ -28,7 +29,7 @@ public abstract class ExcludeFromCompileAction extends AnAction {
public void actionPerformed(@NotNull AnActionEvent e) {
VirtualFile file = getFile();
if (file != null && file.isValid()) {
ExcludeEntryDescription description = new ExcludeEntryDescription(file, false, true, myProject);
ExcludeEntryDescription description = new ExcludeEntryDescription(file, false, true, JavaPluginDisposable.getInstance(myProject));
CompilerConfiguration.getInstance(myProject).getExcludedEntriesConfiguration().addExcludeEntryDescription(description);
}
}
@@ -3,6 +3,7 @@
package com.intellij.compiler.options;
import com.intellij.build.FileNavigatable;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
@@ -27,9 +28,11 @@ public class ExcludeFromValidationAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getData(CommonDataKeys.PROJECT);
if (project == null) return;
final Pair<ExcludesConfiguration, VirtualFile> pair = getExcludedConfigurationAndFile(e, project);
if (pair == null) return;
final ExcludeEntryDescription description = new ExcludeEntryDescription(pair.getSecond(), false, true, project);
final ExcludeEntryDescription description =
new ExcludeEntryDescription(pair.getSecond(), false, true, JavaPluginDisposable.getInstance(project));
pair.getFirst().addExcludeEntryDescription(description);
}
@@ -4,6 +4,7 @@ package com.intellij.openapi.compiler.options;
import com.intellij.compiler.CompilerConfiguration;
import com.intellij.ide.IdeBundle;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.compiler.JavaCompilerBundle;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
@@ -56,7 +57,7 @@ public final class ExcludedEntriesConfigurable implements UnnamedConfigurable, N
ExcludeEntryDescription[] descriptions = myConfiguration.getExcludeEntryDescriptions();
disposeMyDescriptions();
for (ExcludeEntryDescription description : descriptions) {
myExcludeEntryDescriptions.add(description.copy(myProject));
myExcludeEntryDescriptions.add(description.copy(JavaPluginDisposable.getInstance(myProject)));
}
((AbstractTableModel)myExcludedEntriesPanel.myExcludedTable.getModel()).fireTableDataChanged();
}
@@ -77,7 +78,7 @@ public final class ExcludedEntriesConfigurable implements UnnamedConfigurable, N
public void apply() {
myConfiguration.removeAllExcludeEntryDescriptions();
for (ExcludeEntryDescription description : myExcludeEntryDescriptions) {
myConfiguration.addExcludeEntryDescription(description.copy(myProject));
myConfiguration.addExcludeEntryDescription(description.copy(JavaPluginDisposable.getInstance(myProject)));
}
}
@@ -167,10 +168,10 @@ public final class ExcludedEntriesConfigurable implements UnnamedConfigurable, N
}
ExcludeEntryDescription description;
if (chosenFile.isDirectory()) {
description = new ExcludeEntryDescription(chosenFile, true, false, myProject);
description = new ExcludeEntryDescription(chosenFile, true, false, JavaPluginDisposable.getInstance(myProject));
}
else {
description = new ExcludeEntryDescription(chosenFile, false, true, myProject);
description = new ExcludeEntryDescription(chosenFile, false, true, JavaPluginDisposable.getInstance(myProject));
}
myExcludeEntryDescriptions.add(selected, description);
selected++;
@@ -11,6 +11,7 @@ import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.debugger.impl.DebuggerUtilsImpl;
import com.intellij.debugger.ui.JavaDebuggerSupport;
import com.intellij.debugger.ui.tree.render.*;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
@@ -128,7 +129,7 @@ class CompoundRendererConfigurable extends JPanel {
updateContext(myClassNameField.getText());
}
});
ComponentValidator validator = new ComponentValidator(myProject).withValidator(() -> {
ComponentValidator validator = new ComponentValidator(JavaPluginDisposable.getInstance(myProject)).withValidator(() -> {
String text = myClassNameField.getText();
if (StringUtil.containsAnyChar(text, "<>")) {
return new ValidationInfo(JavaDebuggerBundle.message("error.compound.renderer.configurable.fqn.generic"), editorTextField);
@@ -27,6 +27,7 @@ import com.intellij.debugger.statistics.DebuggerStatistics;
import com.intellij.debugger.ui.impl.watch.CompilingEvaluatorImpl;
import com.intellij.debugger.ui.overhead.OverheadProducer;
import com.intellij.icons.AllIcons;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
@@ -228,7 +229,7 @@ public abstract class Breakpoint<P extends JavaBreakpointProperties> implements
breakpoint.emitBreakpointChanged();
})
.coalesceBy(myProject, this)
.expireWith(myProject)
.expireWith(JavaPluginDisposable.getInstance(myProject))
.submit(RELOAD_EXECUTOR);
}
@@ -8,6 +8,7 @@ import com.intellij.execution.remote.RemoteConfiguration;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.util.JavaParametersUtil;
import com.intellij.icons.AllIcons;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
@@ -188,7 +189,7 @@ public final class CompileStepBeforeRun extends BeforeRunTaskProvider<CompileSte
);
env.copyUserDataTo(context);
return new Pair<>(context, projectTask);
}).expireWith(myProject).executeSynchronously();
}).expireWith(JavaPluginDisposable.getInstance(myProject)).executeSynchronously();
ProjectTaskManagerImpl.putBuildOriginator(myProject, CompileStepBeforeRun.class);
@@ -27,6 +27,7 @@ import com.intellij.execution.ui.ConsoleView;
import com.intellij.execution.util.JavaParametersUtil;
import com.intellij.execution.util.ProgramParametersConfigurator;
import com.intellij.execution.util.ProgramParametersUtil;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.compiler.JavaCompilerBundle;
import com.intellij.openapi.diagnostic.Logger;
@@ -281,7 +282,7 @@ public abstract class JavaTestFrameworkRunnableState<T extends
testConsole,
executor
);
Disposer.register(getConfiguration().getProject(), consoleView);
Disposer.register(JavaPluginDisposable.getInstance(getConfiguration().getProject()), consoleView);
OSProcessHandler handler = createHandler(viewer);
@@ -13,6 +13,7 @@ import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.util.JavaParametersUtil;
import com.intellij.execution.util.ProgramParametersConfigurator;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
@@ -68,7 +69,7 @@ public abstract class ApplicationCommandLineState<T extends
}
return null;
})
.expireWith(configuration.getProject())
.expireWith(JavaPluginDisposable.getInstance(configuration.getProject()))
.executeSynchronously();
}
catch (Exception e) {
@@ -5,6 +5,7 @@ import com.intellij.application.options.ModuleDescriptionsComboBox;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.configurations.RemoteConnection;
import com.intellij.execution.ui.ConfigurationModuleSelector;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.compiler.JavaCompilerBundle;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SettingsEditor;
@@ -143,7 +144,7 @@ public class RemoteConfigurable extends SettingsEditor<RemoteConfiguration> {
myTransportCombo.setSelectedItem(Transport.SOCKET);
myPort.setMinimumSize(myPort.getPreferredSize());
new ComponentValidator(project).withValidator(() -> {
new ComponentValidator(JavaPluginDisposable.getInstance(project)).withValidator(() -> {
String pt = myPort.getText();
if (StringUtil.isNotEmpty(pt)) {
try {
@@ -8,6 +8,7 @@ import com.intellij.codeInsight.AttachSourcesProviderFilter;
import com.intellij.ide.JavaUiBundle;
import com.intellij.ide.highlighter.JavaClassFileType;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
@@ -170,7 +171,7 @@ public final class AttachSourcesNotificationProvider implements EditorNotificati
throw new RuntimeException(JavaUiBundle.message("can.t.find.library.for.0", file.getName()));
})
.expireWith(project)
.expireWith(JavaPluginDisposable.getInstance(project))
.expireWhen(() -> !file.isValid())
.coalesceBy(file, project)
.finishOnUiThread(ModalityState.current(), uiThreadAction)
@@ -12,6 +12,7 @@ import com.intellij.ide.util.importProject.LibrariesDetectionStep
import com.intellij.ide.util.importProject.RootDetectionProcessor
import com.intellij.ide.util.projectWizard.WizardContext
import com.intellij.ide.util.projectWizard.importSources.impl.ProjectFromSourcesBuilderImpl
import com.intellij.java.JavaPluginDisposable
import com.intellij.notification.*
import com.intellij.notification.impl.NotificationIdsHolder
import com.intellij.openapi.application.ApplicationManager
@@ -188,7 +189,7 @@ private fun filesToLinks(files: MutableCollection<VirtualFile>, projectDirectory
}
private suspend fun setupFromSources(project: Project, projectDir: VirtualFile) {
val builder = ProjectFromSourcesBuilderImpl(WizardContext(project, project), ModulesProvider.EMPTY_MODULES_PROVIDER)
val builder = ProjectFromSourcesBuilderImpl(WizardContext(project, JavaPluginDisposable.getInstance(project)), ModulesProvider.EMPTY_MODULES_PROVIDER)
val projectPath = projectDir.path
builder.baseProjectPath = projectPath
val roots = RootDetectionProcessor.detectRoots(File(projectPath))
@@ -6,6 +6,7 @@ import com.intellij.ide.projectView.ProjectView;
import com.intellij.ide.util.projectWizard.ModuleBuilder;
import com.intellij.ide.util.projectWizard.ModuleWizardStep;
import com.intellij.ide.util.projectWizard.WizardContext;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
@@ -99,7 +100,8 @@ public final class GeneralModuleTypeForIdea extends GeneralModuleType {
String shortcutText = KeymapUtil.getShortcutText(IdeActions.ACTION_NEW_ELEMENT);
GotItTooltip tooltip =
new GotItTooltip("empty.project.create.file", IdeBundle.message("to.create.new.file.tooltip", shortcutText), project)
new GotItTooltip("empty.project.create.file", IdeBundle.message("to.create.new.file.tooltip", shortcutText),
JavaPluginDisposable.getInstance(project))
.withPosition(Balloon.Position.atRight);
ApplicationManager.getApplication().getMessageBus().connect(tooltip).subscribe(AnActionListener.TOPIC, new AnActionListener() {
@Override
@@ -1,6 +1,7 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.roots.ui.configuration.projectRoot;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
@@ -32,7 +33,7 @@ public final class StructureConfigurableContext implements Disposable, LibraryEd
public StructureConfigurableContext(Project project, final ModulesConfigurator modulesConfigurator) {
myProject = project;
myModulesConfigurator = modulesConfigurator;
Disposer.register(project, this);
Disposer.register(JavaPluginDisposable.getInstance(project), this);
myDaemonAnalyzer = new ProjectStructureDaemonAnalyzer(this);
}
@@ -7,6 +7,7 @@ import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.codeInspection.SuppressionUtilCore;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase;
import com.intellij.codeInspection.ex.*;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
@@ -420,7 +421,7 @@ public final class RefJavaManagerImpl extends RefJavaManager {
public void configureAnnotations() {
}
};
Disposer.register(project, entryPointsManager);
Disposer.register(JavaPluginDisposable.getInstance(project), entryPointsManager);
((EntryPointsManagerBase)entryPointsManager).addAllPersistentEntries(EntryPointsManagerBase.getInstance(project));
}
@@ -2,6 +2,7 @@
package com.intellij.refactoring.extractMethod.preview;
import com.intellij.ide.impl.ContentManagerWatcher;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupManager;
@@ -24,8 +25,12 @@ public final class ExtractMethodPreviewManager {
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> {
ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
ToolWindow toolWindow = toolWindowManager.registerToolWindow(ToolWindowId.EXTRACT_METHOD,
true, ToolWindowAnchor.BOTTOM, myProject);
ToolWindow toolWindow = toolWindowManager.registerToolWindow(
ToolWindowId.EXTRACT_METHOD,
true,
ToolWindowAnchor.BOTTOM,
JavaPluginDisposable.getInstance(myProject)
);
myContentManager = toolWindow.getContentManager();
ContentManagerWatcher.watchContentManager(toolWindow, myContentManager);
});
@@ -3,6 +3,7 @@ package com.intellij.refactoring.extractMethod.preview;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.actions.exclusion.ExclusionHandler;
import com.intellij.java.JavaPluginDisposable;
import com.intellij.java.refactoring.JavaRefactoringBundle;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.CommonDataKeys;
@@ -70,7 +71,7 @@ class PreviewPanel extends BorderLayoutPanel implements Disposable, UiDataProvid
Disposer.register(this, usageModelTracker);
usageModelTracker.addListener(isPropertyChange -> updateLater(), this);
Disposer.register(processor.getProject(), this);
Disposer.register(JavaPluginDisposable.getInstance(processor.getProject()), this);
Disposer.register(this, myTree);
Disposer.register(this, myDiffPanel);
}
@@ -0,0 +1,24 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java
import com.intellij.openapi.Disposable
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import kotlinx.coroutines.CoroutineScope
import org.jetbrains.annotations.ApiStatus
/**
* A project level disposable for the Java plugin.
* Use this class instead of passing [Project] as a disposable to make sure your resource gets disposed when the Java plugin is unloaded.
*/
@ApiStatus.Internal
@Service(Service.Level.PROJECT)
class JavaPluginDisposable(val coroutineScope: CoroutineScope) : Disposable {
override fun dispose() { }
companion object {
@JvmStatic
fun getInstance(project: Project): JavaPluginDisposable = project.service<JavaPluginDisposable>()
}
}
@@ -105,6 +105,7 @@ public class MockPsiDirectory extends MockPsiElement implements PsiDirectory {
@Override
public @Nullable PsiDirectory getParentDirectory() {
final PsiPackage psiPackage = myPackage.getParentPackage();
//noinspection IncorrectParentDisposable
return psiPackage == null ? null : new MockPsiDirectory(psiPackage, getProject());
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.testFramework.fixtures;
import com.intellij.lang.Language;
@@ -6,6 +6,7 @@ import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
@@ -60,6 +61,9 @@ public final class LightIdeaTestFixtureImpl extends BaseFixture implements Light
// don't use method references here to make stack trace reading easier
//noinspection Convert2MethodRef
new RunAll(
// wait for NBRAs to complete because they might access a temporarily disposed project
() -> NonBlockingReadActionImpl.waitForAsyncTaskCompletion(),
() -> PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue(), // dispatch events that NBRAs potentially schedule
() -> {
if (project != null) {
CodeStyle.dropTemporarySettings(project);