avoid removing connection on disconnect from list on each disconnect because removing...
authorVladimir Krivosheev <vladimir.krivosheev@jetbrains.com>
Mon, 4 May 2020 09:54:29 +0000 (11:54 +0200)
committerintellij-monorepo-bot <intellij-monorepo-bot-no-reply@jetbrains.com>
Wed, 6 May 2020 09:45:40 +0000 (09:45 +0000)
GitOrigin-RevId: df90d83f66ae2ab2876a9d11223b1ae5c8fa81eb

112 files changed:
java/compiler/impl/src/com/intellij/compiler/server/BuildManagerListener.java
java/compiler/impl/src/com/intellij/compiler/server/CustomBuilderMessageHandler.java
java/compiler/impl/src/com/intellij/task/impl/JpsProjectTaskRunner.java
java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerTopics.java
java/compiler/openapi/src/com/intellij/openapi/compiler/options/ExcludedEntriesListener.java
java/debugger/impl/src/com/intellij/debugger/impl/DebuggerManagerListener.java
java/java-tests/testSrc/com/intellij/java/refactoring/suggested/JavaSuggestedRefactoringChangeListenerTest.kt
java/testFramework/src/com/intellij/refactoring/suggested/BaseSuggestedRefactoringChangeListenerTest.kt
platform/analysis-api/src/com/intellij/profile/ProfileChangeAdapter.java
platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/SeverityRegistrar.java
platform/configuration-store-impl/src/ComponentStoreImpl.kt
platform/core-api/src/com/intellij/ide/PowerSaveMode.java
platform/core-api/src/com/intellij/openapi/command/CommandListener.java
platform/core-api/src/com/intellij/openapi/vfs/VirtualFileManager.java
platform/core-api/src/com/intellij/psi/util/PsiModificationTracker.java
platform/core-api/src/com/intellij/util/messages/MessageBusFactory.java
platform/core-api/src/com/intellij/util/messages/MessageBusOwner.java
platform/core-api/src/com/intellij/util/messages/impl/CompositeMessageBus.java [new file with mode: 0644]
platform/core-api/src/com/intellij/util/messages/impl/DescriptorBasedMessageBusConnection.java [new file with mode: 0644]
platform/core-api/src/com/intellij/util/messages/impl/MessageBusConnectionImpl.java
platform/core-api/src/com/intellij/util/messages/impl/MessageBusEx.java [new file with mode: 0644]
platform/core-api/src/com/intellij/util/messages/impl/MessageBusFactoryImpl.java
platform/core-api/src/com/intellij/util/messages/impl/MessageBusImpl.java
platform/core-api/src/com/intellij/util/messages/impl/MessageListenerList.java
platform/core-api/src/com/intellij/util/messages/impl/SimpleMessageBusConnectionImpl.java [new file with mode: 0644]
platform/core-impl/src/com/intellij/mock/MockComponentManager.java
platform/core-impl/src/com/intellij/mock/MockDumbService.java
platform/core-impl/src/com/intellij/openapi/command/impl/CoreCommandProcessor.java
platform/core-impl/src/com/intellij/psi/impl/PsiModificationTrackerImpl.java
platform/core-impl/src/com/intellij/psi/impl/file/impl/FileManagerImpl.java
platform/dvcs-impl/src/com/intellij/dvcs/repo/VcsRepositoryManager.java
platform/editor-ui-api/src/com/intellij/ide/ui/UISettingsListener.java
platform/editor-ui-api/src/com/intellij/openapi/actionSystem/ex/AnActionListener.java
platform/editor-ui-api/src/com/intellij/openapi/editor/colors/EditorColorsManager.java
platform/extensions/src/com/intellij/openapi/components/ComponentManager.java
platform/extensions/src/com/intellij/util/messages/MessageBus.java
platform/extensions/src/com/intellij/util/messages/MessageBusConnection.java
platform/extensions/src/com/intellij/util/messages/SimpleMessageBusConnection.java [new file with mode: 0644]
platform/extensions/src/com/intellij/util/messages/Topic.java
platform/lang-api/src/com/intellij/codeInsight/hints/InlayHintsSettings.kt
platform/lang-api/src/com/intellij/refactoring/suggested/SuggestedRefactoringProvider.kt
platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java
platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonListeners.java
platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/EditorTrackerListener.java
platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PsiChangeHandler.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/PasteHandler.java
platform/lang-impl/src/com/intellij/facet/impl/ProjectWideFacetListenersRegistryImpl.java
platform/lang-impl/src/com/intellij/ide/script/IdeStartupScripts.java
platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleImpl.java
platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/ProjectJdkTableImpl.java
platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/projectRoot/SdkDownloadTracker.java
platform/lang-impl/src/com/intellij/openapi/vcs/impl/LineStatusTrackerSettingListener.java
platform/lang-impl/src/com/intellij/openapi/wm/impl/status/TogglePopupHintsPanel.java
platform/lang-impl/src/com/intellij/refactoring/suggested/SuggestedRefactoringChangeListener.kt
platform/lang-impl/src/com/intellij/refactoring/suggested/SuggestedRefactoringProviderImpl.kt
platform/platform-api/src/com/intellij/AppTopics.java
platform/platform-api/src/com/intellij/ide/FrameStateListener.java
platform/platform-api/src/com/intellij/openapi/fileEditor/FileEditorManagerListener.java
platform/platform-api/src/com/intellij/openapi/fileTypes/FileTypeManager.java
platform/platform-api/src/com/intellij/openapi/keymap/KeymapManagerListener.java
platform/platform-api/src/com/intellij/openapi/project/ProjectUtil.kt
platform/platform-api/src/com/intellij/openapi/project/impl/ProjectLifecycleListener.java
platform/platform-impl/src/com/intellij/codeInsight/hint/EditorHintListener.java
platform/platform-impl/src/com/intellij/ide/AppLifecycleListener.java
platform/platform-impl/src/com/intellij/ide/CommandLineWaitingManager.java
platform/platform-impl/src/com/intellij/ide/actions/HideToolWindowAction.kt
platform/platform-impl/src/com/intellij/ide/actions/PopupInMainMenuActionGroup.kt
platform/platform-impl/src/com/intellij/ide/actions/RecentLocationsAction.java
platform/platform-impl/src/com/intellij/ide/actions/RecentLocationsDataModel.kt
platform/platform-impl/src/com/intellij/ide/actions/SaveAllAction.kt
platform/platform-impl/src/com/intellij/ide/file/BatchFileChangeListener.java
platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt
platform/platform-impl/src/com/intellij/ide/startup/ProjectLoadListener.java
platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt
platform/platform-impl/src/com/intellij/openapi/actionSystem/SplitButtonAction.java
platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java
platform/platform-impl/src/com/intellij/openapi/command/impl/UndoManagerImpl.java
platform/platform-impl/src/com/intellij/openapi/editor/ProjectDisposeAwareDocumentListener.java [new file with mode: 0644]
platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java
platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/IdeDocumentHistoryImpl.java
platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java
platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.kt
platform/platform-impl/src/com/intellij/openapi/project/impl/DefaultProject.java
platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java
platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java
platform/platform-impl/src/com/intellij/openapi/project/projectLoader.kt
platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/CachedFileType.java
platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EditorBasedWidget.java
platform/platform-impl/src/com/intellij/openapi/wm/impl/status/IdeStatusBarImpl.java
platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InfoAndProgressPanel.java
platform/platform-resources/src/componentSets/PlatformLangComponents.xml
platform/platform-tests/testSrc/com/intellij/openapi/project/impl/JBNavigateCommandTest.kt
platform/platform-tests/testSrc/com/intellij/openapi/project/impl/RecentProjectsTest.kt
platform/platform-tests/testSrc/com/intellij/util/messages/impl/MessageBusTest.java
platform/projectModel-api/src/com/intellij/ProjectTopics.java
platform/projectModel-api/src/com/intellij/openapi/project/ProjectManager.java
platform/projectModel-api/src/com/intellij/openapi/projectRoots/ProjectJdkTable.java
platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt
platform/util/src/com/intellij/util/EventDispatcher.java
platform/util/src/com/intellij/util/SmartFMap.java
platform/util/src/com/intellij/util/containers/LockFreeCopyOnWriteArrayList.java
platform/util/src/com/intellij/util/lang/CompoundRuntimeException.java
platform/vcs-api/src/com/intellij/openapi/vcs/ProjectLevelVcsManager.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java
plugins/devkit/devkit-core/src/inspections/missingApi/update/IdeExternalAnnotationsUpdateStartupActivity.kt
plugins/gradle/src/org/jetbrains/plugins/gradle/settings/GradleSettingsListener.java
plugins/java-decompiler/plugin/src/org/jetbrains/java/decompiler/IdeaDecompiler.kt
plugins/stats-collector/src/com/intellij/completion/ml/common/RecentPlacesFeatures.kt
plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java
python/python-psi-impl/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java
python/src/com/jetbrains/python/run/PythonTask.java
xml/dom-impl/src/com/intellij/util/xml/highlighting/DomElementAnnotationsManagerImpl.java

index 8f089ddbf488426bd1eb1f662d3d2232a4441979..24ec572f8948b21319f51810e74215a3558fc4a2 100644 (file)
@@ -1,3 +1,4 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.compiler.server;
 
 import com.intellij.openapi.project.Project;
@@ -10,7 +11,7 @@ import java.util.UUID;
  * @author Eugene Zhuravlev
  */
 public interface BuildManagerListener {
-  Topic<BuildManagerListener> TOPIC = Topic.create("Build Manager", BuildManagerListener.class);
+  Topic<BuildManagerListener> TOPIC = new Topic<>(BuildManagerListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
   default void beforeBuildProcessStarted(@NotNull Project project, @NotNull UUID sessionId) {}
 
index eca2185dcadfe5f2e75e282d23223ddf40fa75d8..8b6993c7ef0356be73ffc4d9c2e347e7f117692e 100644 (file)
@@ -1,18 +1,4 @@
-/*
- * Copyright 2000-2012 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.compiler.server;
 
 import com.intellij.util.messages.Topic;
@@ -20,7 +6,10 @@ import com.intellij.util.messages.Topic;
 import java.util.EventListener;
 
 public interface CustomBuilderMessageHandler extends EventListener {
-  Topic<CustomBuilderMessageHandler> TOPIC = Topic.create("custom builder message", CustomBuilderMessageHandler.class);
+  /**
+   * Custom builder message. Project level.
+   */
+  Topic<CustomBuilderMessageHandler> TOPIC = new Topic<>(CustomBuilderMessageHandler.class, Topic.BroadcastDirection.NONE);
 
   void messageReceived(String builderId, String messageType, String messageText);
 }
index b051212bb301edf3b0c6f59da00a91220f7ea5e8..cae6953c18e6496b6c86385cddc78e20176c83c7 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.task.impl;
 
 import com.intellij.compiler.impl.CompileDriver;
@@ -25,7 +25,7 @@ import com.intellij.task.*;
 import com.intellij.ui.GuiUtils;
 import com.intellij.util.SmartList;
 import com.intellij.util.containers.ContainerUtil;
-import com.intellij.util.messages.MessageBusConnection;
+import com.intellij.util.messages.SimpleMessageBusConnection;
 import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -51,9 +51,9 @@ public class JpsProjectTaskRunner extends ProjectTaskRunner {
                   @Nullable ProjectTaskNotification callback,
                   @NotNull Collection<? extends ProjectTask> tasks) {
     context.putUserData(JPS_BUILD_DATA_KEY, new MyJpsBuildData());
-    MessageBusConnection fileGeneratedTopicConnection;
+    SimpleMessageBusConnection fileGeneratedTopicConnection;
     if (context.isCollectionOfGeneratedFilesEnabled()) {
-      fileGeneratedTopicConnection = project.getMessageBus().connect();
+      fileGeneratedTopicConnection = project.getMessageBus().simpleConnect();
       fileGeneratedTopicConnection.subscribe(CompilerTopics.COMPILATION_STATUS, new CompilationStatusListener() {
         @Override
         public void fileGenerated(@NotNull String outputRoot, @NotNull String relativePath) {
index 929c1dae1e9cf6a0d3e844dff23f8be715f34052..e4aa35f9bdea4fc6fcfffbbf630e74e31635dd51 100644 (file)
@@ -1,28 +1,13 @@
-/*
- * Copyright 2000-2009 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.openapi.compiler;
 
 import com.intellij.util.messages.Topic;
 
-/**
- * @author yole
- */
-public class CompilerTopics {
-  public static final Topic<CompilationStatusListener> COMPILATION_STATUS =
-    new Topic<>("compilation status", CompilationStatusListener.class);
+public final class CompilerTopics {
+  /**
+   * Project level.
+   */
+  public static final Topic<CompilationStatusListener> COMPILATION_STATUS = new Topic<>(CompilationStatusListener.class, Topic.BroadcastDirection.NONE);
 
   private CompilerTopics() {
   }
index f7832eebf94590a4c7aa4386370e75647ca8d09e..3a446bcbbecb8bf61316f35d70f107c2b3ab3026 100644 (file)
@@ -1,26 +1,14 @@
-/*
- * Copyright 2000-2016 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.openapi.compiler.options;
 
 import com.intellij.util.messages.Topic;
-import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.NotNull;
 
 public interface ExcludedEntriesListener {
-  Topic<ExcludedEntriesListener> TOPIC = Topic.create("Compiler's excluded entries modification notification", ExcludedEntriesListener.class);
+  /**
+   * Compiler's excluded entries modification notification.
+   */
+  Topic<ExcludedEntriesListener> TOPIC = new Topic<>(ExcludedEntriesListener.class, Topic.BroadcastDirection.NONE);
 
   default void onEntryAdded(@NotNull ExcludeEntryDescription description) {}
 
index aa8916d273186f088c57241a0f99ac0ab7cfcb59..9d2729131eec892e0cf1790b16116112e423212c 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.debugger.impl;
 
 import com.intellij.util.messages.Topic;
@@ -6,7 +6,8 @@ import com.intellij.util.messages.Topic;
 import java.util.EventListener;
 
 public interface DebuggerManagerListener extends EventListener {
-  Topic<DebuggerManagerListener> TOPIC = new Topic<>("DebuggerManagerListener", DebuggerManagerListener.class);
+  // project level
+  Topic<DebuggerManagerListener> TOPIC = new Topic<>("DebuggerManagerListener", DebuggerManagerListener.class, Topic.BroadcastDirection.NONE);
 
   default void sessionCreated(DebuggerSession session) {
   }
index 02d3257086f45e3cbb17460cd14f73fc36aabbb7..d1edf2635e318b3f6751c31d6962bc06152a32a0 100644 (file)
@@ -1,5 +1,4 @@
 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
-
 package com.intellij.java.refactoring.suggested
 
 import com.intellij.ide.highlighter.JavaFileType
index 7144e3ca6cc914d6562e89c6716f7146f1d17160..9c6e0e9f7d0150c173512079ad6bc0883d4f2c0d 100644 (file)
@@ -27,14 +27,16 @@ abstract class BaseSuggestedRefactoringChangeListenerTest : LightJavaCodeInsight
     append("'")
   }
 
+  private val disposable = Disposer.newDisposable()
+
   override fun setUp() {
     super.setUp()
-    changeListener = SuggestedRefactoringChangeListener(project, watcher)
-    changeListener.attach()
+
+    changeListener = SuggestedRefactoringChangeListener(project, watcher, testRootDisposable)
   }
 
   override fun tearDown() {
-    Disposer.dispose(changeListener)
+    Disposer.dispose(disposable)
     super.tearDown()
   }
 
index b780a2f929ef05799e6c03d00014a694e7b8c31d..e3005860c1506221e6f8c7b32ab3f0462a5d29e6 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.profile;
 
 import com.intellij.codeInspection.InspectionProfile;
@@ -6,7 +6,8 @@ import com.intellij.util.messages.Topic;
 import org.jetbrains.annotations.Nullable;
 
 public interface ProfileChangeAdapter {
-  Topic<ProfileChangeAdapter> TOPIC = new Topic<>("ProfileChangeAdapter", ProfileChangeAdapter.class);
+  @Topic.ProjectLevel
+  Topic<ProfileChangeAdapter> TOPIC = new Topic<>(ProfileChangeAdapter.class, Topic.BroadcastDirection.NONE);
 
   default void profileChanged(@Nullable InspectionProfile profile) {
   }
index 2ee68afc0d50ac98f57eabdaa561a0755b023bd1..38b8a4ee402f84bb937f1c1115f605ec8bb8b4be 100644 (file)
@@ -39,13 +39,13 @@ public final class SeverityRegistrar implements Comparator<HighlightSeverity>, M
 
   private static final Logger LOG = Logger.getInstance(SeverityRegistrar.class);
 
-  private static final Topic<Runnable> STANDARD_SEVERITIES_CHANGED_TOPIC = Topic.create("STANDARD_SEVERITIES_CHANGED_TOPIC", Runnable.class, Topic.BroadcastDirection.TO_CHILDREN);
+  private static final Topic<Runnable> STANDARD_SEVERITIES_CHANGED_TOPIC = new Topic<>("standard severities changed", Runnable.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
   @NonNls private static final String INFO_TAG = "info";
   @NonNls private static final String COLOR_ATTRIBUTE = "color";
   private final Map<String, SeverityBasedTextAttributes> myMap = new ConcurrentHashMap<>();
   private final Map<String, Color> myRendererColors = new ConcurrentHashMap<>();
-  static final Topic<Runnable> SEVERITIES_CHANGED_TOPIC = Topic.create("SEVERITIES_CHANGED_TOPIC", Runnable.class, Topic.BroadcastDirection.TO_PARENT);
+  static final Topic<Runnable> SEVERITIES_CHANGED_TOPIC = new Topic<>("severities changed", Runnable.class, Topic.BroadcastDirection.TO_PARENT);
   private final @NotNull MessageBus myMessageBus;
 
   private volatile OrderMap myOrderMap;
@@ -57,7 +57,7 @@ public final class SeverityRegistrar implements Comparator<HighlightSeverity>, M
 
   public SeverityRegistrar(@NotNull MessageBus messageBus) {
     myMessageBus = messageBus;
-    messageBus.connect().subscribe(STANDARD_SEVERITIES_CHANGED_TOPIC, () -> myOrderMap = null);
+    messageBus.simpleConnect().subscribe(STANDARD_SEVERITIES_CHANGED_TOPIC, () -> myOrderMap = null);
   }
 
   static {
index 4f13a5af228ea175171322ca1f41d040b06b3638..439bb559b91d7f9dd042a4c3e10f955467fbbc14 100644 (file)
@@ -112,7 +112,7 @@ abstract class ComponentStoreImpl : IComponentStore {
       throw e
     }
     catch (e: Exception) {
-      LOG.error(PluginException("Cannot init $componentName component state", e, pluginId))
+      LOG.error(PluginException("Cannot init component state (componentName=$componentName, componentClass=${component.javaClass.simpleName})", e, pluginId))
     }
   }
 
index 834a66ad1ba773cfdd4212d4bace9e789e486388..396f180f3044ecca9b6fe1440ce6f6b3d8344d31 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.ide;
 
 import com.intellij.ide.util.PropertiesComponent;
@@ -8,7 +8,7 @@ import com.intellij.util.messages.Topic;
 
 @Service
 public final class PowerSaveMode {
-  public static final Topic<Listener> TOPIC = new Topic<>("PowerSaveMode.Listener", Listener.class);
+  public static final Topic<Listener> TOPIC = new Topic<>(Listener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
   private static final String POWER_SAVE_MODE = "power.save.mode";
 
index eb3feecbdc3ea0d06d195545970caa1fdc3b428a..f84a29e0adf09f887a0ff104d7c74045eb59cec9 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.openapi.command;
 
 import com.intellij.util.messages.Topic;
@@ -7,7 +7,9 @@ import org.jetbrains.annotations.NotNull;
 import java.util.EventListener;
 
 public interface CommandListener extends EventListener {
-  Topic<CommandListener> TOPIC = new Topic<>("command events", CommandListener.class);
+  // immediateDelivery
+  @Topic.AppLevel
+  Topic<CommandListener> TOPIC = new Topic<>(CommandListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN, true);
 
   default void commandStarted(@NotNull CommandEvent event) {
   }
index 200682268bd575212d52fd956457a5fc1713321c..d60e00175ec1bd89bdbcfa85b93ddf42127afa34 100644 (file)
@@ -1,9 +1,9 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.openapi.vfs;
 
 import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.CachedSingletonsRegistry;
-import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.util.ModificationTracker;
 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
 import com.intellij.util.io.URLUtil;
@@ -19,10 +19,10 @@ import org.jetbrains.annotations.Nullable;
  * @see VirtualFileSystem
  */
 public abstract class VirtualFileManager implements ModificationTracker {
-  public static final Topic<BulkFileListener> VFS_CHANGES = new Topic<>("NewVirtualFileSystem changes", BulkFileListener.class);
+  @Topic.AppLevel
+  public static final Topic<BulkFileListener> VFS_CHANGES = new Topic<>(BulkFileListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
-  @NotNull
-  public static final ModificationTracker VFS_STRUCTURE_MODIFICATIONS = () -> getInstance().getStructureModificationCount();
+  public static final @NotNull ModificationTracker VFS_STRUCTURE_MODIFICATIONS = () -> getInstance().getStructureModificationCount();
 
   private static VirtualFileManager ourInstance = CachedSingletonsRegistry.markCachedField(VirtualFileManager.class);
 
@@ -31,11 +31,10 @@ public abstract class VirtualFileManager implements ModificationTracker {
    *
    * @return {@code VirtualFileManager}
    */
-  @NotNull
-  public static VirtualFileManager getInstance() {
+  public static @NotNull VirtualFileManager getInstance() {
     VirtualFileManager result = ourInstance;
     if (result == null) {
-      ourInstance = result = ServiceManager.getService(VirtualFileManager.class);
+      ourInstance = result = ApplicationManager.getApplication().getService(VirtualFileManager.class);
     }
     return result;
   }
@@ -78,8 +77,7 @@ public abstract class VirtualFileManager implements ModificationTracker {
    * @see VirtualFileSystem#findFileByPath
    * @see #refreshAndFindFileByUrl
    */
-  @Nullable
-  public abstract VirtualFile findFileByUrl(@NonNls @NotNull String url);
+  public abstract @Nullable VirtualFile findFileByUrl(@NonNls @NotNull String url);
 
   /**
    * <p>Refreshes only the part of the file system needed for searching the file by the given URL and finds file
@@ -95,8 +93,7 @@ public abstract class VirtualFileManager implements ModificationTracker {
    * @see VirtualFileSystem#findFileByPath
    * @see VirtualFileSystem#refreshAndFindFileByPath
    */
-  @Nullable
-  public abstract VirtualFile refreshAndFindFileByUrl(@NotNull String url);
+  public abstract @Nullable VirtualFile refreshAndFindFileByUrl(@NotNull String url);
 
   /**
    * @deprecated Use {@link #VFS_CHANGES} message bus topic.
@@ -129,8 +126,7 @@ public abstract class VirtualFileManager implements ModificationTracker {
    * @return URL
    * @see VirtualFile#getUrl
    */
-  @NotNull
-  public static String constructUrl(@NotNull String protocol, @NotNull String path) {
+  public static @NotNull String constructUrl(@NotNull String protocol, @NotNull String path) {
     return protocol + URLUtil.SCHEME_SEPARATOR + path;
   }
 
@@ -141,8 +137,7 @@ public abstract class VirtualFileManager implements ModificationTracker {
    * @return protocol or {@code null} if there is no "://" in the URL
    * @see VirtualFileSystem#getProtocol
    */
-  @Nullable
-  public static String extractProtocol(@NotNull String url) {
+  public static @Nullable String extractProtocol(@NotNull String url) {
     int index = url.indexOf(URLUtil.SCHEME_SEPARATOR);
     if (index < 0) return null;
     return url.substring(0, index);
@@ -155,8 +150,7 @@ public abstract class VirtualFileManager implements ModificationTracker {
    * @param url the URL
    * @return path
    */
-  @NotNull
-  public static String extractPath(@NotNull String url) {
+  public static @NotNull String extractPath(@NotNull String url) {
     int index = url.indexOf(URLUtil.SCHEME_SEPARATOR);
     return index >= 0 ? url.substring(index + URLUtil.SCHEME_SEPARATOR.length()) : url;
   }
@@ -195,6 +189,5 @@ public abstract class VirtualFileManager implements ModificationTracker {
   public abstract int storeName(@NotNull String name);
 
   @ApiStatus.Internal
-  @NotNull
-  public abstract CharSequence getVFileName(int nameId);
+  public abstract @NotNull CharSequence getVFileName(int nameId);
 }
\ No newline at end of file
index 23b320c7dbef9d7547888254fcb8385211bc7133..b5a49234493b7c69bb6f684c9c6a67e8a27a3571 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.psi.util;
 
 import com.intellij.openapi.components.ServiceManager;
@@ -71,7 +71,8 @@ public interface PsiModificationTracker extends ModificationTracker {
    * A topic to subscribe for all PSI modification count changes.
    * @see com.intellij.util.messages.MessageBus
    */
-  Topic<Listener> TOPIC = new Topic<>("modification tracker", Listener.class, Topic.BroadcastDirection.TO_PARENT);
+  @Topic.ProjectLevel
+  Topic<Listener> TOPIC = new Topic<>(Listener.class, Topic.BroadcastDirection.TO_PARENT);
 
   /**
    * Tracks any PSI modification.
@@ -118,7 +119,6 @@ public interface PsiModificationTracker extends ModificationTracker {
    */
   @FunctionalInterface
   interface Listener {
-
     /**
      * A method invoked on Swing EventDispatchThread each time any physical PSI change is detected
      */
index efc97590ca635628c0bca0367b1de650e169d785..af36bc9bf43260ff788ebee631b665c2b00f0139 100644 (file)
@@ -1,23 +1,22 @@
 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.util.messages;
 
-import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.application.ApplicationManager;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public abstract class MessageBusFactory {
   public static MessageBusFactory getInstance() {
-    return ServiceManager.getService(MessageBusFactory.class);
+    return ApplicationManager.getApplication().getService(MessageBusFactory.class);
   }
 
-  public abstract @NotNull MessageBus createMessageBus(@NotNull MessageBusOwner owner);
-  public abstract @NotNull MessageBus createMessageBus(@NotNull MessageBusOwner owner, @NotNull MessageBus parentBus);
+  public abstract @NotNull MessageBus createMessageBus(@NotNull MessageBusOwner owner, @Nullable MessageBus parentBus);
 
   public static @NotNull MessageBus newMessageBus(@NotNull MessageBusOwner owner) {
-    return getInstance().createMessageBus(owner);
+    return getInstance().createMessageBus(owner, null);
   }
 
   public static @NotNull MessageBus newMessageBus(@NotNull MessageBusOwner owner, @Nullable MessageBus parentBus) {
-    return parentBus == null ? newMessageBus(owner) : getInstance().createMessageBus(owner, parentBus);
+    return getInstance().createMessageBus(owner, parentBus);
   }
 }
\ No newline at end of file
index 86727836ec51b1919ddc90ce48c915eeecf6430c..3a900bc206aefc29a9844324cf2d173cbe79b0d9 100644 (file)
@@ -1,10 +1,16 @@
 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.util.messages;
 
+import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.NotNull;
 
+@ApiStatus.Internal
 public interface MessageBusOwner {
   @NotNull Object createListener(@NotNull ListenerDescriptor descriptor);
 
   boolean isDisposed();
+
+  default boolean isParentLazyListenersIgnored() {
+    return false;
+  }
 }
diff --git a/platform/core-api/src/com/intellij/util/messages/impl/CompositeMessageBus.java b/platform/core-api/src/com/intellij/util/messages/impl/CompositeMessageBus.java
new file mode 100644 (file)
index 0000000..cd46ae0
--- /dev/null
@@ -0,0 +1,347 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.util.messages.impl;
+
+import com.intellij.openapi.extensions.ExtensionNotApplicableException;
+import com.intellij.openapi.extensions.PluginId;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.EventDispatcher;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.messages.ListenerDescriptor;
+import com.intellij.util.messages.MessageBusOwner;
+import com.intellij.util.messages.Topic;
+import com.intellij.util.messages.Topic.BroadcastDirection;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.ConcurrentMap;
+
+class CompositeMessageBus extends MessageBusImpl implements MessageBusEx {
+  private final List<MessageBusImpl> childBuses = ContainerUtil.createLockFreeCopyOnWriteList();
+  private volatile @NotNull Map<String, List<ListenerDescriptor>> topicClassToListenerDescriptor = Collections.emptyMap();
+
+  CompositeMessageBus(@NotNull MessageBusOwner owner, @NotNull CompositeMessageBus parentBus) {
+    super(owner, parentBus);
+  }
+
+  // root message bus constructor
+  protected CompositeMessageBus(@NotNull MessageBusOwner owner) {
+    super(owner);
+  }
+
+  /**
+   * Must be a concurrent map, because remove operation may be concurrently performed (synchronized only per topic).
+   */
+  @Override
+  public final void setLazyListeners(@NotNull ConcurrentMap<String, List<ListenerDescriptor>> map) {
+    if (topicClassToListenerDescriptor == Collections.<String, List<ListenerDescriptor>>emptyMap()) {
+      topicClassToListenerDescriptor = map;
+    }
+    else {
+      topicClassToListenerDescriptor.putAll(map);
+      // adding project level listener for app level topic is not recommended, but supported
+      if (myRootBus != this) {
+        myRootBus.subscriberCache.clear();
+      }
+      subscriberCache.clear();
+    }
+  }
+
+  @Override
+  protected final boolean hasChildren() {
+    return !childBuses.isEmpty();
+  }
+
+  /**
+   * calculates {@link #myOrder} for the given child bus
+   */
+  final synchronized int @NotNull [] addChild(@NotNull MessageBusImpl bus) {
+    List<MessageBusImpl> children = childBuses;
+    int lastChildIndex = children.isEmpty() ? 0 : ArrayUtil.getLastElement(children.get(children.size() - 1).myOrder, 0);
+    if (lastChildIndex == Integer.MAX_VALUE) {
+      LOG.error("Too many child buses");
+    }
+    children.add(bus);
+    return ArrayUtil.append(myOrder, lastChildIndex + 1);
+  }
+
+  final void onChildBusDisposed(@NotNull MessageBusImpl childBus) {
+    boolean removed = childBuses.remove(childBus);
+    myRootBus.myWaitingBuses.get().remove(childBus);
+
+    MessageBusImpl parentBus = this;
+    do {
+      parentBus.subscriberCache.clear();
+    }
+    while ((parentBus = parentBus.myParentBus) != null);
+    LOG.assertTrue(removed);
+  }
+
+  @Override
+  protected final @NotNull MessageBusImpl.MessagePublisher createPublisher(@NotNull Topic<?> topic, BroadcastDirection direction) {
+    if (direction == BroadcastDirection.TO_PARENT) {
+      return new ToParentMessagePublisher(topic, this);
+    }
+    else if (direction == BroadcastDirection.TO_DIRECT_CHILDREN) {
+      if (myParentBus != null) {
+        throw new IllegalArgumentException("Broadcast direction TO_DIRECT_CHILDREN is allowed only for app level message bus. " +
+                                           "Please publish to app level message bus or change topic broadcast direction to NONE or TO_PARENT");
+      }
+      return new ToDirectChildrenMessagePublisher(topic, this);
+    }
+    else {
+      return new MessagePublisher(topic, this);
+    }
+  }
+
+  private static final class ToDirectChildrenMessagePublisher extends MessagePublisher implements InvocationHandler {
+    ToDirectChildrenMessagePublisher(@NotNull Topic<?> topic, @NotNull CompositeMessageBus bus) {
+      super(topic, bus);
+    }
+
+    @Override
+    protected final boolean publish(@NotNull Method method, Object[] args, @Nullable JobQueue jobQueue) {
+      List<Throwable> exceptions = null;
+      boolean hasHandlers = false;
+
+      List<Object> handlers = bus.subscriberCache.computeIfAbsent(topic, bus::computeSubscribers);
+      if (!handlers.isEmpty()) {
+        exceptions = executeOrAddToQueue(topic, method, args, handlers, jobQueue, bus.messageDeliveryListener, null);
+        hasHandlers = true;
+      }
+
+      for (MessageBusImpl childBus : ((CompositeMessageBus)bus).childBuses) {
+        // light project in tests is not disposed correctly
+        if (childBus.owner.isDisposed()) {
+          continue;
+        }
+
+        handlers = childBus.subscriberCache.computeIfAbsent(topic, topic1 -> {
+          List<Object> result = new ArrayList<>();
+          childBus.doComputeSubscribers(topic1, result, /* subscribeLazyListeners = */ !childBus.owner.isParentLazyListenersIgnored());
+          return result.isEmpty() ? Collections.emptyList() : result;
+        });
+        if (handlers.isEmpty()) {
+          continue;
+        }
+
+        hasHandlers = true;
+        exceptions = executeOrAddToQueue(topic, method, args, handlers, jobQueue, bus.messageDeliveryListener, exceptions);
+      }
+
+      if (exceptions != null) {
+        EventDispatcher.throwExceptions(exceptions);
+      }
+      return hasHandlers;
+    }
+  }
+
+  @Override
+  protected final @NotNull List<Object> computeSubscribers(@NotNull Topic<?> topic) {
+    // light project
+    if (owner.isDisposed()) {
+      return Collections.emptyList();
+    }
+    return super.computeSubscribers(topic);
+  }
+
+  @Override
+  protected final void doComputeSubscribers(@NotNull Topic<?> topic, @NotNull List<Object> result, boolean subscribeLazyListeners) {
+    if (subscribeLazyListeners) {
+      subscribeLazyListeners(topic);
+    }
+
+    super.doComputeSubscribers(topic, result, subscribeLazyListeners);
+
+    if (topic.getBroadcastDirection() == BroadcastDirection.TO_CHILDREN) {
+      for (MessageBusImpl childBus : childBuses) {
+        if (!childBus.isDisposed()) {
+          childBus.doComputeSubscribers(topic, result, !childBus.owner.isParentLazyListenersIgnored());
+        }
+      }
+    }
+  }
+
+  private void subscribeLazyListeners(@NotNull Topic<?> topic) {
+    if (topic.getListenerClass() == Runnable.class) {
+      return;
+    }
+
+    List<ListenerDescriptor> listenerDescriptors = topicClassToListenerDescriptor.remove(topic.getListenerClass().getName());
+    if (listenerDescriptors == null) {
+      return;
+    }
+
+    // use linked hash map for repeatable results
+    LinkedHashMap<PluginId, List<Object>> listenerMap = new LinkedHashMap<>();
+    for (ListenerDescriptor listenerDescriptor : listenerDescriptors) {
+      try {
+        listenerMap.computeIfAbsent(listenerDescriptor.pluginDescriptor.getPluginId(), __ -> new ArrayList<>()).add(
+          owner.createListener(listenerDescriptor));
+      }
+      catch (ExtensionNotApplicableException ignore) {
+      }
+      catch (ProcessCanceledException e) {
+        throw e;
+      }
+      catch (Throwable e) {
+        LOG.error("Cannot create listener", e);
+      }
+    }
+
+    listenerMap.forEach((key, listeners) -> {
+      mySubscribers.add(new DescriptorBasedMessageBusConnection(key, topic, listeners));
+    });
+  }
+
+  @Override
+  protected final void notifyOnSubscriptionToTopicToChildren(@NotNull Topic<?> topic) {
+    for (MessageBusImpl childBus : childBuses) {
+      childBus.subscriberCache.remove(topic);
+      childBus.notifyOnSubscriptionToTopicToChildren(topic);
+    }
+  }
+
+  @Override
+  protected final void clearSubscriberCacheRecursively(@Nullable Map<Topic<?>, Object> handlers, @Nullable Topic<?> topic) {
+    clearSubscriberCache(this, handlers, topic);
+    childBuses.forEach(childBus -> childBus.clearSubscriberCacheRecursively(handlers, topic));
+  }
+
+  @Override
+  final boolean notifyConnectionTerminated(@NotNull Map<Topic<?>, Object> handlers) {
+    boolean isChildClearingNeeded = super.notifyConnectionTerminated(handlers);
+    if (!isChildClearingNeeded) {
+      return false;
+    }
+
+    childBuses.forEach(childBus -> childBus.clearSubscriberCache(handlers));
+
+    // disposed handlers are not removed for TO_CHILDREN topics in the same way as for others directions because it is not wise to check each child bus -
+    // waitingBuses list can be used instead of checking each child bus message queue
+    SortedSet<MessageBusImpl> waitingBuses = myRootBus.myWaitingBuses.get();
+    if (!waitingBuses.isEmpty()) {
+      waitingBuses.removeIf(bus -> {
+        JobQueue jobQueue = bus.myMessageQueue.get();
+        return !jobQueue.queue.isEmpty() &&
+               jobQueue.queue.removeIf(job -> MessageBusConnectionImpl.removeMyHandlers(job, handlers) && job.handlers.isEmpty()) &&
+               jobQueue.current == null &&
+               jobQueue.queue.isEmpty();
+      });
+    }
+    return false;
+  }
+
+  @Override
+  protected final void clearSubscriberCache(@NotNull Map<Topic<?>, Object> handlers) {
+    super.clearSubscriberCache(handlers);
+    childBuses.forEach(childBus -> childBus.clearSubscriberCache(handlers));
+  }
+
+  @Override
+  protected final void removeChildConnectionsRecursively(@NotNull Topic<?> topic, @Nullable Object handlers) {
+    childBuses.forEach(childBus -> childBus.removeChildConnectionsRecursively(topic, handlers));
+  }
+
+  @Override
+  protected final void removeEmptyConnectionsRecursively() {
+    super.removeEmptyConnectionsRecursively();
+
+    childBuses.forEach(MessageBusImpl::removeEmptyConnectionsRecursively);
+  }
+
+  /**
+   * Clear publisher cache, including child buses.
+   */
+  @Override
+  public final void clearPublisherCache() {
+    // keep it simple - we can infer plugin id from topic.getListenerClass(), but granular clearing not worth the code complication
+    publisherCache.clear();
+    childBuses.forEach(childBus -> {
+      if (childBus instanceof CompositeMessageBus) {
+        ((CompositeMessageBus)childBus).clearPublisherCache();
+      }
+      else {
+        childBus.publisherCache.clear();
+      }
+    });
+  }
+
+  @Override
+  public final void unsubscribeLazyListeners(@NotNull PluginId pluginId, @NotNull List<ListenerDescriptor> listenerDescriptors) {
+    if (listenerDescriptors.isEmpty() || mySubscribers.isEmpty()) {
+      return;
+    }
+
+    Map<String, Set<String>> topicToDescriptors = new HashMap<>();
+    for (ListenerDescriptor descriptor : listenerDescriptors) {
+      topicToDescriptors.computeIfAbsent(descriptor.topicClassName, __ -> new HashSet<>()).add(descriptor.listenerClassName);
+    }
+
+    boolean isChanged = false;
+    List<DescriptorBasedMessageBusConnection> newSubscribers = null;
+    for (Iterator<MessageHandlerHolder> connectionIterator = mySubscribers.iterator(); connectionIterator.hasNext(); ) {
+      MessageHandlerHolder holder = connectionIterator.next();
+      if (!(holder instanceof DescriptorBasedMessageBusConnection)) {
+        continue;
+      }
+
+      DescriptorBasedMessageBusConnection connection = (DescriptorBasedMessageBusConnection)holder;
+      if (connection.pluginId != pluginId) {
+        continue;
+      }
+
+      Set<String> listenerClassNames = topicToDescriptors.get(connection.topic.getListenerClass().getName());
+      if (listenerClassNames == null) {
+        continue;
+      }
+
+      List<Object> newHandlers = DescriptorBasedMessageBusConnection.computeNewHandlers(connection.handlers, listenerClassNames);
+      if (newHandlers == null) {
+        continue;
+      }
+
+      isChanged = true;
+      connectionIterator.remove();
+      if (!newHandlers.isEmpty()) {
+        if (newSubscribers == null) {
+          newSubscribers = new ArrayList<>();
+        }
+        newSubscribers.add(new DescriptorBasedMessageBusConnection(pluginId, connection.topic, newHandlers));
+      }
+    }
+
+    // todo it means that order of subscribers is not preserved
+    // it is very minor requirement, but still, makes sense to comply it
+    if (newSubscribers != null) {
+      mySubscribers.addAll(newSubscribers);
+    }
+    if (isChanged) {
+      // we can check it more precisely, but for simplicity, just clear all
+      // adding project level listener for app level topic is not recommended, but supported
+      if (myRootBus != this) {
+        myRootBus.subscriberCache.clear();
+      }
+      subscriberCache.clear();
+    }
+  }
+
+  @Override
+  @TestOnly
+  public final void clearAllSubscriberCache() {
+    LOG.assertTrue(myRootBus != this);
+    myRootBus.subscriberCache.clear();
+    subscriberCache.clear();
+    childBuses.forEach(bus -> bus.subscriberCache.clear());
+  }
+
+  @Override
+  protected final void disposeChildren() {
+    childBuses.forEach(Disposer::dispose);
+  }
+}
diff --git a/platform/core-api/src/com/intellij/util/messages/impl/DescriptorBasedMessageBusConnection.java b/platform/core-api/src/com/intellij/util/messages/impl/DescriptorBasedMessageBusConnection.java
new file mode 100644 (file)
index 0000000..8e3e15a
--- /dev/null
@@ -0,0 +1,59 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.util.messages.impl;
+
+import com.intellij.openapi.extensions.PluginId;
+import com.intellij.util.messages.Topic;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+final class DescriptorBasedMessageBusConnection implements MessageBusImpl.MessageHandlerHolder {
+  final PluginId pluginId;
+  final Topic<?> topic;
+  final List<Object> handlers;
+
+  DescriptorBasedMessageBusConnection(@NotNull PluginId pluginId, @NotNull Topic<?> topic, @NotNull List<Object> handlers) {
+    this.pluginId = pluginId;
+    this.topic = topic;
+    this.handlers = handlers;
+  }
+
+  @Override
+  public void collectHandlers(@NotNull Topic<?> topic, @NotNull List<Object> result) {
+    if (this.topic == topic) {
+      result.addAll(handlers);
+    }
+  }
+
+  @Override
+  public boolean isEmpty() {
+    // never empty
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    return "DescriptorBasedMessageBusConnection(" +
+           "handlers=" + handlers +
+           ')';
+  }
+
+  static @Nullable List<Object> computeNewHandlers(@NotNull List<Object> handlers, @NotNull Set<String> excludeClassNames) {
+    List<Object> newHandlers = null;
+    for (int i = 0, size = handlers.size(); i < size; i++) {
+      Object handler = handlers.get(i);
+      if (excludeClassNames.contains(handler.getClass().getName())) {
+        if (newHandlers == null) {
+          newHandlers = i == 0 ? new ArrayList<>() : new ArrayList<>(handlers.subList(0, i));
+        }
+      }
+      else if (newHandlers != null) {
+        newHandlers.add(handler);
+      }
+    }
+    return newHandlers;
+  }
+}
index 6e7a1823b01a3b3132c75950c64dcd3bf40b382b..1b863f8da1b65c112b429566fb2bf9d16cede78c 100644 (file)
@@ -11,10 +11,11 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicReference;
 
 final class MessageBusConnectionImpl implements MessageBusConnection, MessageBusImpl.MessageHandlerHolder {
-  private final MessageBusImpl myBus;
+  private MessageBusImpl myBus;
 
   private MessageHandler myDefaultHandler;
   private final AtomicReference<SmartFMap<Topic<?>, Object>> mySubscriptions = new AtomicReference<>(SmartFMap.emptyMap());
@@ -25,10 +26,15 @@ final class MessageBusConnectionImpl implements MessageBusConnection, MessageBus
 
   @Override
   public <L> void subscribe(@NotNull Topic<L> topic, @NotNull L handler) {
+    doSubscribe(topic, handler, mySubscriptions);
+    myBus.notifyOnSubscription(topic);
+  }
+
+  static <L> void doSubscribe(@NotNull Topic<L> topic, @NotNull L handler, @NotNull AtomicReference<SmartFMap<Topic<?>, Object>> topicToHandlers) {
     Object newHandlers;
     SmartFMap<Topic<?>, Object> map;
     do {
-      map = mySubscriptions.get();
+      map = topicToHandlers.get();
       Object currentHandler = map.get(topic);
       if (currentHandler == null) {
         newHandlers = handler;
@@ -40,9 +46,7 @@ final class MessageBusConnectionImpl implements MessageBusConnection, MessageBus
         newHandlers = new Object[]{currentHandler, handler};
       }
     }
-    while (!mySubscriptions.compareAndSet(map, map.plus(topic, newHandlers)));
-
-    myBus.notifyOnSubscription(topic);
+    while (!topicToHandlers.compareAndSet(map, map.plus(topic, newHandlers)));
   }
 
   @Override
@@ -81,7 +85,18 @@ final class MessageBusConnectionImpl implements MessageBusConnection, MessageBus
 
   @Override
   public void dispose() {
-    myBus.notifyConnectionTerminated(this);
+    MessageBusImpl bus = myBus;
+    if (bus == null) {
+      // already disposed
+      return;
+    }
+
+    // reset as bus will not remove disposed connection from list immediately
+    SmartFMap<Topic<?>, Object> oldMap = mySubscriptions.getAndSet(SmartFMap.emptyMap());
+
+    myBus = null;
+    myDefaultHandler = null;
+    bus.notifyConnectionTerminated(oldMap);
   }
 
   @Override
@@ -94,38 +109,25 @@ final class MessageBusConnectionImpl implements MessageBusConnection, MessageBus
     myBus.deliverImmediately(this);
   }
 
-  void removeMyHandlers(@NotNull Message job) {
-    List<Object> jobHandlers = job.handlers;
-    if (myDefaultHandler != null) {
-      jobHandlers.removeIf(handler -> handler == myDefaultHandler);
-    }
-
-    Object handlers = mySubscriptions.get().get(job.topic);
-    if (handlers == null) {
-      return;
-    }
+  static boolean removeMyHandlers(@NotNull Message job, @NotNull Map<Topic<?>, Object> topicToHandlers) {
+    Object handlers = topicToHandlers.get(job.topic);
+    return handlers != null && removeHandlers(job, handlers);
+  }
 
+  static boolean removeHandlers(@NotNull Message job, @NotNull Object handlers) {
     if (handlers instanceof Object[]) {
-      jobHandlers.removeIf(handler -> containsByIdentity(handler, (Object[])handlers));
+      return job.handlers.removeIf(handler -> containsByIdentity(handler, (Object[])handlers));
     }
     else {
-      jobHandlers.removeIf(handler -> handlers == handler);
+      return job.handlers.removeIf(handler -> handlers == handler);
     }
   }
 
-  boolean isEmpty() {
+  @Override
+  public boolean isEmpty() {
     return mySubscriptions.get().isEmpty();
   }
 
-  boolean isBroadCastDisabled() {
-    for (Topic<?> topic : mySubscriptions.get().keySet()) {
-      if (topic.getBroadcastDirection() != Topic.BroadcastDirection.NONE) {
-        return false;
-      }
-    }
-    return true;
-  }
-
   @Override
   public String toString() {
     return mySubscriptions.get().toString();
@@ -143,7 +145,7 @@ final class MessageBusConnectionImpl implements MessageBusConnection, MessageBus
     return handlers == handler || (handlers instanceof Object[] && containsByIdentity(handler, (Object[])handlers));
   }
 
-  private static boolean containsByIdentity(@NotNull Object handler, @NotNull Object[] handlers) {
+  private static boolean containsByIdentity(@NotNull Object handler, @NotNull Object @NotNull[] handlers) {
     for (Object item : handlers) {
       if (handler == item) {
         return true;
diff --git a/platform/core-api/src/com/intellij/util/messages/impl/MessageBusEx.java b/platform/core-api/src/com/intellij/util/messages/impl/MessageBusEx.java
new file mode 100644 (file)
index 0000000..15820da
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.util.messages.impl;
+
+import com.intellij.openapi.extensions.PluginId;
+import com.intellij.util.messages.ListenerDescriptor;
+import com.intellij.util.messages.MessageBus;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.TestOnly;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentMap;
+
+@ApiStatus.Internal
+public interface MessageBusEx extends MessageBus {
+  void clearPublisherCache();
+
+  void unsubscribeLazyListeners(@NotNull PluginId pluginId, @NotNull List<ListenerDescriptor> listenerDescriptors);
+
+  @TestOnly
+  void clearAllSubscriberCache();
+
+  void setLazyListeners(@NotNull ConcurrentMap<String, List<ListenerDescriptor>> map);
+}
index 72c69eca283d744f99565fe905ab5cb75b2c4149..f40912893e6cc75798325734c11e01afe398d3ce 100644 (file)
@@ -1,21 +1,31 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.util.messages.impl;
 
 import com.intellij.util.messages.MessageBus;
 import com.intellij.util.messages.MessageBusFactory;
 import com.intellij.util.messages.MessageBusOwner;
+import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
+@ApiStatus.Internal
 public final class MessageBusFactoryImpl extends MessageBusFactory {
-  @NotNull
   @Override
-  public MessageBus createMessageBus(@NotNull MessageBusOwner owner) {
-    return new MessageBusImpl.RootBus(owner);
+  public @NotNull MessageBus createMessageBus(@NotNull MessageBusOwner owner, @Nullable MessageBus parentBus) {
+    if (parentBus == null) {
+      return createRootBus(owner);
+    }
+
+    CompositeMessageBus parent = (CompositeMessageBus)parentBus;
+    if (parent.getParent() == null) {
+      return new CompositeMessageBus(owner, parent);
+    }
+    else {
+      return new MessageBusImpl(owner, parent);
+    }
   }
 
-  @NotNull
-  @Override
-  public MessageBus createMessageBus(@NotNull MessageBusOwner owner, @NotNull MessageBus parentBus) {
-    return new MessageBusImpl(owner, (MessageBusImpl)parentBus);
+  public static @NotNull MessageBusImpl.RootBus createRootBus(@NotNull MessageBusOwner owner) {
+    return new MessageBusImpl.RootBus(owner);
   }
 }
index 8a78ce1df9cb6619b14946937f591a01e161ce38..2b16f1e9093010480cef07a0816a5f6e286c77ef 100644 (file)
@@ -3,109 +3,88 @@ package com.intellij.util.messages.impl;
 
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.extensions.ExtensionNotApplicableException;
-import com.intellij.openapi.extensions.PluginId;
-import com.intellij.openapi.progress.ProcessCanceledException;
 import com.intellij.openapi.util.Disposer;
 import com.intellij.util.ArrayUtil;
 import com.intellij.util.EventDispatcher;
 import com.intellij.util.SmartList;
-import com.intellij.util.containers.ContainerUtil;
-import com.intellij.util.lang.CompoundRuntimeException;
+import com.intellij.util.concurrency.AppExecutorUtil;
 import com.intellij.util.messages.*;
+import com.intellij.util.messages.Topic.BroadcastDirection;
 import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.jetbrains.annotations.TestOnly;
 
-import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
 
 @ApiStatus.Internal
 public class MessageBusImpl implements MessageBus {
   interface MessageHandlerHolder {
     void collectHandlers(@NotNull Topic<?> topic, @NotNull List<Object> result);
+
+    boolean isEmpty();
   }
 
-  private static final Logger LOG = Logger.getInstance(MessageBusImpl.class);
-  private final ThreadLocal<JobQueue> myMessageQueue = ThreadLocal.withInitial(JobQueue::new);
+  protected static final Logger LOG = Logger.getInstance(MessageBusImpl.class);
+  private static final int DISPOSE_IN_PROGRESS = 1;
+  private static final int DISPOSED_STATE = 2;
+  private static final Object NA = new Object();
+
+  protected final ThreadLocal<JobQueue> myMessageQueue = ThreadLocal.withInitial(JobQueue::new);
 
   /**
    * Root's order is empty
    * Child bus's order is its parent order plus one more element, an int that's bigger than that of all sibling buses that come before
    * Sorting by these vectors lexicographically gives DFS order
    */
-  private final int[] myOrder;
+  protected final int[] myOrder;
 
-  private final ConcurrentMap<Topic<?>, Object> publisherCache = new ConcurrentHashMap<>();
+  protected final ConcurrentMap<Topic<?>, Object> publisherCache = new ConcurrentHashMap<>();
 
-  private final Collection<MessageHandlerHolder> mySubscribers = new ConcurrentLinkedQueue<>();
+  protected final Collection<MessageHandlerHolder> mySubscribers = new ConcurrentLinkedQueue<>();
+  // caches subscribers for this bus and its children or parent, depending on the topic's broadcast policy
+  protected final Map<Topic<?>, List<Object>> subscriberCache = new ConcurrentHashMap<>();
 
-  /**
-   * Caches subscribers for this bus and its children or parent, depending on the topic's broadcast policy
-   */
-  private final Map<Topic<?>, List<Object>> mySubscriberCache = new ConcurrentHashMap<>();
-  private final List<MessageBusImpl> myChildBuses = ContainerUtil.createLockFreeCopyOnWriteList();
-
-  private volatile @NotNull Map<String, List<ListenerDescriptor>> topicClassToListenerDescriptor = Collections.emptyMap();
+  protected final @Nullable CompositeMessageBus myParentBus;
+  protected final RootBus myRootBus;
 
-  private static final Object NA = new Object();
-  private final MessageBusImpl myParentBus;
+  protected final MessageBusOwner owner;
+  // 0 active, 1 dispose in progress 2 disposed
+  private int disposeState;
+  // separate disposable must be used, because container will dispose bus connections in a separate step
+  private Disposable myConnectionDisposable = Disposer.newDisposable();
+  protected MessageDeliveryListener messageDeliveryListener;
 
-  private final RootBus myRootBus;
-
-  private final MessageBusOwner myOwner;
-  private boolean myDisposed;
-  private Disposable myConnectionDisposable;
-  private MessageDeliveryListener myMessageDeliveryListener;
-
-  private boolean myIgnoreParentLazyListeners;
-
-  public MessageBusImpl(@NotNull MessageBusOwner owner, @NotNull MessageBusImpl parentBus) {
-    myOwner = owner;
-    myConnectionDisposable = createConnectionDisposable(owner);
+  public MessageBusImpl(@NotNull MessageBusOwner owner, @NotNull CompositeMessageBus parentBus) {
+    this.owner = owner;
     myParentBus = parentBus;
     myRootBus = parentBus.myRootBus;
-    myOrder = parentBus.addChild(this);
-    myRootBus.clearSubscriberCache();
-  }
 
-  private static @NotNull Disposable createConnectionDisposable(@NotNull MessageBusOwner owner) {
-    // separate disposable must be used, because container will dispose bus connections in a separate step
-    return Disposer.newDisposable(owner.toString());
+    MessageBusImpl p = this;
+    while ((p = p.myParentBus) != null) {
+      p.subscriberCache.clear();
+    }
+
+    myOrder = parentBus.addChild(this);
   }
 
   // root message bus constructor
-  private MessageBusImpl(@NotNull MessageBusOwner owner) {
-    myOwner = owner;
-    myConnectionDisposable = createConnectionDisposable(owner);
+  MessageBusImpl(@NotNull MessageBusOwner owner) {
+    this.owner = owner;
     myOrder = ArrayUtil.EMPTY_INT_ARRAY;
     myRootBus = (RootBus)this;
     myParentBus = null;
   }
 
-  public final void setIgnoreParentLazyListeners(boolean ignoreParentLazyListeners) {
-    myIgnoreParentLazyListeners = ignoreParentLazyListeners;
-  }
-
-  /**
-   * Must be a concurrent map, because remove operation may be concurrently performed (synchronized only per topic).
-   */
-  public final void setLazyListeners(@NotNull ConcurrentMap<String, List<ListenerDescriptor>> map) {
-    if (topicClassToListenerDescriptor == Collections.<String, List<ListenerDescriptor>>emptyMap()) {
-      topicClassToListenerDescriptor = map;
-    }
-    else {
-      topicClassToListenerDescriptor.putAll(map);
-      clearSubscriberCache();
-    }
-  }
-
   @Override
   public final MessageBus getParent() {
     return myParentBus;
@@ -113,31 +92,11 @@ public class MessageBusImpl implements MessageBus {
 
   @Override
   public final String toString() {
-    return super.toString() + "; owner=" + myOwner + (isDisposed() ? "; disposed" : "");
-  }
-
-  /**
-   * calculates {@link #myOrder} for the given child bus
-   */
-  private synchronized int @NotNull [] addChild(@NotNull MessageBusImpl bus) {
-    List<MessageBusImpl> children = myChildBuses;
-    int lastChildIndex = children.isEmpty() ? 0 : ArrayUtil.getLastElement(children.get(children.size() - 1).myOrder, 0);
-    if (lastChildIndex == Integer.MAX_VALUE) {
-      LOG.error("Too many child buses");
-    }
-    children.add(bus);
-    return ArrayUtil.append(myOrder, lastChildIndex + 1);
-  }
-
-  private void onChildBusDisposed(@NotNull MessageBusImpl childBus) {
-    boolean removed = myChildBuses.remove(childBus);
-    myRootBus.myWaitingBuses.get().remove(childBus);
-    myRootBus.clearSubscriberCache();
-    LOG.assertTrue(removed);
+    return "MessageBus(owner=" + owner + ", disposeState= " + disposeState + ")";
   }
 
   @Override
-  public final  @NotNull MessageBusConnectionImpl connect() {
+  public final @NotNull MessageBusConnection connect() {
     return connect(myConnectionDisposable);
   }
 
@@ -151,130 +110,146 @@ public class MessageBusImpl implements MessageBus {
   }
 
   @Override
+  public final @NotNull SimpleMessageBusConnection simpleConnect() {
+    // avoid registering in Dispose tree, default handler and deliverImmediately are not supported
+    checkNotDisposed();
+    SimpleMessageBusConnectionImpl connection = new SimpleMessageBusConnectionImpl(this);
+    mySubscribers.add(connection);
+    return connection;
+  }
+
+  @Override
   public final @NotNull <L> L syncPublisher(@NotNull Topic<L> topic) {
     checkNotDisposed();
     //noinspection unchecked
-    return (L)publisherCache.computeIfAbsent(topic, this::createPublisher);
+    return (L)publisherCache.computeIfAbsent(topic, this::createPublisherInvocationHandler);
   }
 
-  /**
-   * Clear publisher cache, including child buses.
-   */
-  public final void clearPublisherCache() {
-    // keep it simple - we can infer plugin id from topic.getListenerClass(), but granular clearing not worth the code complication
-    publisherCache.clear();
-    for (MessageBusImpl childBus : myChildBuses) {
-      childBus.clearPublisherCache();
-    }
+  // separate method to avoid param clash (ensure that lambda is not local - doesn't use method parameter)
+  private @NotNull Object createPublisherInvocationHandler(@NotNull Topic<?> topic) {
+    Class<?> listenerClass = topic.getListenerClass();
+    return Proxy.newProxyInstance(listenerClass.getClassLoader(), new Class[]{listenerClass}, createPublisher(topic, topic.getBroadcastDirection()));
   }
 
-  private <L> void subscribeLazyListeners(@NotNull Topic<L> topic) {
-    List<ListenerDescriptor> listenerDescriptors = topicClassToListenerDescriptor.remove(topic.getListenerClass().getName());
-    if (listenerDescriptors == null) {
-      return;
+  protected @NotNull MessageBusImpl.MessagePublisher createPublisher(@NotNull Topic<?> topic, BroadcastDirection direction) {
+    if (direction == BroadcastDirection.TO_PARENT) {
+      return new ToParentMessagePublisher(topic, this);
     }
-
-    // use linked hash map for repeatable results
-    LinkedHashMap<PluginId, List<Object>> listenerMap = new LinkedHashMap<>();
-    for (ListenerDescriptor listenerDescriptor : listenerDescriptors) {
-      try {
-        listenerMap.computeIfAbsent(listenerDescriptor.pluginDescriptor.getPluginId(), __ -> new ArrayList<>()).add(myOwner.createListener(listenerDescriptor));
-      }
-      catch (ExtensionNotApplicableException ignore) {
-      }
-      catch (ProcessCanceledException e) {
-        throw e;
-      }
-      catch (Throwable e) {
-        LOG.error("Cannot create listener", e);
-      }
+    else if (direction == BroadcastDirection.TO_DIRECT_CHILDREN) {
+      throw new IllegalArgumentException("Broadcast direction TO_DIRECT_CHILDREN is allowed only for app level message bus. " +
+                                         "Please publish to app level message bus or change topic broadcast direction to NONE or TO_PARENT");
+    }
+    else {
+      // warn as there is quite a lot such violations
+      LOG.warn("Broadcast direction TO_CHILDREN  is not allowed for module level message bus. Please change to NONE or TO_PARENT");
+      return new MessagePublisher(topic, this);
     }
-
-    listenerMap.forEach((key, listeners) -> {
-      mySubscribers.add(new DescriptorBasedMessageBusConnection(key, topic, listeners));
-    });
   }
 
-  public final void unsubscribeLazyListeners(@NotNull PluginId pluginId, @NotNull List<ListenerDescriptor> listenerDescriptors) {
-    if (listenerDescriptors.isEmpty() || mySubscribers.isEmpty()) {
-      return;
-    }
+  protected static class MessagePublisher implements InvocationHandler {
+    protected final @NotNull Topic<?> topic;
+    protected final @NotNull MessageBusImpl bus;
 
-    Map<String, Set<String>> topicToDescriptors = new HashMap<>();
-    for (ListenerDescriptor descriptor : listenerDescriptors) {
-      topicToDescriptors.computeIfAbsent(descriptor.topicClassName, __ -> new HashSet<>()).add(descriptor.listenerClassName);
+    MessagePublisher(@NotNull Topic<?> topic, @NotNull MessageBusImpl bus) {
+      this.topic = topic;
+      this.bus = bus;
     }
 
-    boolean isChanged = false;
-    List<DescriptorBasedMessageBusConnection> newSubscribers = null;
-    for (Iterator<MessageHandlerHolder> connectionIterator = mySubscribers.iterator(); connectionIterator.hasNext(); ) {
-      MessageHandlerHolder holder = connectionIterator.next();
-      if (!(holder instanceof DescriptorBasedMessageBusConnection)) {
-        continue;
+    @Override
+    public final Object invoke(Object proxy, Method method, Object[] args) {
+      if (method.getDeclaringClass().getName().equals("java.lang.Object")) {
+        return EventDispatcher.handleObjectMethod(proxy, args, method.getName());
       }
 
-      DescriptorBasedMessageBusConnection connection = (DescriptorBasedMessageBusConnection)holder;
-      if (connection.pluginId != pluginId) {
-        continue;
-      }
+      bus.checkNotDisposed();
 
-      Set<String> listenerClassNames = topicToDescriptors.get(connection.topic.getListenerClass().getName());
-      if (listenerClassNames == null) {
-        continue;
+      boolean isImmediateDelivery = topic.isImmediateDelivery();
+      Set<MessageBusImpl> busQueue;
+      JobQueue jobQueue;
+      if (isImmediateDelivery) {
+        busQueue = null;
+        jobQueue = null;
       }
-
-      List<Object> newHandlers = DescriptorBasedMessageBusConnection.computeNewHandlers(connection.handlers, listenerClassNames);
-      if (newHandlers == null) {
-        continue;
+      else {
+        busQueue = bus.myRootBus.myWaitingBuses.get();
+        jobQueue = bus.myMessageQueue.get();
+        pumpMessages(busQueue);
       }
 
-      isChanged = true;
-      connectionIterator.remove();
-      if (!newHandlers.isEmpty()) {
-        if (newSubscribers == null) {
-          newSubscribers = new ArrayList<>();
-        }
-        newSubscribers.add(new DescriptorBasedMessageBusConnection(pluginId, connection.topic, newHandlers));
+      if (publish(method, args, jobQueue) && !isImmediateDelivery) {
+        busQueue.add(bus);
+        // we must deliver messages now even if currently processing message queue, because if published as part of handler invocation,
+        // handler code expects that message will be delivered immediately after publishing
+        pumpMessages(busQueue);
       }
+      return NA;
     }
 
-    if (newSubscribers != null) {
-      mySubscribers.addAll(newSubscribers);
-    }
-    if (isChanged) {
-      clearSubscriberCache();
+    protected boolean publish(@NotNull Method method, Object[] args, @Nullable JobQueue jobQueue) {
+      List<Object> handlers = bus.subscriberCache.computeIfAbsent(topic, bus::computeSubscribers);
+      if (handlers.isEmpty()) {
+        return false;
+      }
+
+      List<Throwable> exceptions = executeOrAddToQueue(topic, method, args, handlers, jobQueue, bus.messageDeliveryListener, null);
+      if (exceptions != null) {
+        EventDispatcher.throwExceptions(exceptions);
+      }
+      return true;
     }
   }
 
-  private @NotNull Object createPublisher(@NotNull Topic<?> topic) {
-    Class<?> listenerClass = topic.getListenerClass();
-    return Proxy.newProxyInstance(listenerClass.getClassLoader(), new Class[]{listenerClass}, (proxy, method, args) -> {
-      if (method.getDeclaringClass().getName().equals("java.lang.Object")) {
-        return EventDispatcher.handleObjectMethod(proxy, args, method.getName());
-      }
+  protected static final class ToParentMessagePublisher extends MessagePublisher implements InvocationHandler {
+    ToParentMessagePublisher(@NotNull Topic<?> topic, @NotNull MessageBusImpl bus) {
+      super(topic, bus);
+    }
 
-      checkNotDisposed();
+    // args not-null
+    @Override
+    protected boolean publish(@NotNull Method method, Object[] args, @Nullable JobQueue jobQueue) {
+      List<Throwable> exceptions = null;
+      MessageBusImpl parentBus = bus;
+      boolean hasHandlers = false;
+      do {
+        List<Object> handlers = parentBus.subscriberCache.computeIfAbsent(topic, parentBus::computeSubscribers);
+        if (handlers.isEmpty()) {
+          continue;
+        }
 
-      Set<MessageBusImpl> busQueue = myRootBus.myWaitingBuses.get();
-      pumpMessages(busQueue);
+        hasHandlers = true;
+        exceptions = executeOrAddToQueue(topic, method, args, handlers, jobQueue, bus.messageDeliveryListener, exceptions);
+      }
+      while ((parentBus = parentBus.myParentBus) != null);
 
-      List<Object> handlers = getTopicSubscribers(topic);
-      if (handlers.isEmpty()) {
-        return NA;
+      if (exceptions != null) {
+        EventDispatcher.throwExceptions(exceptions);
       }
+      return hasHandlers;
+    }
+  }
 
-      JobQueue jobQueue = myMessageQueue.get();
+  // args not null
+  protected static List<Throwable> executeOrAddToQueue(@NotNull Topic<?> topic,
+                                                     @NotNull Method method,
+                                                     Object[] args,
+                                                     @NotNull List<Object> handlers,
+                                                     @Nullable JobQueue jobQueue,
+                                                     @Nullable MessageDeliveryListener messageDeliveryListener,
+                                                     @Nullable List<Throwable> exceptions) {
+    if (jobQueue == null) {
+      for (Object handler : handlers) {
+        exceptions = invokeListener(method, args, handler, topic, messageDeliveryListener, exceptions);
+      }
+    }
+    else {
       jobQueue.queue.offerLast(new Message(topic, method, args, handlers));
-
-      busQueue.add(this);
-      // we must deliver messages now even if currently processing message queue, because if published as part of handler invocation,
-      // handler code expects that message will be delivered immediately after publishing
-      pumpMessages(busQueue);
-      return NA;
-    });
+    }
+    return exceptions;
   }
 
   public final void disposeConnectionChildren() {
+    // avoid any work on notifyConnectionTerminated
+    disposeState = DISPOSE_IN_PROGRESS;
     Disposer.disposeChildren(myConnectionDisposable);
   }
 
@@ -284,16 +259,14 @@ public class MessageBusImpl implements MessageBus {
   }
 
   @Override
-  public final void dispose() {
-    if (myDisposed) {
+  public void dispose() {
+    if (disposeState == DISPOSED_STATE) {
       LOG.error("Already disposed: " + this);
     }
 
-    myDisposed = true;
+    disposeState = DISPOSED_STATE;
 
-    for (MessageBusImpl childBus : myChildBuses) {
-      Disposer.dispose(childBus);
-    }
+    disposeChildren();
 
     if (myConnectionDisposable != null) {
       Disposer.dispose(myConnectionDisposable);
@@ -305,17 +278,20 @@ public class MessageBusImpl implements MessageBus {
       LOG.error("Not delivered events in the queue: " + jobs);
     }
 
-    if (myParentBus != null) {
-      myParentBus.onChildBusDisposed(this);
+    if (myParentBus == null) {
+      myRootBus.myWaitingBuses.remove();
     }
     else {
-      myRootBus.myWaitingBuses.remove();
+      myParentBus.onChildBusDisposed(this);
     }
   }
 
+  protected void disposeChildren() {
+  }
+
   @Override
   public final boolean isDisposed() {
-    return myDisposed || myOwner.isDisposed();
+    return disposeState == DISPOSED_STATE || owner.isDisposed();
   }
 
   @Override
@@ -351,42 +327,17 @@ public class MessageBusImpl implements MessageBus {
     }
   }
 
-  private void calcSubscribers(@NotNull Topic<?> topic, @NotNull List<Object> result, boolean subscribeLazyListeners) {
-    if (subscribeLazyListeners) {
-      subscribeLazyListeners(topic);
-    }
-
+  protected void doComputeSubscribers(@NotNull Topic<?> topic, @NotNull List<Object> result, boolean subscribeLazyListeners) {
+    // todo — check that handler implements method (not a default implementation)
     for (MessageHandlerHolder subscriber : mySubscribers) {
       subscriber.collectHandlers(topic, result);
     }
-
-    Topic.BroadcastDirection direction = topic.getBroadcastDirection();
-
-    if (direction == Topic.BroadcastDirection.TO_CHILDREN) {
-      for (MessageBusImpl childBus : myChildBuses) {
-        if (!childBus.isDisposed()) {
-          childBus.calcSubscribers(topic, result, !childBus.myIgnoreParentLazyListeners);
-        }
-      }
-    }
-
-    if (direction == Topic.BroadcastDirection.TO_PARENT && myParentBus != null) {
-      myParentBus.calcSubscribers(topic, result, true);
-    }
   }
 
-  private @NotNull List<Object> getTopicSubscribers(@NotNull Topic<?> topic) {
-    return mySubscriberCache.computeIfAbsent(topic, topic1 -> {
-      // light project
-      if (myOwner.isDisposed()) {
-        return Collections.emptyList();
-      }
-
-      List<Object> result = new ArrayList<>();
-      calcSubscribers(topic1, result, true);
-      myRootBus.isSubscriberCacheCleared = false;
-      return result.isEmpty() ? Collections.emptyList() : result;
-    });
+  protected @NotNull List<Object> computeSubscribers(@NotNull Topic<?> topic) {
+    List<Object> result = new ArrayList<>();
+    doComputeSubscribers(topic, result, true);
+    return result.isEmpty() ? Collections.emptyList() : result;
   }
 
   private void jobRemoved(@NotNull JobQueue jobQueue) {
@@ -422,15 +373,17 @@ public class MessageBusImpl implements MessageBus {
       JobQueue jobQueue = bus.myMessageQueue.get();
       Message job = jobQueue.current;
       if (job != null) {
-        exceptions = bus.deliverMessage(job, jobQueue, bus.myMessageDeliveryListener, exceptions);
+        exceptions = bus.deliverMessage(job, jobQueue, bus.messageDeliveryListener, exceptions);
       }
 
       while ((job = jobQueue.queue.pollFirst()) != null) {
-        exceptions = bus.deliverMessage(job, jobQueue, bus.myMessageDeliveryListener, exceptions);
+        exceptions = bus.deliverMessage(job, jobQueue, bus.messageDeliveryListener, exceptions);
       }
     }
 
-    CompoundRuntimeException.throwIfNotEmpty(exceptions);
+    if (exceptions != null) {
+      EventDispatcher.throwExceptions(exceptions);
+    }
   }
 
   private @Nullable List<Throwable> deliverMessage(@NotNull Message job,
@@ -446,20 +399,7 @@ public class MessageBusImpl implements MessageBus {
       }
 
       job.currentHandlerIndex++;
-      try {
-        invokeListener(job, handlers.get(index), messageDeliveryListener);
-      }
-      catch (Throwable e) {
-        //noinspection InstanceofCatchParameter
-        Throwable cause = e instanceof InvocationTargetException && e.getCause() != null ? e.getCause() : e;
-        // Do nothing for AbstractMethodError. This listener just does not implement something newly added yet.
-        // AbstractMethodError is normally wrapped in InvocationTargetException,
-        // but some Java versions didn't do it in some cases (see http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6531596)
-        if (cause instanceof AbstractMethodError) continue;
-        if (exceptions == null) exceptions = new SmartList<>();
-        exceptions.add(cause);
-      }
-
+      exceptions = invokeListener(job.listenerMethod, job.args, handlers.get(index), job.topic, messageDeliveryListener, exceptions);
       if (++index != job.currentHandlerIndex) {
         // handler published some event and message queue including current job was processed as result, so, stop processing
         return exceptions;
@@ -468,65 +408,141 @@ public class MessageBusImpl implements MessageBus {
     return exceptions;
   }
 
+  protected boolean hasChildren() {
+    return false;
+  }
+
   final void notifyOnSubscription(@NotNull Topic<?> topic) {
-    if (topic.getBroadcastDirection() == Topic.BroadcastDirection.NONE) {
-      mySubscriberCache.clear();
+    subscriberCache.remove(topic);
+    if (topic.getBroadcastDirection() != BroadcastDirection.TO_CHILDREN) {
+      return;
     }
-    else {
-      myRootBus.clearSubscriberCache();
+
+    // Clear parents because parent caches subscribers for TO_CHILDREN direction on it's level and child levels.
+    // So, on subscription to child bus (this instance) parent cache must be invalidated.
+    MessageBusImpl parentBus = this;
+    while ((parentBus = parentBus.myParentBus) != null) {
+      parentBus.subscriberCache.remove(topic);
+    }
+
+    if (hasChildren()) {
+      notifyOnSubscriptionToTopicToChildren(topic);
     }
   }
 
-  @TestOnly
-  public final void clearAllSubscriberCache() {
-    myRootBus.clearSubscriberCache();
+  protected void notifyOnSubscriptionToTopicToChildren(@NotNull Topic<?> topic) {
   }
 
-  void clearSubscriberCache() {
-    mySubscriberCache.clear();
-    for (MessageBusImpl bus : myChildBuses) {
-      bus.clearSubscriberCache();
-    }
+  protected void removeChildConnectionsRecursively(@NotNull Topic<?> topic, @Nullable Object handlers) {
   }
 
-  final void notifyConnectionTerminated(@NotNull MessageBusConnectionImpl connection) {
-    mySubscribers.remove(connection);
-    if (isDisposed()) {
-      return;
+  protected void clearSubscriberCacheRecursively(@Nullable Map<Topic<?>, Object> handlers, @Nullable Topic<?> topic) {
+    clearSubscriberCache(this, handlers, topic);
+  }
+
+  // return false if no subscription
+  @SuppressWarnings("UnusedReturnValue")
+  protected static boolean clearSubscriberCache(@NotNull MessageBusImpl bus,
+                                                @Nullable Map<Topic<?>, Object> handlers,
+                                                @Nullable Topic<?> singleTopic) {
+    if (handlers == null) {
+      if (singleTopic == null) {
+        bus.subscriberCache.clear();
+        return true;
+      }
+      else {
+        return bus.subscriberCache.remove(singleTopic) != null;
+      }
     }
+    else {
+      // forEach must be used here as map here it is SmartFMap - avoid temporary map entries creation
+      ToChildrenTopicSubscriberCleaner cleaner = new ToChildrenTopicSubscriberCleaner(bus);
+      handlers.forEach(cleaner);
+      return cleaner.removed;
+    }
+  }
 
-    if (connection.isEmpty()) {
-      return;
+  private static final class ToChildrenTopicSubscriberCleaner implements BiConsumer<Topic<?>, Object> {
+    private final MessageBusImpl bus;
+    boolean removed;
+
+    ToChildrenTopicSubscriberCleaner(@NotNull MessageBusImpl bus) {
+      this.bus = bus;
     }
 
-    if (connection.isBroadCastDisabled()) {
-      mySubscriberCache.clear();
+    @Override
+    public void accept(Topic<?> topic, Object __) {
+      // other directions are already removed
+      BroadcastDirection direction = topic.getBroadcastDirection();
+      if (direction == BroadcastDirection.TO_CHILDREN) {
+        if (bus.subscriberCache.remove(topic) != null) {
+          removed = true;
+        }
+      }
     }
-    else {
-      myRootBus.clearSubscriberCache();
+  }
+
+  protected void removeEmptyConnectionsRecursively() {
+    mySubscribers.removeIf(MessageHandlerHolder::isEmpty);
+  }
+
+  boolean notifyConnectionTerminated(@NotNull Map<Topic<?>, Object> handlers) {
+    if (disposeState != 0) {
+      return false;
     }
 
-    JobQueue jobQueue = myMessageQueue.get();
-    if (jobQueue.queue.isEmpty()) {
-      return;
+    myRootBus.scheduleEmptyConnectionRemoving();
+
+    SubscriberCacheCleanerOnConnectionTerminated cleaner = new SubscriberCacheCleanerOnConnectionTerminated(this);
+    handlers.forEach(cleaner);
+    return cleaner.isChildClearingNeeded;
+  }
+
+  // this method is used only in CompositeMessageBus.notifyConnectionTerminated to clear subscriber cache in children
+  protected void clearSubscriberCache(@NotNull Map<Topic<?>, Object> handlers) {
+    handlers.forEach((topic, o) -> subscriberCache.remove(topic));
+  }
+
+  private static final class SubscriberCacheCleanerOnConnectionTerminated implements BiConsumer<Topic<?>, Object> {
+    private final MessageBusImpl bus;
+    boolean isChildClearingNeeded;
+
+    SubscriberCacheCleanerOnConnectionTerminated(@NotNull MessageBusImpl bus) {
+      this.bus = bus;
     }
 
-    for (Iterator<Message> iterator = jobQueue.queue.iterator(); iterator.hasNext(); ) {
-      Message job = iterator.next();
-      connection.removeMyHandlers(job);
-      if (job.handlers.isEmpty()) {
-        iterator.remove();
+    @Override
+    public void accept(@NotNull Topic<?> topic, Object handlers) {
+      if (bus.subscriberCache.remove(topic) != null) {
+        bus.removeDisposedHandlers(topic, handlers);
+      }
+
+      BroadcastDirection direction = topic.getBroadcastDirection();
+      if (direction != BroadcastDirection.TO_CHILDREN) {
+        return;
+      }
+
+      // clear parents
+      MessageBusImpl parentBus = bus;
+      while ((parentBus = parentBus.myParentBus) != null) {
+        if (parentBus.subscriberCache.remove(topic) != null && handlers != null) {
+          parentBus.removeDisposedHandlers(topic, handlers);
+        }
+      }
+
+      if (bus.hasChildren()) {
+        // clear children
+        isChildClearingNeeded = true;
       }
     }
-    jobRemoved(jobQueue);
   }
 
   final void deliverImmediately(@NotNull MessageBusConnectionImpl connection) {
-    if (myDisposed) {
+    if (disposeState == DISPOSED_STATE) {
       LOG.error("Already disposed: " + this);
     }
     // light project is not disposed in tests properly, so, connection is not removed
-    if (myOwner.isDisposed()) {
+    if (owner.isDisposed()) {
       return;
     }
 
@@ -585,65 +601,98 @@ public class MessageBusImpl implements MessageBus {
     for (Message job : newJobs) {
       // remove here will be not linear as job should be head (first element) in normal conditions
       jobs.removeFirstOccurrence(job);
-      exceptions = deliverMessage(job, jobQueue, myMessageDeliveryListener, exceptions);
+      exceptions = deliverMessage(job, jobQueue, messageDeliveryListener, exceptions);
     }
 
-    CompoundRuntimeException.throwIfNotEmpty(exceptions);
+    if (exceptions != null) {
+      EventDispatcher.throwExceptions(exceptions);
+    }
   }
 
   public final void setMessageDeliveryListener(@Nullable MessageDeliveryListener listener) {
-    if (myMessageDeliveryListener != null && listener != null) {
-      throw new IllegalStateException("Already set: " + myMessageDeliveryListener);
+    if (messageDeliveryListener != null && listener != null) {
+      throw new IllegalStateException("Already set: " + messageDeliveryListener);
     }
-    myMessageDeliveryListener = listener;
+    messageDeliveryListener = listener;
   }
 
-  private static void invokeListener(@NotNull Message message,
-                                     @NotNull Object handler,
-                                     @Nullable MessageDeliveryListener messageDeliveryListener) throws IllegalAccessException, InvocationTargetException {
-    if (handler instanceof MessageHandler) {
-      ((MessageHandler)handler).handle(message.listenerMethod, message.args);
-      return;
+  // args is not null
+  private static @Nullable List<Throwable> invokeListener(@NotNull Method method,
+                                                          Object[] args,
+                                                          @NotNull Object handler,
+                                                          @NotNull Topic<?> topic,
+                                                          @Nullable MessageDeliveryListener messageDeliveryListener,
+                                                          @Nullable List<Throwable> exceptions) {
+    try {
+      if (handler instanceof MessageHandler) {
+        ((MessageHandler)handler).handle(method, args);
+      }
+      else if (messageDeliveryListener == null) {
+        method.invoke(handler, args);
+      }
+      else {
+        long startTime = System.nanoTime();
+        method.invoke(handler, args);
+        messageDeliveryListener.messageDelivered(topic, method.getName(), handler, System.nanoTime() - startTime);
+      }
     }
-
-    Method method = message.listenerMethod;
-    if (messageDeliveryListener == null) {
-      method.invoke(handler, message.args);
-      return;
+    catch (Throwable e) {
+      exceptions = EventDispatcher.handleException(e, exceptions);
     }
-
-    long startTime = System.nanoTime();
-    method.invoke(handler, message.args);
-    messageDeliveryListener.messageDelivered(message.topic, method.getName(), handler, System.nanoTime() - startTime);
+    return exceptions;
   }
 
-  static final class RootBus extends MessageBusImpl {
+  static final class RootBus extends CompositeMessageBus {
+    private final AtomicReference<CompletableFuture<?>> compactionFutureRef = new AtomicReference<>();
+    private final AtomicInteger emptyConnectionCounter = new AtomicInteger();
+
     /**
      * Pending message buses in the hierarchy.
      * The map's keys are sorted by {@link #myOrder}
      * <p>
      * Used to avoid traversing the whole hierarchy when there are no messages to be sent in most of it.
      */
-    private final ThreadLocal<SortedSet<MessageBusImpl>> myWaitingBuses = ThreadLocal.withInitial(() -> {
+    final ThreadLocal<SortedSet<MessageBusImpl>> myWaitingBuses = ThreadLocal.withInitial(() -> {
       return new TreeSet<>((bus1, bus2) -> ArrayUtil.lexicographicCompare(bus1.myOrder, bus2.myOrder));
     });
 
-    // to avoid traversing child buses if already cleared, as
-    // clearSubscriberCache uses O(numberOfModules) time to clear caches
-    private volatile boolean isSubscriberCacheCleared = true;
+    RootBus(@NotNull MessageBusOwner owner) {
+      super(owner);
+    }
 
-    @Override
-    void clearSubscriberCache() {
-      if (isSubscriberCacheCleared) {
+    void scheduleEmptyConnectionRemoving() {
+      int counter = emptyConnectionCounter.incrementAndGet();
+      if (counter < 128 || !emptyConnectionCounter.compareAndSet(counter, 0)) {
         return;
       }
 
-      super.clearSubscriberCache();
-      isSubscriberCacheCleared = true;
+      CompletableFuture<?> oldFuture = compactionFutureRef.get();
+      if (oldFuture == null) {
+        CompletableFuture<?> future = CompletableFuture.runAsync(() -> {
+          removeEmptyConnectionsRecursively();
+          compactionFutureRef.set(null);
+        }, AppExecutorUtil.getAppExecutorService());
+        if (!compactionFutureRef.compareAndSet(null, future)) {
+          future.cancel(false);
+        }
+      }
     }
 
-    RootBus(@NotNull MessageBusOwner owner) {
-      super(owner);
+    @Override
+    public void dispose() {
+      CompletableFuture<?> compactionFuture = compactionFutureRef.getAndSet(null);
+      if (compactionFuture != null) {
+        compactionFuture.cancel(false);
+      }
+      super.dispose();
+    }
+  }
+
+  private void removeDisposedHandlers(@NotNull Topic<?> topic, @NotNull Object handlers) {
+    JobQueue jobQueue = myMessageQueue.get();
+    if (!jobQueue.queue.isEmpty() &&
+        jobQueue.queue.removeIf(job -> job.topic == topic && MessageBusConnectionImpl.removeHandlers(job, handlers) && job.handlers.isEmpty())) {
+      jobRemoved(jobQueue);
     }
   }
 }
@@ -651,46 +700,4 @@ public class MessageBusImpl implements MessageBus {
 final class JobQueue {
   final Deque<Message> queue = new ArrayDeque<>();
   @Nullable Message current;
-}
-
-final class DescriptorBasedMessageBusConnection implements MessageBusImpl.MessageHandlerHolder {
-  final PluginId pluginId;
-  final Topic<?> topic;
-  final List<Object> handlers;
-
-  DescriptorBasedMessageBusConnection(@NotNull PluginId pluginId, @NotNull Topic<?> topic, @NotNull List<Object> handlers) {
-    this.pluginId = pluginId;
-    this.topic = topic;
-    this.handlers = handlers;
-  }
-
-  @Override
-  public void collectHandlers(@NotNull Topic<?> topic, @NotNull List<Object> result) {
-    if (this.topic == topic) {
-      result.addAll(handlers);
-    }
-  }
-
-  @Override
-  public String toString() {
-    return "DescriptorBasedMessageBusConnection(" +
-           "handlers=" + handlers +
-           ')';
-  }
-
-  static @Nullable List<Object> computeNewHandlers(@NotNull List<Object> handlers, @NotNull Set<String> excludeClassNames) {
-    List<Object> newHandlers = null;
-    for (int i = 0, size = handlers.size(); i < size; i++) {
-      Object handler = handlers.get(i);
-      if (excludeClassNames.contains(handler.getClass().getName())) {
-        if (newHandlers == null) {
-          newHandlers = i == 0 ? new ArrayList<>() : new ArrayList<>(handlers.subList(0, i));
-        }
-      }
-      else if (newHandlers != null) {
-        newHandlers.add(handler);
-      }
-    }
-    return newHandlers;
-  }
 }
\ No newline at end of file
index 8e8dc8da02d509ebe7e97be5c59c028d982b6602..0e00f08b6c87bd8b49e3c51aedba4dbce8423295 100644 (file)
@@ -5,6 +5,7 @@ import com.intellij.openapi.Disposable;
 import com.intellij.openapi.util.Disposer;
 import com.intellij.util.messages.MessageBus;
 import com.intellij.util.messages.MessageBusConnection;
+import com.intellij.util.messages.SimpleMessageBusConnection;
 import com.intellij.util.messages.Topic;
 import org.jetbrains.annotations.NotNull;
 
@@ -14,10 +15,10 @@ import java.util.concurrent.ConcurrentHashMap;
 /**
  * @author yole
  */
-public class MessageListenerList<T> {
+public final class MessageListenerList<T> {
   private final MessageBus myMessageBus;
   private final Topic<T> myTopic;
-  private final Map<T, MessageBusConnection> myListenerToConnectionMap = new ConcurrentHashMap<>();
+  private final Map<T, SimpleMessageBusConnection> myListenerToConnectionMap = new ConcurrentHashMap<>();
 
   public MessageListenerList(@NotNull MessageBus messageBus, @NotNull Topic<T> topic) {
     myTopic = topic;
@@ -25,25 +26,25 @@ public class MessageListenerList<T> {
   }
 
   public void add(@NotNull T listener) {
-    final MessageBusConnection connection = myMessageBus.connect();
+    SimpleMessageBusConnection connection = myMessageBus.simpleConnect();
     connection.subscribe(myTopic, listener);
     myListenerToConnectionMap.put(listener, connection);
   }
 
-  public void add(final @NotNull T listener, @NotNull Disposable parentDisposable) {
+  public void add(@NotNull T listener, @NotNull Disposable parentDisposable) {
     Disposer.register(parentDisposable, new Disposable() {
       @Override
       public void dispose() {
         myListenerToConnectionMap.remove(listener);
       }
     });
-    final MessageBusConnection connection = myMessageBus.connect(parentDisposable);
+    MessageBusConnection connection = myMessageBus.connect(parentDisposable);
     connection.subscribe(myTopic, listener);
     myListenerToConnectionMap.put(listener, connection);
   }
 
   public void remove(@NotNull T listener) {
-    final MessageBusConnection connection = myListenerToConnectionMap.remove(listener);
+    SimpleMessageBusConnection connection = myListenerToConnectionMap.remove(listener);
     if (connection != null) {
       connection.disconnect();
     }
diff --git a/platform/core-api/src/com/intellij/util/messages/impl/SimpleMessageBusConnectionImpl.java b/platform/core-api/src/com/intellij/util/messages/impl/SimpleMessageBusConnectionImpl.java
new file mode 100644 (file)
index 0000000..af2cb15
--- /dev/null
@@ -0,0 +1,63 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.util.messages.impl;
+
+import com.intellij.util.SmartFMap;
+import com.intellij.util.messages.SimpleMessageBusConnection;
+import com.intellij.util.messages.Topic;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+final class SimpleMessageBusConnectionImpl implements SimpleMessageBusConnection, MessageBusImpl.MessageHandlerHolder {
+  private MessageBusImpl myBus;
+  private final AtomicReference<SmartFMap<Topic<?>, Object>> topicToHandlers = new AtomicReference<>(SmartFMap.emptyMap());
+
+  SimpleMessageBusConnectionImpl(@NotNull MessageBusImpl bus) {
+    myBus = bus;
+  }
+
+  @Override
+  public <L> void subscribe(@NotNull Topic<L> topic, @NotNull L handler) {
+    MessageBusConnectionImpl.doSubscribe(topic, handler, topicToHandlers);
+    myBus.notifyOnSubscription(topic);
+  }
+
+  @Override
+  public void collectHandlers(@NotNull Topic<?> topic, @NotNull List<Object> result) {
+    Object handlers = topicToHandlers.get().get(topic);
+    if (handlers != null) {
+      if (handlers instanceof Object[]) {
+        Collections.addAll(result, (Object[])handlers);
+      }
+      else {
+        result.add(handlers);
+      }
+    }
+  }
+
+  @Override
+  public void disconnect() {
+    MessageBusImpl bus = myBus;
+    if (bus == null) {
+      return;
+    }
+
+    myBus = null;
+    // reset as bus will not remove disposed connection from list immediately
+    SmartFMap<Topic<?>, Object> oldMap = topicToHandlers.getAndSet(SmartFMap.emptyMap());
+
+    bus.notifyConnectionTerminated(oldMap);
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return topicToHandlers.get().isEmpty();
+  }
+
+  @Override
+  public String toString() {
+    return topicToHandlers.get().toString();
+  }
+}
index 4edd7e1c753542cf9ab32dd2f49d513888b9e458..12def8cf7e180746cb5c239eb547de3aaadbe9ab 100644 (file)
@@ -25,7 +25,7 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class MockComponentManager extends UserDataHolderBase implements ComponentManager, MessageBusOwner {
-  private final MessageBus myMessageBus = new MessageBusFactoryImpl().createMessageBus(this);
+  private final MessageBus myMessageBus = MessageBusFactoryImpl.createRootBus(this);
   private final DefaultPicoContainer myPicoContainer;
   private final ExtensionsAreaImpl myExtensionArea;
 
index f33d153c4e0c664fdd49d9b159bb84e4bb0e030e..ba24d223fddbff3dda648614d497c809bd0d6528 100644 (file)
@@ -14,9 +14,6 @@ import org.jetbrains.annotations.NotNull;
 
 import javax.swing.*;
 
-/**
- * @author yole
- */
 public class MockDumbService extends DumbService {
   private final Project myProject;
 
index 841b7310428e0ec3fe0032c12ff705f1f6956e52..47011fe875d6172df0202072a05b8437b1fbf6d5 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.openapi.command.impl;
 
 import com.intellij.openapi.application.Application;
@@ -20,13 +20,12 @@ import java.util.Stack;
 
 public class CoreCommandProcessor extends CommandProcessorEx {
   private static class CommandDescriptor implements CommandToken {
-    @NotNull
-    public final Runnable myCommand;
+    public final @NotNull Runnable myCommand;
     public final Project myProject;
     public String myName;
     public Object myGroupId;
     public final Document myDocument;
-    @NotNull final UndoConfirmationPolicy myUndoConfirmationPolicy;
+    final @NotNull UndoConfirmationPolicy myUndoConfirmationPolicy;
     final boolean myShouldRecordActionForActiveDocument;
 
     CommandDescriptor(@NotNull Runnable command,
@@ -57,6 +56,7 @@ public class CoreCommandProcessor extends CommandProcessorEx {
   }
 
   protected CommandDescriptor myCurrentCommand;
+  // Stack is used instead of ConcurrentLinkedDeque because null values are not supported by ConcurrentLinkedDeque
   private final Stack<CommandDescriptor> myInterruptedCommands = new Stack<>();
   private final List<CommandListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
   private int myUndoTransparentCount;
@@ -65,7 +65,7 @@ public class CoreCommandProcessor extends CommandProcessorEx {
 
   public CoreCommandProcessor() {
     MessageBus messageBus = ApplicationManager.getApplication().getMessageBus();
-    messageBus.connect().subscribe(CommandListener.TOPIC, new CommandListener() {
+    messageBus.simpleConnect().subscribe(CommandListener.TOPIC, new CommandListener() {
       @Override
       public void commandStarted(@NotNull CommandEvent event) {
         for (CommandListener listener : myListeners) {
@@ -160,7 +160,7 @@ public class CoreCommandProcessor extends CommandProcessorEx {
 
   @Override
   public void executeCommand(Project project,
-                             @NotNull final Runnable command,
+                             final @NotNull Runnable command,
                              final String name,
                              final Object groupId,
                              @NotNull UndoConfirmationPolicy confirmationPolicy) {
@@ -169,7 +169,7 @@ public class CoreCommandProcessor extends CommandProcessorEx {
 
   @Override
   public void executeCommand(Project project,
-                             @NotNull final Runnable command,
+                             final @NotNull Runnable command,
                              final String name,
                              final Object groupId,
                              @NotNull UndoConfirmationPolicy confirmationPolicy,
@@ -228,11 +228,10 @@ public class CoreCommandProcessor extends CommandProcessorEx {
   }
 
   @Override
-  @Nullable
-  public CommandToken startCommand(@Nullable final Project project,
-                                   @Nls final String name,
-                                   @Nullable final Object groupId,
-                                   @NotNull final UndoConfirmationPolicy undoConfirmationPolicy) {
+  public @Nullable CommandToken startCommand(final @Nullable Project project,
+                                             final @Nls String name,
+                                             final @Nullable Object groupId,
+                                             final @NotNull UndoConfirmationPolicy undoConfirmationPolicy) {
     ApplicationManager.getApplication().assertIsWriteThread();
     if (project != null && project.isDisposed()) return null;
 
@@ -246,8 +245,8 @@ public class CoreCommandProcessor extends CommandProcessorEx {
 
     Document document = groupId instanceof Document
                         ? (Document)groupId
-                        : groupId instanceof Ref && ((Ref)groupId).get() instanceof Document
-                           ? (Document)((Ref)groupId).get()
+                        : groupId instanceof Ref && ((Ref<?>)groupId).get() instanceof Document
+                           ? (Document)((Ref<?>)groupId).get()
                            : null;
     myCurrentCommand = new CommandDescriptor(EmptyRunnable.INSTANCE, project, name, groupId, undoConfirmationPolicy, true, document);
     fireCommandStarted();
@@ -255,7 +254,7 @@ public class CoreCommandProcessor extends CommandProcessorEx {
   }
 
   @Override
-  public void finishCommand(@NotNull final CommandToken command, @Nullable Throwable throwable) {
+  public void finishCommand(@NotNull CommandToken command, @Nullable Throwable throwable) {
     ApplicationManager.getApplication().assertIsWriteThread();
     CommandLog.LOG.assertTrue(myCurrentCommand != null, "no current command in progress");
     fireCommandFinished();
@@ -319,15 +318,13 @@ public class CoreCommandProcessor extends CommandProcessorEx {
   }
 
   @Override
-  @Nullable
-  public Runnable getCurrentCommand() {
+  public @Nullable Runnable getCurrentCommand() {
     CommandDescriptor currentCommand = myCurrentCommand;
     return currentCommand != null ? currentCommand.myCommand : null;
   }
 
   @Override
-  @Nullable
-  public String getCurrentCommandName() {
+  public @Nullable String getCurrentCommandName() {
     CommandDescriptor currentCommand = myCurrentCommand;
     if (currentCommand != null) return currentCommand.myName;
     if (!myInterruptedCommands.isEmpty()) {
@@ -338,8 +335,7 @@ public class CoreCommandProcessor extends CommandProcessorEx {
   }
 
   @Override
-  @Nullable
-  public Object getCurrentCommandGroupId() {
+  public @Nullable Object getCurrentCommandGroupId() {
     CommandDescriptor currentCommand = myCurrentCommand;
     if (currentCommand != null) return currentCommand.myGroupId;
     if (!myInterruptedCommands.isEmpty()) {
@@ -350,8 +346,7 @@ public class CoreCommandProcessor extends CommandProcessorEx {
   }
 
   @Override
-  @Nullable
-  public Project getCurrentCommandProject() {
+  public @Nullable Project getCurrentCommandProject() {
     CommandDescriptor currentCommand = myCurrentCommand;
     return currentCommand != null ? currentCommand.myProject : null;
   }
index 47ed0846b9b9d5eca9e3534703f9d19a5d1812d6..258704aacda2c185b6a0653b4df1143ff06be62d 100644 (file)
@@ -24,8 +24,7 @@ import java.util.Map;
 
 import static com.intellij.psi.impl.PsiTreeChangeEventImpl.PsiEventType.*;
 
-public class PsiModificationTrackerImpl implements PsiModificationTracker, PsiTreeChangePreprocessor {
-
+public final class PsiModificationTrackerImpl implements PsiModificationTracker, PsiTreeChangePreprocessor {
   private final SimpleModificationTracker myModificationCount = new SimpleModificationTracker();
 
   private final SimpleModificationTracker myAllLanguagesTracker = new SimpleModificationTracker();
@@ -33,7 +32,7 @@ public class PsiModificationTrackerImpl implements PsiModificationTracker, PsiTr
     ConcurrentFactoryMap.createWeakMap(language -> new SimpleModificationTracker());
   private final Listener myPublisher;
 
-  public PsiModificationTrackerImpl(Project project) {
+  public PsiModificationTrackerImpl(@NotNull Project project) {
     MessageBus bus = project.getMessageBus();
     myPublisher = bus.syncPublisher(TOPIC);
     bus.connect().subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
@@ -81,6 +80,8 @@ public class PsiModificationTrackerImpl implements PsiModificationTracker, PsiTr
     incCountersInner();
   }
 
+  // used by Kotlin
+  @SuppressWarnings("WeakerAccess")
   public static boolean canAffectPsi(@NotNull PsiTreeChangeEventImpl event) {
     PsiTreeChangeEventImpl.PsiEventType code = event.getCode();
     return !(code == BEFORE_PROPERTY_CHANGE ||
@@ -139,18 +140,18 @@ public class PsiModificationTrackerImpl implements PsiModificationTracker, PsiTr
     return myModificationCount.getModificationCount();
   }
 
-  @NotNull
   @Override
-  public ModificationTracker getOutOfCodeBlockModificationTracker() {
+  public @NotNull ModificationTracker getOutOfCodeBlockModificationTracker() {
     return myModificationCount;
   }
 
-  @NotNull
   @Override
-  public ModificationTracker getJavaStructureModificationTracker() {
+  public @NotNull ModificationTracker getJavaStructureModificationTracker() {
     return myModificationCount;
   }
 
+  // used by Kotlin
+  @SuppressWarnings("WeakerAccess")
   @ApiStatus.Experimental
   public void incLanguageModificationCount(@Nullable Language language) {
     if (language == null) return;
@@ -158,16 +159,14 @@ public class PsiModificationTrackerImpl implements PsiModificationTracker, PsiTr
   }
 
   @ApiStatus.Experimental
-  @NotNull
-  public ModificationTracker forLanguage(@NotNull Language language) {
+  public @NotNull ModificationTracker forLanguage(@NotNull Language language) {
     SimpleModificationTracker languageTracker = myLanguageTrackers.get(language);
     return () -> languageTracker.getModificationCount() +
                  myAllLanguagesTracker.getModificationCount();
   }
 
   @ApiStatus.Experimental
-  @NotNull
-  public ModificationTracker forLanguages(@NotNull Condition<? super Language> condition) {
+  public @NotNull ModificationTracker forLanguages(@NotNull Condition<? super Language> condition) {
     return () -> {
       long result = myAllLanguagesTracker.getModificationCount();
       for (Language l : myLanguageTrackers.keySet()) {
index ab7a825956a738212523c8fc0a9c871d1867fc28..0d5af4cc9017f4bc42d93a042090f3143f957eab 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.psi.impl.file.impl;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -11,6 +11,7 @@ import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.fileTypes.FileType;
 import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
 import com.intellij.openapi.roots.FileIndexFacade;
 import com.intellij.openapi.util.Key;
 import com.intellij.openapi.util.LowMemoryWatcher;
@@ -51,16 +52,14 @@ public final class FileManagerImpl implements FileManager {
   /**
    * Holds thread-local temporary providers that are sometimes needed while checking if a file is valid
    */
-  private final ThreadLocal<Map<VirtualFile, FileViewProvider>> myTempProviders = ThreadLocal.withInitial(() -> new HashMap<>());
-
-  private boolean myDisposed;
+  private final ThreadLocal<Map<VirtualFile, FileViewProvider>> myTempProviders = ThreadLocal.withInitial(HashMap::new);
 
   private final MessageBusConnection myConnection;
 
   public FileManagerImpl(@NotNull PsiManagerImpl manager, @NotNull NotNullLazyValue<? extends FileIndexFacade> fileIndex) {
     myManager = manager;
     myFileIndex = fileIndex;
-    myConnection = manager.getProject().getMessageBus().connect();
+    myConnection = manager.getProject().getMessageBus().connect(manager);
 
     LowMemoryWatcher.register(this::processQueue, manager);
 
@@ -150,10 +149,7 @@ public final class FileManagerImpl implements FileManager {
   }
 
   public void dispose() {
-    myConnection.disconnect();
     clearViewProviders();
-
-    myDisposed = true;
   }
 
   private void clearViewProviders() {
@@ -161,7 +157,7 @@ public final class FileManagerImpl implements FileManager {
     DebugUtil.performPsiModification("clearViewProviders", () -> {
       ConcurrentMap<VirtualFile, FileViewProvider> map = myVFileToViewProviderMap.get();
       if (map != null) {
-        for (final FileViewProvider provider : map.values()) {
+        for (FileViewProvider provider : map.values()) {
           markInvalidated(provider);
         }
       }
@@ -262,6 +258,7 @@ public final class FileManagerImpl implements FileManager {
   }
 
   /** @deprecated Left for plugin compatibility */
+  @SuppressWarnings("MethodMayBeStatic")
   @Deprecated
   public boolean isInitialized() {
     return true;
@@ -302,10 +299,10 @@ public final class FileManagerImpl implements FileManager {
   }
 
   void dispatchPendingEvents() {
-    if (myDisposed) {
-      LOG.error("Project is already disposed: "+myManager.getProject());
+    Project project = myManager.getProject();
+    if (project.isDisposed()) {
+      LOG.error("Project is already disposed: " + project);
     }
-
     myConnection.deliverImmediately();
   }
 
@@ -364,9 +361,13 @@ public final class FileManagerImpl implements FileManager {
   @Nullable
   public PsiFile getCachedPsiFile(@NotNull VirtualFile vFile) {
     ApplicationManager.getApplication().assertReadAccessAllowed();
-    if (!vFile.isValid()) throw new InvalidVirtualFileAccessException(vFile);
-    if (myDisposed) {
-      LOG.error("Project is already disposed: " + myManager.getProject());
+    if (!vFile.isValid()) {
+      throw new InvalidVirtualFileAccessException(vFile);
+    }
+
+    Project project = myManager.getProject();
+    if (project.isDisposed()) {
+      LOG.error("Project is already disposed: " + project);
     }
 
     dispatchPendingEvents();
@@ -377,18 +378,20 @@ public final class FileManagerImpl implements FileManager {
   @Override
   @Nullable
   public PsiDirectory findDirectory(@NotNull VirtualFile vFile) {
-    if (myDisposed) {
-      LOG.error("Access to psi files should not be performed after project disposal: "+myManager.getProject());
+    Project project = myManager.getProject();
+    if (project.isDisposed()) {
+      LOG.error("Access to psi files should not be performed after project disposal: " + project);
     }
 
-
     ApplicationManager.getApplication().assertReadAccessAllowed();
     if (!vFile.isValid()) {
       LOG.error("File is not valid:" + vFile);
       return null;
     }
 
-    if (!vFile.isDirectory()) return null;
+    if (!vFile.isDirectory()) {
+      return null;
+    }
     dispatchPendingEvents();
 
     return findDirectoryImpl(vFile, getVFileToPsiDirMap());
@@ -628,12 +631,12 @@ public final class FileManagerImpl implements FileManager {
       FileViewProvider recreated = createFileViewProvider(file, true);
       tempProviders.put(file, recreated);
       return areViewProvidersEquivalent(viewProvider, recreated) &&
-             ((AbstractFileViewProvider)viewProvider).getCachedPsiFiles().stream().noneMatch(f -> hasInvalidOriginal(f));
+             ((AbstractFileViewProvider)viewProvider).getCachedPsiFiles().stream().noneMatch(FileManagerImpl::hasInvalidOriginal);
     }
     finally {
       FileViewProvider temp = tempProviders.remove(file);
       if (temp != null) {
-        DebugUtil.performPsiModification("invalidate temp view provider", () -> ((AbstractFileViewProvider)temp).markInvalidated());
+        DebugUtil.performPsiModification("invalidate temp view provider", ((AbstractFileViewProvider)temp)::markInvalidated);
       }
     }
   }
@@ -642,5 +645,4 @@ public final class FileManagerImpl implements FileManager {
     PsiFile original = file.getOriginalFile();
     return original != file && !original.isValid();
   }
-
 }
index 2aef818f6850fa75c2deb2bdacf74895f28de439..3407b211fa9bb9db39dcd54891e51ac56e56ac54 100644 (file)
@@ -37,8 +37,10 @@ public final class VcsRepositoryManager implements Disposable {
 
   private static final Logger LOG = Logger.getInstance(VcsRepositoryManager.class);
 
-  public static final Topic<VcsRepositoryMappingListener> VCS_REPOSITORY_MAPPING_UPDATED =
-    Topic.create("VCS repository mapping updated", VcsRepositoryMappingListener.class);
+  /**
+   * VCS repository mapping updated. Project level.
+   */
+  public static final Topic<VcsRepositoryMappingListener> VCS_REPOSITORY_MAPPING_UPDATED = new Topic<>(VcsRepositoryMappingListener.class, Topic.BroadcastDirection.NONE);
 
   private final @NotNull Project myProject;
   private final @NotNull ProjectLevelVcsManager myVcsManager;
index 07fdfb489ee90ff9c5a389619c89826322de4eb1..c61f516e34c33e9684b33c24b231c23a2d6af6d5 100644 (file)
@@ -1,5 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
-
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.ide.ui;
 
 import com.intellij.util.messages.Topic;
@@ -18,7 +17,7 @@ import java.util.EventListener;
  */
 @FunctionalInterface
 public interface UISettingsListener extends EventListener {
-  Topic<UISettingsListener> TOPIC = Topic.create("UI settings", UISettingsListener.class);
+  Topic<UISettingsListener> TOPIC = new Topic<>(UISettingsListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
   void uiSettingsChanged(@NotNull UISettings uiSettings);
 }
index 13a9440c1b2d6aa2b36275dfc4bec747dd5cded6..a8bd45bf84d407d66abdcb9aabec0b09a124bb3b 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.openapi.actionSystem.ex;
 
 import com.intellij.openapi.actionSystem.AnAction;
@@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
  * @author Konstantin Bulenkov
  */
 public interface AnActionListener {
-  Topic<AnActionListener> TOPIC = new Topic<>("action changes", AnActionListener.class);
+  Topic<AnActionListener> TOPIC = new Topic<>(AnActionListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN, true);
 
   default void beforeActionPerformed(@NotNull AnAction action, @NotNull DataContext dataContext, @NotNull AnActionEvent event) {
   }
index c2d1ab91163d880c808f95bca763bde6de991b1c..ffc14b4ae6399dcf88fd3de6d2834c8ed73189f0 100644 (file)
@@ -1,23 +1,8 @@
-/*
- * Copyright 2000-2019 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.openapi.editor.colors;
 
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.components.ServiceManager;
 import com.intellij.ui.ColorUtil;
 import com.intellij.util.messages.Topic;
 import org.jetbrains.annotations.NonNls;
@@ -26,14 +11,14 @@ import org.jetbrains.annotations.NotNull;
 import java.awt.*;
 
 public abstract class EditorColorsManager {
-  public static final Topic<EditorColorsListener> TOPIC = Topic.create("EditorColorsListener", EditorColorsListener.class);
+  public static final Topic<EditorColorsListener> TOPIC = new Topic<>(EditorColorsListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
   @NonNls public static final String DEFAULT_SCHEME_NAME = "Default";
 
   @NonNls public static final String COLOR_SCHEME_FILE_EXTENSION = ".icls";
 
   public static EditorColorsManager getInstance() {
-    return ServiceManager.getService(EditorColorsManager.class);
+    return ApplicationManager.getApplication().getService(EditorColorsManager.class);
   }
 
   public abstract void addColorsScheme(@NotNull EditorColorsScheme scheme);
index ed295f10f12a6a06ade29a5275fc30e0a1c6beaa..24ad336f87884c293becaade61db27852a425ff8 100644 (file)
@@ -76,8 +76,7 @@ public interface ComponentManager extends UserDataHolder, Disposable, AreaInstan
   /**
    * @see com.intellij.application.Topics#subscribe
    */
-  @NotNull
-  MessageBus getMessageBus();
+  @NotNull MessageBus getMessageBus();
 
   /**
    * @return true when this component is disposed (e.g. the "File|Close Project" invoked or the application is exited)
index 4bbc763bd6ba2d2b6f7b9933cfd8b189fa0d8d4f..b785ab0b0ececedb97d81dc4c437bbb540a650ec 100644 (file)
@@ -1,7 +1,8 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.util.messages;
 
 import com.intellij.openapi.Disposable;
+import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -20,7 +21,6 @@ import org.jetbrains.annotations.Nullable;
  * <a href="http://www.jetbrains.org/intellij/sdk/docs/basics/plugin_structure/plugin_listeners.html">Plugin Listeners</a>.
  */
 public interface MessageBus extends Disposable {
-
   /**
    * Message buses can be organised into hierarchies. That allows facilities {@link Topic#getBroadcastDirection() broadcasting}.
    * <p/>
@@ -28,26 +28,26 @@ public interface MessageBus extends Disposable {
    *
    * @return parent bus (if defined)
    */
-  @Nullable
-  MessageBus getParent();
+  @Nullable MessageBus getParent();
 
   /**
-   * Allows to create new connection that is not bound to any {@link Disposable}.
-   *
-   * @return newly created connection
+   * Create a new {@link Disposable} connection that is disconnected on message bus dispose, or on explicit dispose.
+   */
+  @NotNull MessageBusConnection connect();
+
+  /**
+   * Create a new connection that is disconnected on message bus dispose, or on explicit {@link SimpleMessageBusConnection#disconnect()}.
    */
-  @NotNull
-  MessageBusConnection connect();
+  @ApiStatus.Experimental
+  @NotNull SimpleMessageBusConnection simpleConnect();
 
   /**
    * Allows to create new connection that is bound to the given {@link Disposable}. That means that returned connection
    * will be automatically {@link MessageBusConnection#dispose() released} if given {@link Disposable disposable parent} is collected.
    *
    * @param parentDisposable target parent disposable to which life cycle newly created connection shall be bound
-   * @return newly created connection which life cycle is bound to the given disposable parent
    */
-  @NotNull
-  MessageBusConnection connect(@NotNull Disposable parentDisposable);
+  @NotNull MessageBusConnection connect(@NotNull Disposable parentDisposable);
 
   /**
    * Allows to retrieve an interface for publishing messages to the target topic.
@@ -105,8 +105,7 @@ public interface MessageBus extends Disposable {
    * @param <L>   {@link Topic#getListenerClass() business interface} of the target topic
    * @return publisher for target topic
    */
-  @NotNull
-  <L> L syncPublisher(@NotNull Topic<L> topic);
+  @NotNull <L> L syncPublisher(@NotNull Topic<L> topic);
 
   /**
    * Disposes current bus, i.e. drops all queued but not delivered messages (if any) and disallows further
index c49e2d372c15f5e929b7788fb29e80a98e80e7c6..ad8f88563e1d44577558a7970615a18abf523e31 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 
 /*
  * @author max
@@ -14,21 +14,7 @@ import org.jetbrains.annotations.Nullable;
  * listen for messages it should grab appropriate connection (or create a new one) and {@link #subscribe(Topic, Object) subscribe}
  * to particular endpoint.
  */
-public interface MessageBusConnection extends Disposable {
-
-  /**
-   * Subscribes given handler to the target endpoint within the current connection.
-   *
-   * @param topic    target endpoint
-   * @param handler  target handler to use for incoming messages
-   * @param <L>      interface for working with the target topic
-   * @throws IllegalStateException    if there is already registered handler for the target endpoint within the current connection.
-   *                                  Note that that previously registered handler is not replaced by the given one then
-   * @see MessageBus#syncPublisher(Topic)
-   * @see com.intellij.application.Topics#subscribe
-   */
-  <L> void subscribe(@NotNull Topic<L> topic, @NotNull L handler) throws IllegalStateException;
-
+public interface MessageBusConnection extends SimpleMessageBusConnection, Disposable {
   /**
    * Subscribes to the target topic within the current connection using {@link #setDefaultHandler(MessageHandler) default handler}.
    *
@@ -53,9 +39,4 @@ public interface MessageBusConnection extends Disposable {
    * @see MessageBus#syncPublisher(Topic)
    */
   void deliverImmediately();
-
-  /**
-   * Disconnects current connections from the {@link MessageBus message bus} and drops all queued but not dispatched messages (if any)
-   */
-  void disconnect();
 }
diff --git a/platform/extensions/src/com/intellij/util/messages/SimpleMessageBusConnection.java b/platform/extensions/src/com/intellij/util/messages/SimpleMessageBusConnection.java
new file mode 100644 (file)
index 0000000..6d48409
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.util.messages;
+
+import org.jetbrains.annotations.NotNull;
+
+public interface SimpleMessageBusConnection {
+  /**
+   * Subscribes given handler to the target endpoint within the current connection.
+   *
+   * @param topic   target endpoint
+   * @param handler target handler to use for incoming messages
+   * @param <L>     interface for working with the target topic
+   * @throws IllegalStateException if there is already registered handler for the target endpoint within the current connection.
+   *                               Note that that previously registered handler is not replaced by the given one then
+   * @see MessageBus#syncPublisher(Topic)
+   * @see com.intellij.application.Topics#subscribe
+   */
+  <L> void subscribe(@NotNull Topic<L> topic, @NotNull L handler) throws IllegalStateException;
+
+  /**
+   * Disconnects current connections from the {@link MessageBus message bus} and drops all queued but not dispatched messages (if any)
+   */
+  void disconnect();
+}
index bf3496c9da76745ca2939251bfe4fcce5abdfc41..c770fb66bcf5af49358714afc93775c52a758fa6 100644 (file)
@@ -1,21 +1,43 @@
 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.util.messages;
 
+import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
 /**
  * Defines messaging endpoint within particular {@link MessageBus bus}.
  *
  * @param <L>  type of the interface that defines contract for working with the particular topic instance
  */
+@ApiStatus.NonExtendable
 public class Topic<L> {
-  private final String myDisplayName;
+  /**
+   * Indicates that messages the of annotated topic are published to a application level message bus.
+   */
+  @Retention(RetentionPolicy.SOURCE)
+  @Target(ElementType.FIELD)
+  public @interface AppLevel {}
+
+  /**
+   * Indicates that messages the of annotated topic are published to a project level message bus.
+   */
+  @Retention(RetentionPolicy.SOURCE)
+  @Target(ElementType.FIELD)
+  public @interface ProjectLevel {}
+
+  private final String name;
   private final Class<L> myListenerClass;
   private final BroadcastDirection myBroadcastDirection;
+  private final boolean immediateDelivery;
 
-  public Topic(@NonNls @NotNull String displayName, @NotNull Class<L> listenerClass) {
-    this(displayName, listenerClass, BroadcastDirection.TO_CHILDREN);
+  public Topic(@NonNls @NotNull String name, @NotNull Class<L> listenerClass) {
+    this(name, listenerClass, BroadcastDirection.TO_CHILDREN);
   }
 
   public Topic(@NotNull Class<L> listenerClass) {
@@ -26,10 +48,19 @@ public class Topic<L> {
     this(listenerClass.getSimpleName(), listenerClass, broadcastDirection);
   }
 
-  public Topic(@NonNls @NotNull String displayName, @NotNull Class<L> listenerClass, @NotNull BroadcastDirection broadcastDirection) {
-    myDisplayName = displayName;
+  @ApiStatus.Experimental
+  public Topic(@NotNull Class<L> listenerClass, @NotNull BroadcastDirection broadcastDirection, boolean immediateDelivery) {
+    name = listenerClass.getSimpleName();
     myListenerClass = listenerClass;
     myBroadcastDirection = broadcastDirection;
+    this.immediateDelivery = immediateDelivery;
+  }
+
+  public Topic(@NonNls @NotNull String name, @NotNull Class<L> listenerClass, @NotNull BroadcastDirection broadcastDirection) {
+    this.name = name;
+    myListenerClass = listenerClass;
+    myBroadcastDirection = broadcastDirection;
+    immediateDelivery = false;
   }
 
   /**
@@ -37,7 +68,7 @@ public class Topic<L> {
    */
   @NonNls
   public @NotNull String getDisplayName() {
-    return myDisplayName;
+    return name;
   }
 
   /**
@@ -59,8 +90,14 @@ public class Topic<L> {
     return myListenerClass;
   }
 
+  @Override
   public String toString() {
-    return myDisplayName;
+    return "Topic(" +
+           "name='" + name + '\'' +
+           ", listenerClass=" + myListenerClass +
+           ", broadcastDirection=" + myBroadcastDirection +
+           ", immediateDelivery=" + immediateDelivery +
+           ')';
   }
 
   public static @NotNull <L> Topic<L> create(@NonNls @NotNull String displayName, @NotNull Class<L> listenerClass) {
@@ -79,6 +116,12 @@ public class Topic<L> {
     return myBroadcastDirection;
   }
 
+  @ApiStatus.Internal
+  @ApiStatus.Experimental
+  public boolean isImmediateDelivery() {
+    return immediateDelivery;
+  }
+
   /**
    * {@link MessageBus Message buses} may be organised into {@link MessageBus#getParent() hierarchies}. That allows to provide
    * additional messaging features like {@code 'broadcasting'}. Here it means that messages sent to particular topic within
@@ -87,7 +130,6 @@ public class Topic<L> {
    * Current enum holds available broadcasting options.
    */
   public enum BroadcastDirection {
-
     /**
      * The message is dispatched to all subscribers of the target topic registered within the child message buses.
      * <p/>
@@ -105,6 +147,12 @@ public class Topic<L> {
     TO_CHILDREN,
 
     /**
+     * Use only for application level publishers. To avoid collection subscribers from modules.
+     */
+    @ApiStatus.Experimental
+    TO_DIRECT_CHILDREN,
+
+    /**
      * No broadcasting is performed for the
      */
     NONE,
index 3ad195ff4aaa8ccc292dc684d4064095797b7381..dc7f173bae0086f7e2217c35a3081164b708631c 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.codeInsight.hints
 
 import com.intellij.configurationStore.deserializeInto
@@ -6,21 +6,35 @@ import com.intellij.configurationStore.serialize
 import com.intellij.lang.Language
 import com.intellij.openapi.application.ApplicationManager
 import com.intellij.openapi.components.PersistentStateComponent
-import com.intellij.openapi.components.ServiceManager
 import com.intellij.openapi.components.State
 import com.intellij.openapi.components.Storage
 import com.intellij.util.messages.Topic
 import org.jdom.Element
 
-@State(name = "InlayHintsSettings", storages = [Storage("workspace.xml")])
+@State(name = "InlayHintsSettings", storages = [Storage("editor.xml"), Storage("workspace.xml", deprecated = true)])
 class InlayHintsSettings : PersistentStateComponent<InlayHintsSettings.State> {
-  private val listener = ApplicationManager.getApplication().messageBus.syncPublisher(INLAY_SETTINGS_CHANGED)
+  companion object {
+    @JvmStatic
+    fun instance(): InlayHintsSettings {
+      return ApplicationManager.getApplication().getService(InlayHintsSettings::class.java)
+    }
+
+    /**
+     * Inlay hints settings changed.
+     */
+    @Topic.AppLevel
+    @JvmStatic
+    val INLAY_SETTINGS_CHANGED = Topic(SettingsListener::class.java, Topic.BroadcastDirection.TO_DIRECT_CHILDREN)
+  }
+
+  private val listener: SettingsListener
+    get() = ApplicationManager.getApplication().messageBus.syncPublisher(INLAY_SETTINGS_CHANGED)
 
   private var myState = State()
   private val lock = Any()
 
   class State {
-    var disabledHintProviderIds: MutableSet<String> = hashSetOf()
+    val disabledHintProviderIds = sortedSetOf<String>()
     // We can't store Map<String, Any> directly, because values deserialized as Object
     var settingsMapElement = Element("settingsMapElement")
 
@@ -28,7 +42,7 @@ class InlayHintsSettings : PersistentStateComponent<InlayHintsSettings.State> {
 
     var isEnabled: Boolean = true
 
-    var disabledLanguages: MutableSet<String> = hashSetOf()
+    val disabledLanguages = sortedSetOf<String>()
   }
 
   private val myCachedSettingsMap: MutableMap<String, Any> = hashMapOf()
@@ -38,7 +52,8 @@ class InlayHintsSettings : PersistentStateComponent<InlayHintsSettings.State> {
       val id = key.getFullId(language)
       if (enable) {
         myState.disabledHintProviderIds.remove(id)
-      } else {
+      }
+      else {
         myState.disabledHintProviderIds.add(id)
       }
     }
@@ -127,7 +142,9 @@ class InlayHintsSettings : PersistentStateComponent<InlayHintsSettings.State> {
   fun hintsEnabled(key: SettingsKey<*>, language: Language) : Boolean = synchronized(lock) {
     var lang: Language? = language
     while (lang != null) {
-      if (key.getFullId(lang) in myState.disabledHintProviderIds) return false
+      if (key.getFullId(lang) in myState.disabledHintProviderIds) {
+        return false
+      }
       lang = lang.baseLanguage
     }
     return true
@@ -169,16 +186,6 @@ class InlayHintsSettings : PersistentStateComponent<InlayHintsSettings.State> {
     return settings
   }
 
-  companion object {
-    @JvmStatic
-    fun instance(): InlayHintsSettings {
-      return ServiceManager.getService(InlayHintsSettings::class.java)
-    }
-
-    @JvmStatic
-    val INLAY_SETTINGS_CHANGED: Topic<SettingsListener> = Topic.create("Inlay hints settings changed", SettingsListener::class.java)
-  }
-
   interface SettingsListener {
     /**
      * @param newEnabled whether inlay hints are globally switched on or off now
index 290ef3705daf78a85a857795c1912200513f84e4..12955134cdc52f61ca371030a11f7fcb9f6c8335 100644 (file)
@@ -1,7 +1,6 @@
 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.refactoring.suggested
 
-import com.intellij.openapi.components.ServiceManager
 import com.intellij.openapi.project.Project
 
 interface SuggestedRefactoringProvider {
@@ -12,7 +11,6 @@ interface SuggestedRefactoringProvider {
 
   companion object {
     @JvmStatic
-    fun getInstance(project: Project): SuggestedRefactoringProvider =
-      ServiceManager.getService(project, SuggestedRefactoringProvider::class.java)
+    fun getInstance(project: Project): SuggestedRefactoringProvider = project.getService(SuggestedRefactoringProvider::class.java)
   }
 }
\ No newline at end of file
index 3c4690ad8cae52e2776386fd2ac8a34ef2c90539..7ed3481274ec42eabd212abf7482abd1fdc22352 100644 (file)
@@ -57,7 +57,7 @@ import com.intellij.util.concurrency.Semaphore;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.indexing.DumbModeAccessType;
 import com.intellij.util.indexing.FileBasedIndex;
-import com.intellij.util.messages.MessageBusConnection;
+import com.intellij.util.messages.SimpleMessageBusConnection;
 import com.intellij.util.ui.update.MergingUpdateQueue;
 import com.intellij.util.ui.update.Update;
 import org.jetbrains.annotations.*;
@@ -803,13 +803,17 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement
   }
 
   private LightweightHint showErrorHint(Project project, Editor editor, String text) {
-    final LightweightHint[] result = {null};
-    final EditorHintListener listener = (project1, hint, flags) -> result[0] = hint;
-    final MessageBusConnection connection = project.getMessageBus().connect();
-    connection.subscribe(EditorHintListener.TOPIC, listener);
-    assert text != null;
-    myEmptyCompletionNotifier.showIncompleteHint(editor, text, DumbService.isDumb(project));
-    connection.disconnect();
+    LightweightHint[] result = {null};
+    EditorHintListener listener = (project1, hint, flags) -> result[0] = hint;
+    SimpleMessageBusConnection connection = project.getMessageBus().simpleConnect();
+    try {
+      connection.subscribe(EditorHintListener.TOPIC, listener);
+      assert text != null;
+      myEmptyCompletionNotifier.showIncompleteHint(editor, text, DumbService.isDumb(project));
+    }
+    finally {
+      connection.disconnect();
+    }
     return result[0];
   }
 
index 1f88d498ca4f99594af5a88c7aab266b4db98d9e..0f9850ce4d2ffd24805de789be29688cb31e2a4b 100644 (file)
@@ -60,7 +60,6 @@ import com.intellij.openapi.project.ProjectUtil;
 import com.intellij.openapi.roots.ModuleRootEvent;
 import com.intellij.openapi.roots.ModuleRootListener;
 import com.intellij.openapi.util.Comparing;
-import com.intellij.openapi.util.Disposer;
 import com.intellij.openapi.util.Key;
 import com.intellij.openapi.util.UserDataHolderEx;
 import com.intellij.openapi.util.registry.Registry;
@@ -134,58 +133,64 @@ public final class DaemonListeners implements Disposable {
     eventMulticaster.addDocumentListener(new DocumentListener() {
       // clearing highlighters before changing document because change can damage editor highlighters drastically, so we'll clear more than necessary
       @Override
-      public void beforeDocumentChange(@NotNull final DocumentEvent e) {
+      public void beforeDocumentChange(@NotNull DocumentEvent e) {
         Document document = e.getDocument();
         VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
         Project project = virtualFile == null ? null : ProjectUtil.guessProjectForFile(virtualFile);
         //no need to stop daemon if something happened in the console or in non-physical document
-        if (worthBothering(document, project) && ApplicationManager.getApplication().isDispatchThread()) {
+        if (!myProject.isDisposed() && ApplicationManager.getApplication().isDispatchThread() && worthBothering(document, project)) {
           stopDaemon(true, "Document change");
           UpdateHighlightersUtil.updateHighlightersByTyping(myProject, e);
         }
       }
     }, this);
 
-    eventMulticaster.addCaretListener(new CaretListener() {
-      @Override
-      public void caretPositionChanged(@NotNull CaretEvent e) {
-        final Editor editor = e.getEditor();
-        if (EditorActivityManager.getInstance().isVisible(editor) &&
-            worthBothering(editor.getDocument(), editor.getProject())) {
-
-          if (!ApplicationManager.getApplication().isUnitTestMode()) {
+    if (!ApplicationManager.getApplication().isUnitTestMode()) {
+      eventMulticaster.addCaretListener(new CaretListener() {
+        @Override
+        public void caretPositionChanged(@NotNull CaretEvent e) {
+          Editor editor = e.getEditor();
+          if (EditorActivityManager.getInstance().isVisible(editor) &&
+              worthBothering(editor.getDocument(), editor.getProject())) {
             ApplicationManager.getApplication().invokeLater(() -> {
-              if (EditorActivityManager.getInstance().isVisible(editor) && !myProject.isDisposed()) {
+              if (!myProject.isDisposed() && EditorActivityManager.getInstance().isVisible(editor)) {
                 IntentionsUI.getInstance(myProject).invalidate();
               }
-            }, ModalityState.current());
+            }, ModalityState.current(), myProject.getDisposed());
           }
         }
-      }
-    }, this);
+      }, this);
+    }
 
-    connection.subscribe(EditorTrackerListener.TOPIC, activeEditors -> {
-      if (myActiveEditors.equals(activeEditors)) {
-        return;
-      }
+    connection.subscribe(EditorTrackerListener.TOPIC, new EditorTrackerListener() {
+      @Override
+      public void activeEditorsChanged(@NotNull List<Editor> activeEditors) {
+        if (myActiveEditors.equals(activeEditors)) {
+          return;
+        }
 
-      myActiveEditors = activeEditors;
-      // do not stop daemon if idea loses/gains focus
-      stopDaemon(true, "Active editor change");
-      if (ApplicationManager.getApplication().isDispatchThread() && LaterInvocator.isInModalContext()) {
-        // editor appear in modal context, re-enable the daemon
-        myDaemonCodeAnalyzer.setUpdateByTimerEnabled(true);
-      }
+        myActiveEditors = activeEditors;
+        // do not stop daemon if idea loses/gains focus
+        DaemonListeners.this.stopDaemon(true, "Active editor change");
+        if (ApplicationManager.getApplication().isDispatchThread() && LaterInvocator.isInModalContext()) {
+          // editor appear in modal context, re-enable the daemon
+          myDaemonCodeAnalyzer.setUpdateByTimerEnabled(true);
+        }
 
-      ErrorStripeUpdateManager errorStripeUpdateManager = ErrorStripeUpdateManager.getInstance(myProject);
-      for (Editor editor : activeEditors) {
-        errorStripeUpdateManager.repaintErrorStripePanel(editor);
+        ErrorStripeUpdateManager errorStripeUpdateManager = ErrorStripeUpdateManager.getInstance(myProject);
+        for (Editor editor : activeEditors) {
+          errorStripeUpdateManager.repaintErrorStripePanel(editor);
+        }
       }
     });
 
     editorFactory.addEditorFactoryListener(new EditorFactoryListener() {
       @Override
       public void editorCreated(@NotNull EditorFactoryEvent event) {
+        if (myProject.isDisposed()) {
+          return;
+        }
+
         Editor editor = event.getEditor();
         Document document = editor.getDocument();
         Project editorProject = editor.getProject();
@@ -214,9 +219,7 @@ public final class DaemonListeners implements Disposable {
       }
     }, this);
 
-    PsiChangeHandler changeHandler = new PsiChangeHandler(myProject, connection);
-    Disposer.register(this, changeHandler);
-    PsiManager.getInstance(myProject).addPsiTreeChangeListener(changeHandler, changeHandler);
+    PsiManager.getInstance(myProject).addPsiTreeChangeListener(new PsiChangeHandler(myProject, connection, this), this);
 
     connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() {
       @Override
@@ -373,9 +376,13 @@ public final class DaemonListeners implements Disposable {
     name.addChangeListener(() -> stopDaemonAndRestartAllFiles(message), this);
   }
 
-  private boolean worthBothering(final Document document, Project project) {
-    if (document == null) return true;
-    if (project != null && project != myProject) return false;
+  private boolean worthBothering(@Nullable Document document, Project project) {
+    if (document == null) {
+      return true;
+    }
+    if (project != null && project != myProject) {
+      return false;
+    }
     // cached is essential here since we do not want to create PSI file in alien project
     PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getCachedPsiFile(document);
     return psiFile != null && psiFile.isPhysical() && psiFile.getOriginalFile() == psiFile;
@@ -436,14 +443,17 @@ public final class DaemonListeners implements Disposable {
     }
   }
 
-  private static class Holder {
+  private static final class Holder {
     private static final String myCutActionName = ActionManager.getInstance().getAction(IdeActions.ACTION_EDITOR_CUT).getTemplatePresentation().getText();
   }
-  private class MyCommandListener implements CommandListener {
+
+  private final class MyCommandListener implements CommandListener {
     @Override
     public void commandStarted(@NotNull CommandEvent event) {
       Document affectedDocument = extractDocumentFromCommand(event);
-      if (!worthBothering(affectedDocument, event.getProject())) return;
+      if (!worthBothering(affectedDocument, event.getProject())) {
+        return;
+      }
 
       cutOperationJustHappened = Comparing.strEqual(Holder.myCutActionName, event.getCommandName());
       if (!myDaemonCodeAnalyzer.isRunning()) return;
@@ -453,8 +463,7 @@ public final class DaemonListeners implements Disposable {
       stopDaemon(false, "Command start");
     }
 
-    @Nullable
-    private Document extractDocumentFromCommand(@NotNull CommandEvent event) {
+    private @Nullable Document extractDocumentFromCommand(@NotNull CommandEvent event) {
       Document affectedDocument = event.getDocument();
       if (affectedDocument != null) return affectedDocument;
       Object id = event.getCommandGroupId();
@@ -471,7 +480,9 @@ public final class DaemonListeners implements Disposable {
     @Override
     public void commandFinished(@NotNull CommandEvent event) {
       Document affectedDocument = extractDocumentFromCommand(event);
-      if (!worthBothering(affectedDocument, event.getProject())) return;
+      if (!worthBothering(affectedDocument, event.getProject())) {
+        return;
+      }
 
       if (myEscPressed) {
         myEscPressed = false;
@@ -488,7 +499,7 @@ public final class DaemonListeners implements Disposable {
     }
   }
 
-  private class MyTodoListener implements PropertyChangeListener {
+  private final class MyTodoListener implements PropertyChangeListener {
     @Override
     public void propertyChange(@NotNull PropertyChangeEvent evt) {
       if (TodoConfiguration.PROP_TODO_PATTERNS.equals(evt.getPropertyName())) {
index c551507fd25f0659b8e8023195d759f9b9aabbb2..dc7cdeb91623a9fdf4715a8067ee0b3f2d8e721d 100644 (file)
@@ -9,6 +9,7 @@ import java.util.EventListener;
 import java.util.List;
 
 public interface EditorTrackerListener extends EventListener{
+  @Topic.ProjectLevel
   Topic<EditorTrackerListener> TOPIC = new Topic<>(EditorTrackerListener.class, Topic.BroadcastDirection.NONE);
 
   void activeEditorsChanged(@NotNull List<Editor> activeEditors);
index 7bc9a5fd7c23fc0f0b0122500070e628cce87377..1af30723dd9d9662573ad1715442d6369e70b542 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.codeInsight.daemon.impl;
 
 import com.intellij.codeInsight.daemon.ChangeLocalityDetector;
@@ -9,6 +9,7 @@ import com.intellij.openapi.application.ModalityState;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.editor.ProjectDisposeAwareDocumentListener;
 import com.intellij.openapi.editor.event.DocumentEvent;
 import com.intellij.openapi.editor.event.DocumentListener;
 import com.intellij.openapi.editor.ex.EditorMarkupModel;
@@ -36,26 +37,30 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-final class PsiChangeHandler extends PsiTreeChangeAdapter implements Disposable {
-  private static final ExtensionPointName<ChangeLocalityDetector> EP_NAME = ExtensionPointName.create("com.intellij.daemon.changeLocalityDetector");
+final class PsiChangeHandler extends PsiTreeChangeAdapter {
+  private static final ExtensionPointName<ChangeLocalityDetector> EP_NAME = new ExtensionPointName<>("com.intellij.daemon.changeLocalityDetector");
   private /*NOT STATIC!!!*/ final Key<Boolean> UPDATE_ON_COMMIT_ENGAGED = Key.create("UPDATE_ON_COMMIT_ENGAGED");
 
   private final Project myProject;
   private final Map<Document, List<Pair<PsiElement, Boolean>>> changedElements = ContainerUtil.createWeakMap();
   private final FileStatusMap myFileStatusMap;
 
-  PsiChangeHandler(@NotNull Project project, @NotNull MessageBusConnection connection) {
+  PsiChangeHandler(@NotNull Project project, @NotNull MessageBusConnection connection, @NotNull Disposable parentDisposable) {
     myProject = project;
     myFileStatusMap = DaemonCodeAnalyzerEx.getInstanceEx(myProject).getFileStatusMap();
-    EditorFactory.getInstance().getEventMulticaster().addDocumentListener(new DocumentListener() {
+    EditorFactory.getInstance().getEventMulticaster().addDocumentListener(ProjectDisposeAwareDocumentListener.create(project, new DocumentListener() {
       @Override
-      public void beforeDocumentChange(@NotNull DocumentEvent e) {
-        final Document document = e.getDocument();
+      public void beforeDocumentChange(@NotNull DocumentEvent event) {
+        Document document = event.getDocument();
         PsiDocumentManagerImpl documentManager = (PsiDocumentManagerImpl)PsiDocumentManager.getInstance(myProject);
-        if (documentManager.getSynchronizer().isInSynchronization(document)) return;
+        if (documentManager.getSynchronizer().isInSynchronization(document)) {
+          return;
+        }
 
         PsiFile psi = documentManager.getCachedPsiFile(document);
-        if (psi == null || !psi.getViewProvider().isEventSystemEnabled()) return;
+        if (psi == null || !psi.getViewProvider().isEventSystemEnabled()) {
+          return;
+        }
 
         if (document.getUserData(UPDATE_ON_COMMIT_ENGAGED) == null) {
           document.putUserData(UPDATE_ON_COMMIT_ENGAGED, Boolean.TRUE);
@@ -67,26 +72,22 @@ final class PsiChangeHandler extends PsiTreeChangeAdapter implements Disposable
           });
         }
       }
-    }, this);
+    }), parentDisposable);
 
     connection.subscribe(PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() {
       @Override
-      public void transactionStarted(@NotNull final Document doc, @NotNull final PsiFile file) {
+      public void transactionStarted(final @NotNull Document doc, final @NotNull PsiFile file) {
       }
 
       @Override
-      public void transactionCompleted(@NotNull final Document document, @NotNull final PsiFile file) {
+      public void transactionCompleted(final @NotNull Document document, final @NotNull PsiFile file) {
         updateChangesForDocument(document);
         document.putUserData(UPDATE_ON_COMMIT_ENGAGED, null); // ensure we don't call updateChangesForDocument() twice which can lead to whole file re-highlight
       }
     });
   }
 
-  @Override
-  public void dispose() {
-  }
-
-  private void updateChangesForDocument(@NotNull final Document document) {
+  private void updateChangesForDocument(final @NotNull Document document) {
     ApplicationManager.getApplication().assertIsWriteThread();
     if (myProject.isDisposed()) return;
     List<Pair<PsiElement, Boolean>> toUpdate = changedElements.get(document);
@@ -206,7 +207,7 @@ final class PsiChangeHandler extends PsiTreeChangeAdapter implements Disposable
     }
   }
 
-  private void updateByChange(@NotNull PsiElement child, @NotNull final Document document, final boolean whitespaceOptimizationAllowed) {
+  private void updateByChange(@NotNull PsiElement child, final @NotNull Document document, final boolean whitespaceOptimizationAllowed) {
     ApplicationManager.getApplication().assertIsWriteThread();
     final PsiFile file;
     try {
@@ -254,8 +255,7 @@ final class PsiChangeHandler extends PsiTreeChangeAdapter implements Disposable
            ProjectRootManager.getInstance(myProject).getFileIndex().isExcluded(virtualFile);
   }
 
-  @Nullable
-  private static PsiElement getChangeHighlightingScope(@NotNull PsiElement element) {
+  private static @Nullable PsiElement getChangeHighlightingScope(@NotNull PsiElement element) {
     DefaultChangeLocalityDetector defaultDetector = null;
     for (ChangeLocalityDetector detector : EP_NAME.getExtensionList()) {
       if (detector instanceof DefaultChangeLocalityDetector) {
index 26d16acf11bce483f4094fcd146cfdebc17006d2..29aacc2598656755a6ee3c8dd8f3182ef925aec5 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.codeInsight.editorActions;
 
 import com.intellij.codeInsight.CodeInsightSettings;
@@ -58,7 +58,7 @@ public class PasteHandler extends EditorActionHandler implements EditorTextInser
   }
 
   @Override
-  public void execute(final Editor editor, final DataContext dataContext, @Nullable final Producer<Transferable> producer) {
+  public void execute(Editor editor, DataContext dataContext, @Nullable Producer<Transferable> producer) {
     final Transferable transferable = EditorModificationUtil.getContentsToPasteToEditor(producer);
     if (transferable == null) return;
 
@@ -69,8 +69,9 @@ public class PasteHandler extends EditorActionHandler implements EditorTextInser
       return;
     }
 
-    DataContext context =
-      dataId -> PasteAction.TRANSFERABLE_PROVIDER.is(dataId) ? (Producer<Transferable>)() -> transferable : dataContext.getData(dataId);
+    DataContext context = dataId -> {
+      return PasteAction.TRANSFERABLE_PROVIDER.is(dataId) ? (Producer<Transferable>)() -> transferable : dataContext.getData(dataId);
+    };
 
     final Project project = editor.getProject();
     if (project == null || editor.isColumnMode() || editor.getCaretModel().getCaretCount() > 1) {
@@ -175,12 +176,12 @@ public class PasteHandler extends EditorActionHandler implements EditorTextInser
     }
 
     final String _text = text;
-    ApplicationManager.getApplication().runWriteAction(
-      () -> {
-        EditorModificationUtil.insertStringAtCaret(editor, _text, false, true);
+    ApplicationManager.getApplication().runWriteAction(() -> {
+      EditorModificationUtil.insertStringAtCaret(editor, _text, false, true);
+      if (!project.isDisposed()) {
         ((UndoManagerImpl)UndoManager.getInstance(project)).addDocumentAsAffected(editor.getDocument());
       }
-    );
+    });
 
     int length = text.length();
     int offset = caretModel.getOffset() - length;
index 985d9fd0246e5cf5d8e1ca95f73b4bba8850ec8d..eef80688879c5f28ccedc594e8a25ae9d4a53974 100644 (file)
@@ -1,5 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
-
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.facet.impl;
 
 import com.intellij.ProjectTopics;
@@ -18,25 +17,22 @@ import org.jetbrains.annotations.NotNull;
 import java.util.HashMap;
 import java.util.Map;
 
-public final class ProjectWideFacetListenersRegistryImpl extends ProjectWideFacetListenersRegistry {
+final class ProjectWideFacetListenersRegistryImpl extends ProjectWideFacetListenersRegistry {
   private final Map<FacetTypeId<?>, EventDispatcher<ProjectWideFacetListener>> myDispatchers = new HashMap<>();
   private final Map<FacetTypeId<?>, Map<Facet<?>, Boolean>> myFacetsByType = new HashMap<>();
-  private final Map<Module, MessageBusConnection> myModule2Connection = new HashMap<>();
-  private final FacetManagerAdapter myFacetListener;
   private final EventDispatcher<ProjectWideFacetListener> myAllFacetsListener = EventDispatcher.create(ProjectWideFacetListener.class);
 
-  public ProjectWideFacetListenersRegistryImpl(@NotNull Project project) {
-    myFacetListener = new MyFacetManagerAdapter();
-    project.getMessageBus().connect().subscribe(ProjectTopics.MODULES, new ModuleListener() {
+  ProjectWideFacetListenersRegistryImpl(@NotNull Project project) {
+    MessageBusConnection connection = project.getMessageBus().connect();
+    connection.subscribe(ProjectTopics.MODULES, new ModuleListener() {
       @Override
       public void moduleAdded(@NotNull Project project, @NotNull Module module) {
         onModuleAdded(module);
       }
 
       @Override
-      public void beforeModuleRemoved(@NotNull final Project project, @NotNull final Module module) {
-        Facet<?>[] allFacets = FacetManager.getInstance(module).getAllFacets();
-        for (Facet<?> facet : allFacets) {
+      public void beforeModuleRemoved(@NotNull Project project, @NotNull Module module) {
+        for (Facet<?> facet : FacetManager.getInstance(module).getAllFacets()) {
           onFacetRemoved(facet, true);
         }
       }
@@ -47,33 +43,43 @@ public final class ProjectWideFacetListenersRegistryImpl extends ProjectWideFace
       }
     });
 
+    connection.subscribe(FacetManager.FACETS_TOPIC, new FacetManagerAdapter() {
+      @Override
+      public void facetAdded(@NotNull Facet facet) {
+        onFacetAdded(facet);
+      }
+
+      @Override
+      public void beforeFacetRemoved(@NotNull Facet facet) {
+        onFacetRemoved(facet, true);
+      }
+
+      @Override
+      public void facetRemoved(@NotNull Facet facet) {
+        onFacetRemoved(facet, false);
+      }
+
+      @Override
+      public void facetConfigurationChanged(@NotNull Facet facet) {
+        onFacetChanged(facet);
+      }
+    });
+
     for (Module module : ModuleManager.getInstance(project).getModules()) {
       onModuleAdded(module);
     }
   }
 
-  private void onModuleRemoved(final Module module) {
-    final MessageBusConnection connection = myModule2Connection.remove(module);
-    if (connection != null) {
-      connection.disconnect();
-    }
-
-    final FacetManager facetManager = FacetManager.getInstance(module);
-    final Facet<?>[] facets = facetManager.getAllFacets();
-    for (Facet<?> facet : facets) {
+  private void onModuleRemoved(@NotNull Module module) {
+    for (Facet<?> facet : FacetManager.getInstance(module).getAllFacets()) {
       onFacetRemoved(facet, false);
     }
   }
 
-  private void onModuleAdded(final Module module) {
-    final FacetManager facetManager = FacetManager.getInstance(module);
-    final Facet<?>[] facets = facetManager.getAllFacets();
-    for (Facet<?> facet : facets) {
+  private void onModuleAdded(@NotNull Module module) {
+    for (Facet<?> facet : FacetManager.getInstance(module).getAllFacets()) {
       onFacetAdded(facet);
     }
-    final MessageBusConnection connection = module.getMessageBus().connect();
-    myModule2Connection.put(module, connection);
-    connection.subscribe(FacetManager.FACETS_TOPIC, myFacetListener);
   }
 
   private void onFacetRemoved(@NotNull Facet<?> facet, final boolean before) {
@@ -90,7 +96,7 @@ public final class ProjectWideFacetListenersRegistryImpl extends ProjectWideFace
     else {
       lastFacet = true;
     }
-    final EventDispatcher<ProjectWideFacetListener> dispatcher = myDispatchers.get(typeId);
+    EventDispatcher<ProjectWideFacetListener> dispatcher = myDispatchers.get(typeId);
     if (dispatcher != null) {
       if (before) {
         //noinspection unchecked
@@ -200,28 +206,4 @@ public final class ProjectWideFacetListenersRegistryImpl extends ProjectWideFace
   public void registerListener(@NotNull final ProjectWideFacetListener<Facet> listener, @NotNull final Disposable parentDisposable) {
     myAllFacetsListener.addListener(listener, parentDisposable);
   }
-
-  private class MyFacetManagerAdapter extends FacetManagerAdapter {
-
-    @Override
-    public void facetAdded(@NotNull Facet facet) {
-      onFacetAdded(facet);
-    }
-
-    @Override
-    public void beforeFacetRemoved(@NotNull final Facet facet) {
-      onFacetRemoved(facet, true);
-    }
-
-    @Override
-    public void facetRemoved(@NotNull Facet facet) {
-      onFacetRemoved(facet, false);
-    }
-
-    @Override
-    public void facetConfigurationChanged(@NotNull final Facet facet) {
-      onFacetChanged(facet);
-    }
-
-  }
 }
index edc33396f39fdd8b501702521e53b0cbaad06c4a..94af0b5d59799a14801605fb963d69447c2f52cc 100644 (file)
@@ -1,7 +1,6 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.ide.script;
 
-import com.intellij.ide.ApplicationInitializedListener;
 import com.intellij.ide.extensionResources.ExtensionsRootType;
 import com.intellij.ide.plugins.PluginManagerCore;
 import com.intellij.openapi.application.ApplicationManager;
@@ -9,31 +8,34 @@ import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.extensions.ExtensionNotApplicableException;
 import com.intellij.openapi.progress.ProcessCanceledException;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectManager;
 import com.intellij.openapi.project.ProjectManagerListener;
 import com.intellij.openapi.startup.StartupManager;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.util.io.FileUtilRt;
 import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.concurrency.NonUrgentExecutor;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.containers.JBIterable;
-import com.intellij.util.messages.MessageBusConnection;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
 import java.io.IOException;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.Future;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
 
-final class IdeStartupScripts implements ApplicationInitializedListener {
+final class IdeStartupScripts implements ProjectManagerListener {
   private static final Logger LOG = Logger.getInstance(IdeStartupScripts.class);
 
   private static final String SCRIPT_DIR = "startup";
 
+  private final AtomicBoolean isActive = new AtomicBoolean(true);
+
   IdeStartupScripts() {
     if (ApplicationManager.getApplication().isUnitTestMode()) {
       throw ExtensionNotApplicableException.INSTANCE;
@@ -41,26 +43,22 @@ final class IdeStartupScripts implements ApplicationInitializedListener {
   }
 
   @Override
-  public void componentsInitialized() {
-    MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect();
-    connection.subscribe(ProjectManager.TOPIC, new ProjectManagerListener() {
-      Future<List<Pair<File, IdeScriptEngine>>> future;
-      @Override
-      public void projectOpened(@NotNull Project project) {
-        if (future == null) {
-          future = ApplicationManager.getApplication().executeOnPooledThread(() -> prepareScriptsAndEngines());
-        }
+  public void projectOpened(@NotNull Project project) {
+    if (!isActive.compareAndSet(true, false)) {
+      return;
+    }
 
-        StartupManager.getInstance(project).runAfterOpened(() -> {
-          connection.disconnect();
-          ApplicationManager.getApplication().executeOnPooledThread(() -> runAllScriptsImpl(project, future));
-        });
+    CompletableFuture<List<Pair<File, IdeScriptEngine>>> future = CompletableFuture.supplyAsync(IdeStartupScripts::prepareScriptsAndEngines, NonUrgentExecutor.getInstance());
+    WeakReference<Project> projectRef = new WeakReference<>(project);
+    StartupManager.getInstance(project).runAfterOpened(() -> future.thenAcceptAsync(it -> {
+      Project project1 = projectRef.get();
+      if (project1 != null) {
+        runAllScriptsImpl(project1, it);
       }
-    });
+    }, NonUrgentExecutor.getInstance()));
   }
 
-  @NotNull
-  private static List<Pair<File, IdeScriptEngine>> prepareScriptsAndEngines() {
+  private static @NotNull List<Pair<File, IdeScriptEngine>> prepareScriptsAndEngines() {
     List<File> scripts = getScripts();
     LOG.info(scripts.size() + " startup script(s) found");
     if (scripts.isEmpty()) return Collections.emptyList();
@@ -91,8 +89,7 @@ final class IdeStartupScripts implements ApplicationInitializedListener {
     }
   }
 
-  @NotNull
-  private static List<File> getScripts() {
+  private static @NotNull List<File> getScripts() {
     File directory = getScriptsRootDirectory();
     List<File> scripts = JBIterable.of(directory == null ? null : directory.listFiles())
       .filter(ExtensionsRootType.regularFileFilter())
@@ -106,8 +103,7 @@ final class IdeStartupScripts implements ApplicationInitializedListener {
     return scripts;
   }
 
-  @Nullable
-  private static File getScriptsRootDirectory() {
+  private static @Nullable File getScriptsRootDirectory() {
     try {
       return ExtensionsRootType.getInstance().findResourceDirectory(PluginManagerCore.CORE_ID, SCRIPT_DIR, false);
     }
@@ -116,9 +112,9 @@ final class IdeStartupScripts implements ApplicationInitializedListener {
     return null;
   }
 
-  private static void runAllScriptsImpl(@NotNull Project project, @NotNull Future<? extends List<Pair<File, IdeScriptEngine>>> future) {
+  private static void runAllScriptsImpl(@NotNull Project project, @NotNull List<Pair<File, IdeScriptEngine>> result) {
     try {
-      for (Pair<File, IdeScriptEngine> pair : future.get()) {
+      for (Pair<File, IdeScriptEngine> pair : result) {
         try {
           if (pair.second == null) {
             LOG.warn(pair.first.getPath() + " not supported (no script engine)");
@@ -135,9 +131,6 @@ final class IdeStartupScripts implements ApplicationInitializedListener {
     catch (ProcessCanceledException e) {
       LOG.warn("... cancelled");
     }
-    catch (InterruptedException e) {
-      LOG.warn("... interrupted");
-    }
     catch (Exception e) {
       LOG.error(e);
     }
index 402d09c15e8c321340795e07572aa7572774ae6c..90975e97563c042abced19d7dbc9b65b1fed9cb5 100644 (file)
@@ -87,7 +87,7 @@ public class ModuleImpl extends ComponentManagerImpl implements ModuleEx {
     // do not measure (activityNamePrefix method not overridden by this class)
     // because there are a lot of modules and no need to measure each one
     //noinspection unchecked
-    registerComponents((List<IdeaPluginDescriptorImpl>)PluginManagerCore.getLoadedPlugins(), false);
+    registerComponents((List<IdeaPluginDescriptorImpl>)PluginManagerCore.getLoadedPlugins());
     if (!isPersistent()) {
       registerService(IComponentStore.class,
                       NonPersistentModuleStore.class,
index d205086b1214224bd84136075dddfcc773b54e16..06e11288e04c8d00e128949aaebf65d263a11758 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.openapi.projectRoots.impl;
 
 import com.intellij.ide.highlighter.ArchiveFileType;
@@ -24,7 +24,6 @@ import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
 import com.intellij.util.ThreeState;
 import com.intellij.util.containers.SmartHashSet;
-import com.intellij.util.messages.MessageBus;
 import com.intellij.util.messages.MessageBusConnection;
 import org.jdom.Element;
 import org.jetbrains.annotations.NonNls;
@@ -46,11 +45,9 @@ public class ProjectJdkTableImpl extends ProjectJdkTable implements ExportableCo
   private static final String ELEMENT_JDK = "jdk";
 
   private final Map<String, ProjectJdkImpl> myCachedProjectJdks = new HashMap<>();
-  private final MessageBus myMessageBus;
 
   // constructor is public because it is accessed from Upsource
   public ProjectJdkTableImpl() {
-    myMessageBus = ApplicationManager.getApplication().getMessageBus();
     // support external changes to jdk libraries (Endorsed Standards Override)
     final MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect();
     connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
@@ -252,13 +249,13 @@ public class ProjectJdkTableImpl extends ProjectJdkTable implements ExportableCo
   public void addJdk(@NotNull Sdk jdk) {
     ApplicationManager.getApplication().assertWriteAccessAllowed();
     mySdks.add(jdk);
-    myMessageBus.syncPublisher(JDK_TABLE_TOPIC).jdkAdded(jdk);
+    ApplicationManager.getApplication().getMessageBus().syncPublisher(JDK_TABLE_TOPIC).jdkAdded(jdk);
   }
 
   @Override
   public void removeJdk(@NotNull Sdk jdk) {
     ApplicationManager.getApplication().assertWriteAccessAllowed();
-    myMessageBus.syncPublisher(JDK_TABLE_TOPIC).jdkRemoved(jdk);
+    ApplicationManager.getApplication().getMessageBus().syncPublisher(JDK_TABLE_TOPIC).jdkRemoved(jdk);
     mySdks.remove(jdk);
     if (jdk instanceof Disposable) {
       Disposer.dispose((Disposable)jdk);
@@ -274,7 +271,7 @@ public class ProjectJdkTableImpl extends ProjectJdkTable implements ExportableCo
 
     if (!previousName.equals(newName)) {
       // fire changes because after renaming JDK its name may match the associated jdk name of modules/project
-      myMessageBus.syncPublisher(JDK_TABLE_TOPIC).jdkNameChanged(originalJdk, previousName);
+      ApplicationManager.getApplication().getMessageBus().syncPublisher(JDK_TABLE_TOPIC).jdkNameChanged(originalJdk, previousName);
     }
   }
 
index 01c99eec21f8a1bba10f27f75167518ad4751fee..59c09461834e5529988a6a18d2c35c2eb61178d7 100644 (file)
@@ -30,7 +30,7 @@ import java.util.*;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-public class SdkDownloadTracker implements Disposable {
+public class SdkDownloadTracker {
   private static final Logger LOG = Logger.getInstance(SdkDownloadTracker.class);
 
   @NotNull
@@ -41,19 +41,13 @@ public class SdkDownloadTracker implements Disposable {
   private final List<PendingDownload> myPendingTasks = new CopyOnWriteArrayList<>();
 
   public SdkDownloadTracker() {
-    ApplicationManager.getApplication().getMessageBus()
-      .connect(this)
-      .subscribe(ProjectJdkTable.JDK_TABLE_TOPIC,
-                 new ProjectJdkTable.Adapter() {
-                   @Override
-                   public void jdkRemoved(@NotNull Sdk jdk) {
-                     onSdkRemoved(jdk);
-                   }
-                 });
-  }
-
-  @Override
-  public void dispose() {
+    ApplicationManager.getApplication().getMessageBus().simpleConnect()
+      .subscribe(ProjectJdkTable.JDK_TABLE_TOPIC, new ProjectJdkTable.Listener() {
+        @Override
+        public void jdkRemoved(@NotNull Sdk jdk) {
+          onSdkRemoved(jdk);
+        }
+      });
   }
 
   public void onSdkRemoved(@NotNull Sdk sdk) {
index 0e472f4910403ee2c886bed874d449b5c797a366..b0ee7ab380b1d4aabf1498c8f41b151c9b0a804e 100644 (file)
@@ -1,24 +1,10 @@
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.openapi.vcs.impl;
 
 import com.intellij.util.messages.Topic;
 
 public interface LineStatusTrackerSettingListener {
-  Topic<LineStatusTrackerSettingListener> TOPIC = Topic.create("line status tracker settings changed", LineStatusTrackerSettingListener.class);
+  Topic<LineStatusTrackerSettingListener> TOPIC = new Topic<>(LineStatusTrackerSettingListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
   void settingsUpdated();
 }
index e878104c62ea876be81987ae75bae76c3eb66652..02daf8b91ee4d85ee73319b1f029cb16224ca2e1 100644 (file)
@@ -26,6 +26,7 @@ import com.intellij.psi.PsiManager;
 import com.intellij.ui.AppUIUtil;
 import com.intellij.ui.UIBundle;
 import com.intellij.util.Consumer;
+import com.intellij.util.messages.MessageBusConnection;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -85,8 +86,10 @@ public final class TogglePopupHintsPanel extends EditorBasedWidget implements St
   @Override
   public void install(@NotNull StatusBar statusBar) {
     super.install(statusBar);
-    myConnection.subscribe(PowerSaveMode.TOPIC, this::updateStatus);
-    myConnection.subscribe(ProfileChangeAdapter.TOPIC,  new ProfileChangeAdapter() {
+
+    MessageBusConnection connection = myConnection;
+    connection.subscribe(PowerSaveMode.TOPIC, this::updateStatus);
+    connection.subscribe(ProfileChangeAdapter.TOPIC,  new ProfileChangeAdapter() {
       @Override
       public void profilesInitialized() {
         updateStatus();
@@ -102,7 +105,7 @@ public final class TogglePopupHintsPanel extends EditorBasedWidget implements St
       }
     });
 
-    myConnection.subscribe(FileHighlightingSettingListener.SETTING_CHANGE, (__, ___) -> updateStatus());
+    connection.subscribe(FileHighlightingSettingListener.SETTING_CHANGE, (__, ___) -> updateStatus());
     updateStatus();
   }
 
index 8bbb4d87dd6e6e6faf741a20d9e29909a14a48f3..a53c4402c471431a71e927269e07fc185072391b 100644 (file)
@@ -1,5 +1,4 @@
 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
-
 package com.intellij.refactoring.suggested
 
 import com.intellij.codeInsight.template.TemplateManager
@@ -9,6 +8,7 @@ import com.intellij.openapi.command.CommandListener
 import com.intellij.openapi.command.undo.UndoManager
 import com.intellij.openapi.editor.Document
 import com.intellij.openapi.editor.EditorFactory
+import com.intellij.openapi.editor.ProjectDisposeAwareDocumentListener
 import com.intellij.openapi.editor.RangeMarker
 import com.intellij.openapi.editor.event.DocumentEvent
 import com.intellij.openapi.editor.event.DocumentListener
@@ -21,9 +21,9 @@ import com.intellij.psi.util.hasErrorElementInRange
 
 class SuggestedRefactoringChangeListener(
   private val project: Project,
-  private val watcher: SuggestedRefactoringSignatureWatcher
-: Disposable
-{
+  private val watcher: SuggestedRefactoringSignatureWatcher,
+  parentDisposable: Disposable
+{
   private val psiDocumentManager = PsiDocumentManager.getInstance(project)
   private val newIdentifierWatcher = NewIdentifierWatcher(5)
 
@@ -37,20 +37,17 @@ class SuggestedRefactoringChangeListener(
 
   private var isFirstChangeInsideCommand = false
 
-  fun attach() {
-    EditorFactory.getInstance().eventMulticaster.addDocumentListener(MyDocumentListener(), this)
-    PsiManager.getInstance(project).addPsiTreeChangeListener(MyPsiTreeChangeListener(), this)
+  init {
+    EditorFactory.getInstance().eventMulticaster.addDocumentListener(ProjectDisposeAwareDocumentListener.create(project, MyDocumentListener()), parentDisposable)
+    PsiManager.getInstance(project).addPsiTreeChangeListener(MyPsiTreeChangeListener(), parentDisposable)
 
-    project.messageBus.connect(this).subscribe(CommandListener.TOPIC, object : CommandListener {
+    project.messageBus.connect(parentDisposable).subscribe(CommandListener.TOPIC, object : CommandListener {
       override fun commandStarted(event: CommandEvent) {
         isFirstChangeInsideCommand = true
       }
     })
   }
 
-  override fun dispose() {
-  }
-
   fun reset(withNewIdentifiers: Boolean = false) {
     if (editingState != null) {
       editingState!!.signatureRangeMarker.dispose()
@@ -195,7 +192,9 @@ class SuggestedRefactoringChangeListener(
     override fun beforeDocumentChange(event: DocumentEvent) {
       val document = event.document
       val psiFile = psiDocumentManager.getCachedPsiFile(document) ?: return
-      if (!psiFile.isPhysical || psiFile is PsiCodeFragment) return
+      if (!psiFile.isPhysical || psiFile is PsiCodeFragment) {
+        return
+      }
 
       val firstChangeInsideCommand = isFirstChangeInsideCommand
       isFirstChangeInsideCommand = false
@@ -313,7 +312,9 @@ class SuggestedRefactoringChangeListener(
     }
 
     private fun processBeforeEvent(event: PsiTreeChangeEvent) {
-      if (!isFirstChangeInsideCommand) return
+      if (project.isDisposed || !isFirstChangeInsideCommand) {
+        return
+      }
 
       val psiFile = event.file ?: return
       val document = psiDocumentManager.getCachedDocument(psiFile)
index c174b7f4ce52c83371aed8ce9b85afef6f6be1d4..047b7673fde1489435645d072dc9f029f718808d 100644 (file)
@@ -1,25 +1,35 @@
 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.refactoring.suggested
 
+import com.intellij.openapi.application.ApplicationManager
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.startup.StartupActivity
-import com.intellij.openapi.util.Disposer
 import com.intellij.openapi.util.TextRange
 import org.jetbrains.annotations.TestOnly
 
 class SuggestedRefactoringProviderImpl(project: Project) : SuggestedRefactoringProvider {
+  companion object {
+    fun getInstance(project: Project): SuggestedRefactoringProviderImpl {
+      return SuggestedRefactoringProvider.getInstance(project) as SuggestedRefactoringProviderImpl
+    }
+  }
+
   val availabilityIndicator = SuggestedRefactoringAvailabilityIndicator(project)
   private val changeCollector = SuggestedRefactoringChangeCollector(availabilityIndicator)
-  private val listener = SuggestedRefactoringChangeListener(project, changeCollector)
+  private val listener: SuggestedRefactoringChangeListener
 
   val state: SuggestedRefactoringState?
     get() = changeCollector.state
 
+  init {
+    listener = SuggestedRefactoringChangeListener(project, changeCollector, project)
+  }
+
   internal class Startup : StartupActivity.DumbAware {
     override fun runActivity(project: Project) {
-      val listener = getInstance(project).listener
-      listener.attach()
-      Disposer.register(project, listener)
+      if (!ApplicationManager.getApplication().isUnitTestMode) {
+        getInstance(project)
+      }
     }
   }
 
@@ -42,9 +52,4 @@ class SuggestedRefactoringProviderImpl(project: Project) : SuggestedRefactoringP
   var _amendStateInBackgroundEnabled: Boolean
     get() = changeCollector._amendStateInBackgroundEnabled
     set(value) { changeCollector._amendStateInBackgroundEnabled = value }
-
-  companion object {
-    fun getInstance(project: Project): SuggestedRefactoringProviderImpl =
-      SuggestedRefactoringProvider.getInstance(project) as SuggestedRefactoringProviderImpl
-  }
 }
index 005426e51abd14d28932e8f9adb759e0769aa6be..e9224522dcaf3fe27480267184a5c9f1fd17f2cc 100644 (file)
@@ -1,28 +1,13 @@
-/*
- * Copyright 2000-2009 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/*
- * @author max
- */
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij;
 
 import com.intellij.openapi.fileEditor.FileDocumentManagerListener;
 import com.intellij.util.messages.Topic;
 
-public class AppTopics {
-  public static final Topic<FileDocumentManagerListener> FILE_DOCUMENT_SYNC =
-    new Topic<>("Document load, save and reload events", FileDocumentManagerListener.class);
+public final class AppTopics {
+  /**
+   * Document load, save and reload events.
+   */
+  @Topic.AppLevel
+  public static final Topic<FileDocumentManagerListener> FILE_DOCUMENT_SYNC = new Topic<>(FileDocumentManagerListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 }
\ No newline at end of file
index 16c87c4a1e6eae9de5b3409dca5e81be43eae6b5..bf2e318940e8b441909d10501e3b6b2e5422aaad 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.ide;
 
 import com.intellij.util.messages.Topic;
@@ -7,7 +7,8 @@ import com.intellij.util.messages.Topic;
  * Listener for receiving notifications when the IDE window is activated or deactivated.
  */
 public interface FrameStateListener {
-  Topic<FrameStateListener> TOPIC = new Topic<>("FrameStateListener", FrameStateListener.class);
+  @Topic.AppLevel
+  Topic<FrameStateListener> TOPIC = new Topic<>("FrameStateListener", FrameStateListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
   /**
    * Called when the IDE window is deactivated.
index c66aad0dd2e2c6e8aab7560ea9a74f6fcc8db659..ae280d94dc4ce0649ed1c483a37466e785f13b6c 100644 (file)
@@ -12,7 +12,7 @@ import java.util.EventListener;
  * Listener for {@link FileEditorManager} events. All methods are invoked in EDT.
  */
 public interface FileEditorManagerListener extends EventListener {
-  // project level
+  @Topic.ProjectLevel
   Topic<FileEditorManagerListener> FILE_EDITOR_MANAGER = new Topic<>(FileEditorManagerListener.class, Topic.BroadcastDirection.TO_PARENT);
 
   /**
index 1a0da7ffb558db897d344200d3b2c218860a4250..93ed2df1c8213f7411699f3e7a4e4b976ddd5d6f 100644 (file)
@@ -25,8 +25,7 @@ public abstract class FileTypeManager extends FileTypeRegistry {
 
   private static FileTypeManager ourInstance = CachedSingletonsRegistry.markCachedField(FileTypeManager.class);
 
-  @NotNull
-  public static final Topic<FileTypeListener> TOPIC = new Topic<>("File types change", FileTypeListener.class);
+  public static final @NotNull Topic<FileTypeListener> TOPIC = new Topic<>(FileTypeListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
   /**
    * Returns the singleton instance of the FileTypeManager component.
@@ -86,9 +85,7 @@ public abstract class FileTypeManager extends FileTypeRegistry {
   @Deprecated
   public abstract String @NotNull [] getAssociatedExtensions(@NotNull FileType type);
 
-
-  @NotNull
-  public abstract List<FileNameMatcher> getAssociations(@NotNull FileType type);
+  public abstract @NotNull List<FileNameMatcher> getAssociations(@NotNull FileType type);
 
   /**
    * Adds a listener for receiving notifications about changes in the list of
@@ -118,12 +115,10 @@ public abstract class FileTypeManager extends FileTypeRegistry {
    * @return Known file type or {@code null}. Never returns {@link FileTypes#UNKNOWN}.
    * @deprecated Use {@link #getKnownFileTypeOrAssociate(VirtualFile, Project)} instead
    */
-  @Nullable
   @Deprecated
-  public FileType getKnownFileTypeOrAssociate(@NotNull VirtualFile file) { return file.getFileType(); }
+  public @Nullable FileType getKnownFileTypeOrAssociate(@NotNull VirtualFile file) { return file.getFileType(); }
 
-  @Nullable
-  public abstract FileType getKnownFileTypeOrAssociate(@NotNull VirtualFile file, @NotNull Project project);
+  public abstract @Nullable FileType getKnownFileTypeOrAssociate(@NotNull VirtualFile file, @NotNull Project project);
 
   /**
    * Returns the semicolon-delimited list of patterns for files and folders
@@ -132,8 +127,7 @@ public abstract class FileTypeManager extends FileTypeRegistry {
    *
    * @return Semicolon-delimited list of patterns.
    */
-  @NotNull
-  public abstract String getIgnoredFilesList();
+  public abstract @NotNull String getIgnoredFilesList();
 
   /**
    * Sets new list of semicolon-delimited patterns for files and folders which
@@ -171,11 +165,9 @@ public abstract class FileTypeManager extends FileTypeRegistry {
 
   public abstract void removeAssociation(@NotNull FileType type, @NotNull FileNameMatcher matcher);
 
-  @NotNull
-  public static FileNameMatcher parseFromString(@NotNull String pattern) {
+  public static @NotNull FileNameMatcher parseFromString(@NotNull String pattern) {
     return FileNameMatcherFactory.getInstance().createMatcher(pattern);
   }
 
-  @NotNull
-  public abstract FileType getStdFileType(@NotNull @NonNls String fileTypeName);
+  public abstract @NotNull FileType getStdFileType(@NotNull @NonNls String fileTypeName);
 }
index fe62296df3b560cf8f1d86ee085ad329dc3a2eed..5e53e83d47b5f1cee2f3123f4507a9530c74117c 100644 (file)
@@ -6,7 +6,8 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public interface KeymapManagerListener {
-  Topic<KeymapManagerListener> TOPIC = new Topic<>("KeymapManagerListener", KeymapManagerListener.class, Topic.BroadcastDirection.NONE);
+  @Topic.AppLevel
+  Topic<KeymapManagerListener> TOPIC = new Topic<>(KeymapManagerListener.class, Topic.BroadcastDirection.NONE);
 
   default void keymapAdded(@NotNull Keymap keymap) {
   }
index 5ef501c3a01ed1fc52e8c1762f5c2db524c497f0..dbefc9894095205960eb55de3bd53b062d2faa88 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 @file:JvmName("ProjectUtil")
 package com.intellij.openapi.project
 
@@ -197,7 +197,7 @@ fun runWhenProjectOpened(project: Project? = null, handler: Consumer<Project>) {
  * Add one-time projectOpened listener.
  */
 inline fun runWhenProjectOpened(project: Project? = null, crossinline handler: (project: Project) -> Unit) {
-  val connection = (project ?: ApplicationManager.getApplication()).messageBus.connect()
+  val connection = (project ?: ApplicationManager.getApplication()).messageBus.simpleConnect()
   connection.subscribe(ProjectManager.TOPIC, object : ProjectManagerListener {
     override fun projectOpened(eventProject: Project) {
       if (project == null || project === eventProject) {
index d53a31dc06b7c463fc00cae82efc5ba3133883ba..9ebda023f3359ab969f7522cc3a9ac80503c3cc6 100644 (file)
@@ -7,11 +7,11 @@ import org.jetbrains.annotations.NotNull;
 
 /**
  * Reports some project lifecycle events. Note that these events are published on application-level {@link com.intellij.util.messages.MessageBus}.
- * They're also delivered for subscribers on project and module levels, but they will need to check that the events are relevant, i.e. the
+ * They're also delivered for subscribers on project levels, but they will need to check that the events are relevant, i.e. the
  * {@code project} parameter is the project those subscribers are associated with.
  */
 public interface ProjectLifecycleListener {
-  Topic<ProjectLifecycleListener> TOPIC = Topic.create("Various stages of project lifecycle notifications", ProjectLifecycleListener.class);
+  Topic<ProjectLifecycleListener> TOPIC = new Topic<>(ProjectLifecycleListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
   /**
    * @deprecated Do not use.
index a843c336bbadd93e2a55b266a2fc1e64d86e4e7d..3d6cdff3d6ff8bb7d7bcccfc7a9adbfe655b6702 100644 (file)
@@ -1,18 +1,4 @@
-/*
- * Copyright 2000-2009 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.codeInsight.hint;
 
 import com.intellij.openapi.project.Project;
@@ -20,11 +6,11 @@ import com.intellij.ui.LightweightHint;
 import com.intellij.util.messages.Topic;
 import org.jetbrains.annotations.NotNull;
 
-/**
- * @author yole
- */
 public interface EditorHintListener {
-  Topic<EditorHintListener> TOPIC = Topic.create("Notification about showing editor hints", EditorHintListener.class);
+  /**
+   * Notification about showing editor hints.
+   */
+  Topic<EditorHintListener> TOPIC = new Topic<>(EditorHintListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
   void hintShown(Project project, @NotNull LightweightHint hint, int flags);
 }
\ No newline at end of file
index 291cc36bb4a5eacbea4a56a36fd40e3db345b41b..b3b43a037045f1a604966e3b2cc53ad58b3eb50d 100644 (file)
@@ -14,7 +14,8 @@ import java.util.List;
  * Listener for application lifecycle events.
  */
 public interface AppLifecycleListener {
-  Topic<AppLifecycleListener> TOPIC = new Topic<>(AppLifecycleListener.class, Topic.BroadcastDirection.NONE);
+  @Topic.AppLevel
+  Topic<AppLifecycleListener> TOPIC = new Topic<>(AppLifecycleListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
   /** @deprecated use {@link #appFrameCreated(List)} */
   @Deprecated
index be33907f6573e5a25185ca4a6f09a2bddeed2f07..0e31a982d769d691797775946da46050ba6c86c4 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.ide;
 
 import com.intellij.ide.util.PropertiesComponent;
@@ -6,7 +6,6 @@ import com.intellij.notification.Notification;
 import com.intellij.notification.NotificationType;
 import com.intellij.notification.Notifications;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.colors.EditorColors;
@@ -38,8 +37,7 @@ public final class CommandLineWaitingManager {
   private final Set<Object> myDismissedObjects = Collections.synchronizedSet(new HashSet<>());
 
   private CommandLineWaitingManager() {
-    final MessageBusConnection busConnection = ApplicationManager.getApplication().getMessageBus().connect();
-    
+    MessageBusConnection busConnection = ApplicationManager.getApplication().getMessageBus().connect();
     busConnection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() {
       @Override
       public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
@@ -53,25 +51,21 @@ public final class CommandLineWaitingManager {
       }
     });
   }
-  
-  @NotNull
-  public static CommandLineWaitingManager getInstance() {
-    return ServiceManager.getService(CommandLineWaitingManager.class);
+
+  public static @NotNull CommandLineWaitingManager getInstance() {
+    return ApplicationManager.getApplication().getService(CommandLineWaitingManager.class);
   }
 
-  @NotNull
-  public Future<CliResult> addHookForFile(@NotNull VirtualFile file) {
+  public @NotNull Future<CliResult> addHookForFile(@NotNull VirtualFile file) {
     return addHookAndNotify(file, IdeBundle.message("activation.file.is.waiting.notification", file.getPath()));
   }
 
-  @NotNull
-  public Future<CliResult> addHookForProject(@NotNull Project project) {
+  public @NotNull Future<CliResult> addHookForProject(@NotNull Project project) {
     return addHookAndNotify(project, IdeBundle.message("activation.project.is.waiting.notification", project.getName()));
   }
-  
-  @NotNull
-  private Future<CliResult> addHookAndNotify(@NotNull Object fileOrProject,
-                                             @NotNull String notificationText) {
+
+  private @NotNull Future<CliResult> addHookAndNotify(@NotNull Object fileOrProject,
+                                                      @NotNull String notificationText) {
     LOG.info(notificationText);
 
     final CompletableFuture<CliResult> result = new CompletableFuture<>();
@@ -89,34 +83,33 @@ public final class CommandLineWaitingManager {
   private void freeObject(@NotNull Object fileOrProject) {
     myDismissedObjects.remove(fileOrProject);
     CompletableFuture<CliResult> future = myFileOrProjectToCallback.remove(fileOrProject);
-    if (future == null) return;
+    if (future == null) {
+      return;
+    }
     future.complete(CliResult.OK);
   }
 
-  public static class MyNotification extends EditorNotifications.Provider<EditorNotificationPanel> {
+  static final class MyNotification extends EditorNotifications.Provider<EditorNotificationPanel> {
     private static final Key<EditorNotificationPanel> KEY = Key.create("CommandLineWaitingNotification");
 
-    @NotNull
     @Override
-    public Key<EditorNotificationPanel> getKey() {
+    public @NotNull Key<EditorNotificationPanel> getKey() {
       return KEY;
     }
 
-    @Nullable
     @Override
-    public EditorNotificationPanel createNotificationPanel(@NotNull VirtualFile file, @NotNull FileEditor fileEditor, @NotNull Project project) {
-      if (getInstance().myFileOrProjectToCallback.containsKey(file)
-          && !PropertiesComponent.getInstance().getBoolean(DO_NOT_SHOW_KEY, false)
-          && !getInstance().myDismissedObjects.contains(file)) {
-        return new MyNotificationPanel(file);
-      }
-      else {
-        return null;
+    public @Nullable EditorNotificationPanel createNotificationPanel(@NotNull VirtualFile file, @NotNull FileEditor fileEditor, @NotNull Project project) {
+      if (!PropertiesComponent.getInstance().getBoolean(DO_NOT_SHOW_KEY, false)) {
+        CommandLineWaitingManager manager = ApplicationManager.getApplication().getServiceIfCreated(CommandLineWaitingManager.class);
+        if (manager != null && manager.myFileOrProjectToCallback.containsKey(file) && !manager.myDismissedObjects.contains(file)) {
+          return new MyNotificationPanel(file);
+        }
       }
+      return null;
     }
   }
 
-  private static class MyNotificationPanel extends EditorNotificationPanel {
+  private static final class MyNotificationPanel extends EditorNotificationPanel {
     private MyNotificationPanel(@NotNull VirtualFile virtualFile) {
       super(EditorColors.GUTTER_BACKGROUND);
       setText(IdeBundle.message("activation.file.is.waiting.title"));
index d9745b83429fbee68a256ffc732ec4a7889188ba..c556ed71e9ce19b58e2ba823d0106c8ba4b6bf5e 100644 (file)
@@ -9,7 +9,7 @@ import com.intellij.openapi.wm.ToolWindowManager
 import com.intellij.openapi.wm.ToolWindowType
 import com.intellij.util.ui.UIUtil
 
-class HideToolWindowAction : AnAction(), DumbAware {
+internal class HideToolWindowAction : AnAction(), DumbAware {
   companion object {
     internal fun shouldBeHiddenByShortCut(manager: ToolWindowManager, id: String): Boolean {
       val window = manager.getToolWindow(id)
index e2b8d91370f1b0f1a8595f11ae97d7a6982d78e2..dd9063880c8381cc64a8fed7becb053da891655d 100644 (file)
@@ -1,9 +1,9 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.ide.actions
 
 import com.intellij.openapi.actionSystem.ActionPlaces
 
-class PopupInMainMenuActionGroup : NonTrivialActionGroup() {
+private class PopupInMainMenuActionGroup : NonTrivialActionGroup() {
   override fun isPopup(place: String): Boolean {
     return place == ActionPlaces.MAIN_MENU
   }
index f59155b66b7a015c817c645c22862bd11600c235..1679434a69bd10f2572cd017a689efdaa0948178 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.ide.actions;
 
 import com.intellij.featureStatistics.FeatureUsageTracker;
@@ -53,14 +53,15 @@ import java.util.List;
 
 import static com.intellij.ui.speedSearch.SpeedSearchSupply.ENTERED_PREFIX_PROPERTY_NAME;
 
-public class RecentLocationsAction extends DumbAwareAction implements LightEditCompatible {
+public final class RecentLocationsAction extends DumbAwareAction implements LightEditCompatible {
   public static final String RECENT_LOCATIONS_ACTION_ID = "RecentLocations";
   private static final String LOCATION_SETTINGS_KEY = "recent.locations.popup";
   private static final int DEFAULT_WIDTH = JBUIScale.scale(700);
   private static final int DEFAULT_HEIGHT = JBUIScale.scale(530);
   private static final int MINIMUM_WIDTH = JBUIScale.scale(600);
   private static final int MINIMUM_HEIGHT = JBUIScale.scale(450);
-  static class Holder {
+
+  static final class Holder {
     private static final Color SHORTCUT_FOREGROUND_COLOR = UIUtil.getContextHelpForeground();
     public static final String SHORTCUT_HEX_COLOR = String.format("#%02x%02x%02x",
                                                                   SHORTCUT_FOREGROUND_COLOR.getRed(),
@@ -202,8 +203,7 @@ public class RecentLocationsAction extends DumbAwareAction implements LightEditC
     popup.pack(false, false);
   }
 
-  @NotNull
-  public static JBCheckBox createCheckbox(@NotNull ShortcutSet checkboxShortcutSet, boolean showChanged) {
+  private static @NotNull JBCheckBox createCheckbox(@NotNull ShortcutSet checkboxShortcutSet, boolean showChanged) {
     //noinspection HardCodedStringLiteral
     String text = "<html>"
                   + IdeBundle.message("recent.locations.title.text")
@@ -250,16 +250,14 @@ public class RecentLocationsAction extends DumbAwareAction implements LightEditC
     listWithFilter.getSpeedSearch().reset();
   }
 
-  @NotNull
-  private static JPanel createMainPanel(@NotNull ListWithFilter listWithFilter, @NotNull JPanel topPanel) {
+  private static @NotNull JPanel createMainPanel(@NotNull ListWithFilter listWithFilter, @NotNull JPanel topPanel) {
     JPanel mainPanel = new JPanel(new BorderLayout());
     mainPanel.add(topPanel, BorderLayout.NORTH);
     mainPanel.add(listWithFilter, BorderLayout.CENTER);
     return mainPanel;
   }
 
-  @NotNull
-  private static JPanel createHeaderPanel(@NotNull JLabel title, @NotNull JComponent checkbox) {
+  private static @NotNull JPanel createHeaderPanel(@NotNull JLabel title, @NotNull JComponent checkbox) {
     JPanel topPanel = new CaptionPanel();
     topPanel.add(title, BorderLayout.WEST);
     topPanel.add(checkbox, BorderLayout.EAST);
@@ -276,8 +274,7 @@ public class RecentLocationsAction extends DumbAwareAction implements LightEditC
     return topPanel;
   }
 
-  @NotNull
-  private static JLabel createTitle(boolean showChanged) {
+  private static @NotNull JLabel createTitle(boolean showChanged) {
     JBLabel title = new JBLabel();
     title.setFont(title.getFont().deriveFont(Font.BOLD));
     updateTitleText(title, showChanged);
@@ -290,8 +287,7 @@ public class RecentLocationsAction extends DumbAwareAction implements LightEditC
                   : IdeBundle.message("recent.locations.popup.title"));
   }
 
-  @NotNull
-  private static Function<RecentLocationItem, String> getNamer(@NotNull RecentLocationsDataModel data, @NotNull JBCheckBox checkBox) {
+  private static @NotNull Function<RecentLocationItem, String> getNamer(@NotNull RecentLocationsDataModel data, @NotNull JBCheckBox checkBox) {
     return value -> {
       String breadcrumb = data.getBreadcrumbsMap(checkBox.isSelected()).get(value.getInfo());
       EditorEx editor = value.getEditor();
index 9c864946aabcd4e9c7694a4b70928e62bad6171f..2988798b672c69622411eebc7770ca3be71a9a95 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.ide.actions
 
 import com.intellij.codeInsight.breadcrumbs.FileBreadcrumbsCollector
@@ -24,12 +24,16 @@ import com.intellij.openapi.util.text.StringUtil
 import com.intellij.util.DocumentUtil
 import com.intellij.util.concurrency.SynchronizedClearableLazy
 import com.intellij.util.containers.ContainerUtil
+import org.jetbrains.annotations.ApiStatus
 import java.util.*
 import java.util.stream.Collectors
 import javax.swing.ScrollPaneConstants
+import kotlin.math.max
+import kotlin.math.min
 
-data class RecentLocationsDataModel(val project: Project, val editorsToRelease: ArrayList<Editor> = arrayListOf()) {
-  val projectConnection = project.messageBus.connect()
+@ApiStatus.Internal
+internal data class RecentLocationsDataModel(val project: Project, val editorsToRelease: ArrayList<Editor> = arrayListOf()) {
+  val projectConnection = project.messageBus.simpleConnect()
 
   init {
     projectConnection.subscribe(RecentPlacesListener.TOPIC, object : RecentPlacesListener {
@@ -259,14 +263,14 @@ data class RecentLocationsDataModel(val project: Project, val editorsToRelease:
 
     val beforeAfterLinesCount = Registry.intValue("recent.locations.lines.before.and.after", 2)
 
-    val before = Math.min(beforeAfterLinesCount, line)
-    val after = Math.min(beforeAfterLinesCount, lineCount - line)
+    val before = min(beforeAfterLinesCount, line)
+    val after = min(beforeAfterLinesCount, lineCount - line)
 
     val linesBefore = before + beforeAfterLinesCount - after
     val linesAfter = after + beforeAfterLinesCount - before
 
-    val startLine = Math.max(line - linesBefore, 0)
-    val endLine = Math.min(line + linesAfter, lineCount - 1)
+    val startLine = max(line - linesBefore, 0)
+    val endLine = min(line + linesAfter, lineCount - 1)
 
     val startOffset = document.getLineStartOffset(startLine)
     val endOffset = document.getLineEndOffset(endLine)
index d3689213bc45140bad9ea8da9df204256a875038..5bfaccf52f44392dcacd457c770f8db901509085 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.ide.actions
 
 import com.intellij.ide.SaveAndSyncHandler
@@ -7,7 +7,6 @@ import com.intellij.openapi.actionSystem.AnAction
 import com.intellij.openapi.actionSystem.AnActionEvent
 import com.intellij.openapi.actionSystem.CommonDataKeys
 import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
 import com.intellij.openapi.editor.impl.TrailingSpacesStripper
 import com.intellij.openapi.fileEditor.FileDocumentManager
 import com.intellij.openapi.project.DumbAware
index 73137623952b7ae5b268c60bc6ffd51f17a6c87c..4c4619321532a724db13cb999814f195675e3df6 100644 (file)
@@ -13,8 +13,7 @@ import org.jetbrains.annotations.Nullable;
  * via the {@link #TOPIC} defined below.
  */
 public interface BatchFileChangeListener {
-
-  Topic<BatchFileChangeListener> TOPIC = Topic.create("Batch File Update", BatchFileChangeListener.class);
+  Topic<BatchFileChangeListener> TOPIC = new Topic<>(BatchFileChangeListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
 
   /**
    * @param project Project where many file changes are expected to happen
index 624ac3a36324a1d05e0da8e6349ac0dfa0498cb8..5760e621e1e5663f92ba3936a0ad969e723eb49b 100644 (file)
@@ -51,7 +51,7 @@ import com.intellij.util.MemoryDumpHelper
 import com.intellij.util.SystemProperties
 import com.intellij.util.io.URLUtil
 import com.intellij.util.messages.Topic
-import com.intellij.util.messages.impl.MessageBusImpl
+import com.intellij.util.messages.impl.MessageBusEx
 import com.intellij.util.xmlb.BeanBinding
 import java.nio.file.Paths
 import java.text.SimpleDateFormat
@@ -87,7 +87,7 @@ interface DynamicPluginListener {
   fun checkUnloadPlugin(pluginDescriptor: IdeaPluginDescriptor) { }
 
   companion object {
-    @JvmField val TOPIC = Topic.create("DynamicPluginListener", DynamicPluginListener::class.java)
+    @JvmField val TOPIC = Topic(DynamicPluginListener::class.java, Topic.BroadcastDirection.TO_DIRECT_CHILDREN)
   }
 }
 
@@ -395,7 +395,7 @@ object DynamicPlugins {
           ActionToolbarImpl.updateAllToolbarsImmediately()
           (NotificationsManager.getNotificationsManager() as NotificationsManagerImpl).expireAll()
 
-          (ApplicationManager.getApplication().messageBus as MessageBusImpl).clearPublisherCache()
+          (ApplicationManager.getApplication().messageBus as MessageBusEx).clearPublisherCache()
 
           if (disable) {
             // update list of disabled plugins
@@ -491,7 +491,7 @@ object DynamicPlugins {
 
     val pluginId = pluginDescriptor.pluginId ?: loadedPluginDescriptor.pluginId
     application.unloadServices(pluginDescriptor.appContainerDescriptor.getServices(), pluginId)
-    (application.messageBus as MessageBusImpl).unsubscribeLazyListeners(pluginId, pluginDescriptor.appContainerDescriptor.getListeners())
+    (application.messageBus as MessageBusEx).unsubscribeLazyListeners(pluginId, pluginDescriptor.appContainerDescriptor.getListeners())
 
     for (project in openProjects) {
       (project as ProjectImpl).unloadServices(pluginDescriptor.projectContainerDescriptor.getServices(), pluginId)
@@ -499,7 +499,7 @@ object DynamicPlugins {
       for (module in ModuleManager.getInstance(project).modules) {
         (module as ComponentManagerImpl).unloadServices(moduleServices, pluginId)
       }
-      (project.messageBus as MessageBusImpl).unsubscribeLazyListeners(pluginId, pluginDescriptor.projectContainerDescriptor.getListeners())
+      (project.messageBus as MessageBusEx).unsubscribeLazyListeners(pluginId, pluginDescriptor.projectContainerDescriptor.getListeners())
     }
   }
 
index 4b893af2951d772b20c26438f5a359380c9fdf08..126e65c9fb5966d1811ffc98851dfcc95426a790 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.ide.startup;
 
 import com.intellij.util.messages.Topic;
@@ -6,7 +6,8 @@ import org.jetbrains.annotations.ApiStatus;
 
 @ApiStatus.Internal
 public interface ProjectLoadListener {
-  Topic<ProjectLoadListener> TOPIC = new Topic<>(ProjectLoadListener.class);
+  @Topic.AppLevel
+  Topic<ProjectLoadListener> TOPIC = new Topic<>(ProjectLoadListener.class, Topic.BroadcastDirection.NONE);
 
   default void postStartUpActivitiesPassed() {
   }
index 26256335644f5072821835687a5b464b8e5c1769..fc01c9ed0b0781631e432736be4a7fff6303c3a2 100644 (file)
@@ -99,7 +99,7 @@ fun registerAppComponents(pluginFuture: CompletableFuture<List<IdeaPluginDescrip
                           app: ApplicationImpl): CompletableFuture<List<IdeaPluginDescriptor>> {
   return pluginFuture.thenApply {
     runActivity("app component registration", ActivityCategory.MAIN) {
-      app.registerComponents(it, false)
+      app.registerComponents(it)
     }
     it
   }
index d945ca94d78ea478b9073e221cbbca05cca9272c..f2647326eec6f51108119d47b2bc36b73d11aa59 100644 (file)
@@ -3,7 +3,6 @@ package com.intellij.openapi.actionSystem;
 
 import com.intellij.icons.AllIcons;
 import com.intellij.ide.HelpTooltip;
-import com.intellij.openapi.Disposable;
 import com.intellij.openapi.actionSystem.ex.ActionButtonLook;
 import com.intellij.openapi.actionSystem.ex.ActionUtil;
 import com.intellij.openapi.actionSystem.ex.AnActionListener;
@@ -12,10 +11,10 @@ import com.intellij.openapi.actionSystem.impl.ActionButton;
 import com.intellij.openapi.actionSystem.impl.ActionManagerImpl;
 import com.intellij.openapi.actionSystem.impl.MenuItemPresentationFactory;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.util.Disposer;
 import com.intellij.openapi.util.IconLoader;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.ui.scale.JBUIScale;
+import com.intellij.util.messages.SimpleMessageBusConnection;
 import com.intellij.util.ui.JBInsets;
 import com.intellij.util.ui.JBUI;
 import com.intellij.util.ui.StartupUiUtil;
@@ -27,8 +26,6 @@ import java.awt.*;
 import java.awt.event.MouseEvent;
 import java.awt.geom.Area;
 
-import static com.intellij.openapi.actionSystem.ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE;
-
 public final class SplitButtonAction extends ActionGroup implements CustomComponentAction {
   private final ActionGroup myActionGroup;
 
@@ -79,10 +76,10 @@ public final class SplitButtonAction extends ActionGroup implements CustomCompon
     private AnAction selectedAction;
     private boolean actionEnabled = true;
     private MousePressType mousePressType = MousePressType.None;
-    private Disposable myDisposable;
+    private SimpleMessageBusConnection myConnection;
 
     private SplitButton(@NotNull AnAction action, @NotNull Presentation presentation, String place, ActionGroup actionGroup) {
-      super(action, presentation, place, DEFAULT_MINIMUM_BUTTON_SIZE);
+      super(action, presentation, place, ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE);
       myActionGroup = actionGroup;
 
       AnAction[] actions = myActionGroup.getChildren(null);
@@ -217,7 +214,7 @@ public final class SplitButtonAction extends ActionGroup implements CustomCompon
       JPopupMenu menu = popupMenu.getComponent();
       menu.addPopupMenuListener(myPopupState);
       if (event.isFromActionToolbar()) {
-        menu.show(this, DEFAULT_MINIMUM_BUTTON_SIZE.width + getInsets().left, getHeight());
+        menu.show(this, ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE.width + getInsets().left, getHeight());
       }
       else {
         menu.show(this, getWidth(), 0);
@@ -229,9 +226,8 @@ public final class SplitButtonAction extends ActionGroup implements CustomCompon
     @Override
     public void addNotify() {
       super.addNotify();
-      myDisposable = Disposer.newDisposable();
-      Disposer.register(ApplicationManager.getApplication(), myDisposable);
-      ApplicationManager.getApplication().getMessageBus().connect(myDisposable).subscribe(AnActionListener.TOPIC, new AnActionListener() {
+      myConnection = ApplicationManager.getApplication().getMessageBus().simpleConnect();
+      myConnection.subscribe(AnActionListener.TOPIC, new AnActionListener() {
         @Override
         public void beforeActionPerformed(@NotNull AnAction action, @NotNull DataContext dataContext, @NotNull AnActionEvent event) {
           if (dataContext.getData(PlatformDataKeys.CONTEXT_COMPONENT) == SplitButton.this) {
@@ -246,9 +242,9 @@ public final class SplitButtonAction extends ActionGroup implements CustomCompon
     @Override
     public void removeNotify() {
       super.removeNotify();
-      if (myDisposable != null) {
-        Disposer.dispose(myDisposable);
-        myDisposable = null;
+      if (myConnection != null) {
+        myConnection.disconnect();
+        myConnection = null;
       }
     }
 
index bca3abdb6f7ea42f0e7ff27132aa5e7f54675f59..4ebfb8a8ab1f30b3a5e6ff74be5eaa284063e608 100644 (file)
@@ -326,7 +326,7 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application
   public final void load(@Nullable String configPath) {
     @SuppressWarnings("unchecked")
     List<IdeaPluginDescriptorImpl> plugins = (List<IdeaPluginDescriptorImpl>)PluginManagerCore.getLoadedPlugins();
-    registerComponents(plugins, false);
+    registerComponents(plugins);
     ApplicationLoader.initConfigurationStore(this, configPath);
     Executor executor = ApplicationLoader.createExecutorToPreloadServices();
     preloadServices(plugins, executor, false).getSyncPreloadedServices().join();
index d6a9b0f77702faa4ddf1f8cd85c9d65dd41fd636..0c3d68a4623016bd396454b4c1563d111d6acd6e 100644 (file)
@@ -3,7 +3,6 @@ package com.intellij.openapi.command.impl;
 
 import com.intellij.ide.DataManager;
 import com.intellij.idea.ActionsBundle;
-import com.intellij.openapi.Disposable;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.application.Application;
 import com.intellij.openapi.application.ApplicationManager;
@@ -34,7 +33,6 @@ import com.intellij.openapi.wm.ex.WindowManagerEx;
 import com.intellij.psi.ExternalChangeAction;
 import com.intellij.util.ObjectUtils;
 import com.intellij.util.messages.MessageBus;
-import com.intellij.util.messages.MessageBusConnection;
 import gnu.trove.THashSet;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -45,7 +43,7 @@ import java.util.List;
 import java.util.*;
 
 // Android team doesn't want to use new mockito for now, so, class cannot be final
-public class UndoManagerImpl extends UndoManager implements Disposable {
+public class UndoManagerImpl extends UndoManager {
   private static final Logger LOG = Logger.getInstance(UndoManagerImpl.class);
 
   @TestOnly
@@ -55,7 +53,7 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
   private static final int COMMAND_TO_RUN_COMPACT = 20;
   private static final int FREE_QUEUES_LIMIT = 30;
 
-  @Nullable private final ProjectEx myProject;
+  private final @Nullable ProjectEx myProject;
 
   private CurrentEditorProvider myEditorProvider;
 
@@ -63,7 +61,6 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
   private final UndoRedoStacksHolder myRedoStacksHolder = new UndoRedoStacksHolder(false);
 
   private final CommandMerger myMerger;
-  private final MessageBusConnection myConnection;
 
   private CommandMerger myCurrentMerger;
   private Project myCurrentActionProject = DummyProject.getInstance();
@@ -93,32 +90,36 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
     myMerger = new CommandMerger(this);
 
     if (myProject != null && myProject.isDefault()) {
-      myConnection = null;
       return;
     }
 
     myEditorProvider = new FocusBasedCurrentEditorProvider();
 
     MessageBus messageBus = myProject == null ? ApplicationManager.getApplication().getMessageBus() : myProject.getMessageBus();
-    myConnection = messageBus.connect(this);
-    myConnection.subscribe(CommandListener.TOPIC, new CommandListener() {
+    messageBus.connect().subscribe(CommandListener.TOPIC, new CommandListener() {
       private boolean myStarted;
 
       @Override
       public void commandStarted(@NotNull CommandEvent event) {
-        if (myProject != null && myProject.isDisposed() || myStarted) return;
+        if (myProject != null && myProject.isDisposed() || myStarted) {
+          return;
+        }
         onCommandStarted(event.getProject(), event.getUndoConfirmationPolicy(), event.shouldRecordActionForOriginalDocument());
       }
 
       @Override
       public void commandFinished(@NotNull CommandEvent event) {
-        if (myProject != null && myProject.isDisposed() || myStarted) return;
+        if (myProject != null && myProject.isDisposed() || myStarted) {
+          return;
+        }
         onCommandFinished(event.getProject(), event.getCommandName(), event.getCommandGroupId());
       }
 
       @Override
       public void undoTransparentActionStarted() {
-        if (myProject != null && myProject.isDisposed()) return;
+        if (myProject != null && myProject.isDisposed()) {
+          return;
+        }
         if (!isInsideCommand()) {
           myStarted = true;
           onCommandStarted(myProject, UndoConfirmationPolicy.DEFAULT, true);
@@ -127,7 +128,9 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
 
       @Override
       public void undoTransparentActionFinished() {
-        if (myProject != null && myProject.isDisposed()) return;
+        if (myProject != null && myProject.isDisposed()) {
+          return;
+        }
         if (myStarted) {
           myStarted = false;
           onCommandFinished(myProject, "", null);
@@ -136,15 +139,10 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
     });
   }
 
-  @Nullable
-  public Project getProject() {
+  public @Nullable Project getProject() {
     return myProject;
   }
 
-  @Override
-  public void dispose() {
-  }
-
   public boolean isActive() {
     return Comparing.equal(myProject, myCurrentActionProject) || myProject == null && myCurrentActionProject.isDefault();
   }
@@ -153,8 +151,7 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
     return myCommandLevel > 0;
   }
 
-  @NotNull
-  private List<UndoProvider> getUndoProviders() {
+  private @NotNull List<UndoProvider> getUndoProviders() {
     return myProject == null ? UndoProvider.EP_NAME.getExtensionList() : UndoProvider.PROJECT_EP_NAME.getExtensionList(myProject);
   }
 
@@ -213,7 +210,6 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
     myCurrentMerger.mergeUndoConfirmationPolicy(undoConfirmationPolicy);
 
     myCommandLevel++;
-
   }
 
   private void commandFinished(String commandName, Object groupId) {
@@ -238,9 +234,11 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
   }
 
   private void addDocumentAsAffected(@NotNull DocumentReference documentReference) {
-    if (myCurrentMerger.hasChangesOf(documentReference, true)) return;
+    if (myCurrentMerger.hasChangesOf(documentReference, true)) {
+      return;
+    }
 
-    final DocumentReference[] refs = {documentReference};
+    DocumentReference[] refs = {documentReference};
     myCurrentMerger.addAction(new MentionOnlyUndoableAction(refs));
   }
 
@@ -263,7 +261,7 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
   }
 
   @Override
-  public void nonundoableActionPerformed(@NotNull final DocumentReference ref, final boolean isGlobal) {
+  public void nonundoableActionPerformed(final @NotNull DocumentReference ref, final boolean isGlobal) {
     ApplicationManager.getApplication().assertIsWriteThread();
     if (myProject != null && myProject.isDisposed()) return;
     undoableActionPerformed(new NonUndoableAction(ref, isGlobal));
@@ -272,10 +270,9 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
   @Override
   public void undoableActionPerformed(@NotNull UndoableAction action) {
     ApplicationManager.getApplication().assertIsWriteThread();
-    if (myProject != null && myProject.isDisposed()) return;
-    if (myConnection != null) myConnection.deliverImmediately();
-
-    if (myCurrentOperationState != OperationState.NONE) return;
+    if (myProject != null && myProject.isDisposed() || myCurrentOperationState != OperationState.NONE) {
+      return;
+    }
 
     if (myCommandLevel == 0) {
       LOG.assertTrue(action instanceof NonUndoableAction,
@@ -416,8 +413,7 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
     return getDocumentReferences(editor);
   }
 
-  @NotNull
-  static Set<DocumentReference> getDocumentReferences(@NotNull FileEditor editor) {
+  static @NotNull Set<DocumentReference> getDocumentReferences(@NotNull FileEditor editor) {
     Set<DocumentReference> result = new THashSet<>();
 
     if (editor instanceof DocumentReferenceProvider) {
@@ -436,25 +432,21 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
     return result;
   }
 
-  @NotNull
-  private UndoRedoStacksHolder getStackHolder(boolean isUndo) {
+  private @NotNull UndoRedoStacksHolder getStackHolder(boolean isUndo) {
     return isUndo ? myUndoStacksHolder : myRedoStacksHolder;
   }
 
-  @NotNull
   @Override
-  public Pair<String, String> getUndoActionNameAndDescription(FileEditor editor) {
+  public @NotNull Pair<String, String> getUndoActionNameAndDescription(FileEditor editor) {
     return getUndoOrRedoActionNameAndDescription(editor, true);
   }
 
-  @NotNull
   @Override
-  public Pair<String, String> getRedoActionNameAndDescription(FileEditor editor) {
+  public @NotNull Pair<String, String> getRedoActionNameAndDescription(FileEditor editor) {
     return getUndoOrRedoActionNameAndDescription(editor, false);
   }
 
-  @NotNull
-  private Pair<String, String> getUndoOrRedoActionNameAndDescription(@Nullable FileEditor editor, boolean undo) {
+  private @NotNull Pair<String, String> getUndoOrRedoActionNameAndDescription(@Nullable FileEditor editor, boolean undo) {
     String desc = isUndoOrRedoAvailable(editor, undo) ? doFormatAvailableUndoRedoAction(editor, undo) : null;
     if (desc == null) desc = "";
     String shortActionName = StringUtil.first(desc, 30, true);
@@ -471,8 +463,7 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
                              : ActionsBundle.message("action.redo.description", desc)).trim());
   }
 
-  @Nullable
-  private String doFormatAvailableUndoRedoAction(FileEditor editor, boolean isUndo) {
+  private @Nullable String doFormatAvailableUndoRedoAction(FileEditor editor, boolean isUndo) {
     Collection<DocumentReference> refs = getDocRefs(editor);
     if (refs == null) return null;
     if (isUndo && myMerger.isUndoAvailable(refs)) return myMerger.getCommandName();
@@ -493,8 +484,7 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
     return ++myCommandTimestamp;
   }
 
-  @NotNull
-  private static Document getOriginal(@NotNull Document document) {
+  private static @NotNull Document getOriginal(@NotNull Document document) {
     Document result = document.getUserData(ORIGINAL_DOCUMENT);
     return result == null ? document : result;
   }
@@ -545,8 +535,7 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
     return Math.max(myUndoStacksHolder.getLastCommandTimestamp(ref), myRedoStacksHolder.getLastCommandTimestamp(ref));
   }
 
-  @NotNull
-  private Collection<DocumentReference> collectReferencesWithoutMergers() {
+  private @NotNull Collection<DocumentReference> collectReferencesWithoutMergers() {
     Set<DocumentReference> result = new THashSet<>();
     myUndoStacksHolder.collectAllAffectedDocuments(result);
     myRedoStacksHolder.collectAllAffectedDocuments(result);
@@ -567,8 +556,7 @@ public class UndoManagerImpl extends UndoManager implements Disposable {
   }
 
   @TestOnly
-  @NotNull
-  public CurrentEditorProvider getEditorProvider() {
+  public @NotNull CurrentEditorProvider getEditorProvider() {
     return myEditorProvider;
   }
 
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ProjectDisposeAwareDocumentListener.java b/platform/platform-impl/src/com/intellij/openapi/editor/ProjectDisposeAwareDocumentListener.java
new file mode 100644 (file)
index 0000000..1cb51ea
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.openapi.editor;
+
+import com.intellij.openapi.editor.event.DocumentEvent;
+import com.intellij.openapi.editor.event.DocumentListener;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.impl.ProjectManagerImpl;
+import org.jetbrains.annotations.NotNull;
+
+// PsiChangeHandler is used in a light tests, where project is not disposed on close, so, listener is not removed on close,
+// so, we have to check isDisposed explicitly
+public final class ProjectDisposeAwareDocumentListener implements DocumentListener {
+  private final Project project;
+  private final DocumentListener listener;
+
+  public static @NotNull DocumentListener create(@NotNull Project project, @NotNull DocumentListener listener) {
+    //noinspection TestOnlyProblems
+    return ProjectManagerImpl.isLight(project) ? new ProjectDisposeAwareDocumentListener(project, listener) : listener;
+  }
+
+  private ProjectDisposeAwareDocumentListener(@NotNull Project project, @NotNull DocumentListener listener) {
+    this.project = project;
+    this.listener = listener;
+  }
+
+  @Override
+  public void beforeDocumentChange(@NotNull DocumentEvent event) {
+    if (!project.isDisposed()) {
+      listener.beforeDocumentChange(event);
+    }
+  }
+
+  @Override
+  public void documentChanged(@NotNull DocumentEvent event) {
+    if (!project.isDisposed()) {
+      listener.documentChanged(event);
+    }
+  }
+
+  @Override
+  public void bulkUpdateStarting(@NotNull Document document) {
+    if (!project.isDisposed()) {
+      listener.bulkUpdateStarting(document);
+    }
+  }
+
+  @Override
+  public void bulkUpdateFinished(@NotNull Document document) {
+    if (!project.isDisposed()) {
+      listener.bulkUpdateFinished(document);
+    }
+  }
+}
index 8b6beed134f252d68d6c1c1888eccc623f0c99ad..e492ca002e009ac50aa716d258e9dd3cc615f5e7 100644 (file)
@@ -55,7 +55,7 @@ import com.intellij.ui.scale.JBUIScale;
 import com.intellij.util.Alarm;
 import com.intellij.util.IJSwingUtilities;
 import com.intellij.util.containers.ContainerUtil;
-import com.intellij.util.messages.MessageBus;
+import com.intellij.util.messages.MessageBusConnection;
 import com.intellij.util.ui.*;
 import com.intellij.util.ui.update.MergingUpdateQueue;
 import com.intellij.util.ui.update.Update;
@@ -297,18 +297,19 @@ public final class EditorMarkupModelImpl extends MarkupModelImpl
 
     ((JBScrollPane)myEditor.getScrollPane()).setStatusComponent(statusPanel);
 
-    MessageBus bus = ApplicationManager.getApplication().getMessageBus();
-
-    bus.connect(resourcesDisposable).subscribe(AnActionListener.TOPIC, new AnActionListener() {
+    MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect(resourcesDisposable);
+    connection.subscribe(AnActionListener.TOPIC, new AnActionListener() {
       @Override
       public void beforeActionPerformed(@NotNull AnAction action, @NotNull DataContext dataContext, @NotNull AnActionEvent event) {
-        if (action instanceof HintManagerImpl.ActionToIgnore) return;
+        if (action instanceof HintManagerImpl.ActionToIgnore) {
+          return;
+        }
         myPopupManager.hidePopup();
       }
     });
 
-    bus.connect(resourcesDisposable).subscribe(LafManagerListener.TOPIC, source -> myPopupManager.updateUI());
-    bus.connect(resourcesDisposable).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() {
+    connection.subscribe(LafManagerListener.TOPIC, source -> myPopupManager.updateUI());
+    connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() {
       @Override
       public void selectionChanged(@NotNull FileEditorManagerEvent event) {
         showToolbar = EditorSettingsExternalizable.getInstance().isShowInspectionWidget() &&
index 8e237bb59aece82defa9e8f5bd63a26cb4adbe18..9170c2a9b5d43d07118c8a9390e55f2e8ff65f98 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 package com.intellij.openapi.fileEditor.impl;
 
 import com.intellij.ide.ui.UISettings;
@@ -164,8 +164,7 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Dispos
     return FileEditorManagerEx.getInstanceEx(myProject);
   }
 
-  @NotNull
-  private PersistentHashMap<String, Long> initRecentFilesTimestampMap(@NotNull Project project) {
+  private @NotNull PersistentHashMap<String, Long> initRecentFilesTimestampMap(@NotNull Project project) {
     Path file = ProjectUtil.getProjectCachePath(project, "recentFilesTimeStamps.dat");
 
     PersistentHashMap<String, Long> map;
@@ -188,8 +187,7 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Dispos
     return map;
   }
 
-  @NotNull
-  private static PersistentHashMap<String, Long> createMap(Path file) throws IOException {
+  private static @NotNull PersistentHashMap<String, Long> createMap(Path file) throws IOException {
     return new PersistentHashMap<>(file,
                                    EnumeratorStringDescriptor.INSTANCE,
                                    EnumeratorLongDescriptor.INSTANCE,
@@ -229,9 +227,10 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Dispos
     }
   }
 
-  public static class RecentlyChangedFilesState {
+  static class RecentlyChangedFilesState {
     // don't make it private, see: IDEA-130363 Recently Edited Files list should survive restart
-    @SuppressWarnings("WeakerAccess") public List<String> CHANGED_PATHS = new ArrayList<>();
+    @SuppressWarnings("WeakerAccess")
+    public List<String> CHANGED_PATHS = new ArrayList<>();
   }
 
   @Override
@@ -264,8 +263,7 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Dispos
     myChangedFilesInCurrentCommand.clear();
   }
 
-  @Nullable
-  private PlaceInfo getCurrentPlaceInfo() {
+  private @Nullable PlaceInfo getCurrentPlaceInfo() {
     FileEditorWithProvider selectedEditorWithProvider = getSelectedEditor();
     if (selectedEditorWithProvider == null) {
       return null;
@@ -273,8 +271,7 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Dispos
     return createPlaceInfo(selectedEditorWithProvider.getFileEditor(), selectedEditorWithProvider.getProvider());
   }
 
-  @Nullable
-  private static PlaceInfo getPlaceInfoFromFocus() {
+  private static @Nullable PlaceInfo getPlaceInfoFromFocus() {
     FileEditor fileEditor = new FocusBasedCurrentEditorProvider().getCurrentEditor();
     if (fileEditor instanceof TextEditor && fileEditor.isValid()) {
       VirtualFile file = fileEditor.getFile();
@@ -479,8 +476,7 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Dispos
   }
 
   @Override
-  @NotNull
-  public List<PlaceInfo> getBackPlaces() {
+  public @NotNull List<PlaceInfo> getBackPlaces() {
     return ContainerUtil.immutableList(myBackPlaces);
   }
 
@@ -582,14 +578,15 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Dispos
   /**
    * @return currently selected FileEditor or null.
    */
-  @Nullable
-  protected FileEditorWithProvider getSelectedEditor() {
+  protected @Nullable FileEditorWithProvider getSelectedEditor() {
     FileEditorManagerEx editorManager = getFileEditorManager();
     VirtualFile file = editorManager != null ? editorManager.getCurrentFile() : null;
     return file == null ? null : editorManager.getSelectedEditorWithProvider(file);
   }
 
-  protected PlaceInfo createPlaceInfo(@NotNull final FileEditor fileEditor, final FileEditorProvider fileProvider) {
+  // used by Rider
+  @SuppressWarnings("WeakerAccess")
+  protected PlaceInfo createPlaceInfo(final @NotNull FileEditor fileEditor, final FileEditorProvider fileProvider) {
     if (!fileEditor.isValid()) {
       return null;
     }
@@ -603,8 +600,7 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Dispos
                          System.currentTimeMillis());
   }
 
-  @Nullable
-  private static RangeMarker getCaretPosition(@NotNull FileEditor fileEditor) {
+  private static @Nullable RangeMarker getCaretPosition(@NotNull FileEditor fileEditor) {
     if (!(fileEditor instanceof TextEditor)) {
       return null;
     }
@@ -647,7 +643,7 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Dispos
     private final FileEditorState myNavigationState;
     private final String myEditorTypeId;
     private final Reference<EditorWindow> myWindow;
-    @Nullable private final RangeMarker myCaretPosition;
+    private final @Nullable RangeMarker myCaretPosition;
     private final long myTimeStamp;
 
     public PlaceInfo(@NotNull VirtualFile file,
@@ -681,18 +677,15 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Dispos
       return myWindow.get();
     }
 
-    @NotNull
-    public FileEditorState getNavigationState() {
+    public @NotNull FileEditorState getNavigationState() {
       return myNavigationState;
     }
 
-    @NotNull
-    public VirtualFile getFile() {
+    public @NotNull VirtualFile getFile() {
       return myFile;
     }
 
-    @NotNull
-    public String getEditorTypeId() {
+    public @NotNull String getEditorTypeId() {
       return myEditorTypeId;
     }
 
@@ -701,8 +694,7 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Dispos
       return getFile().getName() + " " + getNavigationState();
     }
 
-    @Nullable
-    public RangeMarker getCaretPosition() {
+    public @Nullable RangeMarker getCaretPosition() {
       return myCaretPosition;
     }
 
@@ -734,7 +726,8 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Dispos
    * {@link RecentPlacesListener} listens recently viewed or changed place adding and removing events.
    */
   public interface RecentPlacesListener {
-    Topic<RecentPlacesListener> TOPIC = Topic.create("RecentPlacesListener", RecentPlacesListener.class);
+    @Topic.ProjectLevel
+    Topic<RecentPlacesListener> TOPIC = new Topic<>(RecentPlacesListener.class, Topic.BroadcastDirection.NONE);
 
     /**
      * Fires on a new place info adding into {@link #myChangePlaces} or {@link #myBackPlaces} infos list
index 4d155b1778589842cd59d6207e55b5e095ae73e7..ff23a3d08d60798aee659bdf868f85b15af48aab 100644 (file)
@@ -44,7 +44,6 @@ import com.intellij.ui.GuiUtils;
 import com.intellij.util.*;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.io.URLUtil;
-import com.intellij.util.messages.MessageBus;
 import com.intellij.util.messages.MessageBusConnection;
 import gnu.trove.THashMap;
 import gnu.trove.THashSet;
@@ -109,7 +108,6 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
     }
   }
 
-  private final MessageBus myMessageBus;
   private final Map<String, StandardFileType> myStandardFileTypes = new LinkedHashMap<>();
   @NonNls
   private static final String[] FILE_TYPES_WITH_PREDEFINED_EXTENSIONS = {"JSP", "JSPX", "DTD", "HTML", "Properties", "XHTML"};
@@ -118,7 +116,6 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
   static final String FILE_SPEC = "filetypes";
 
   public FileTypeManagerImpl() {
-    myMessageBus = ApplicationManager.getApplication().getMessageBus();
     mySchemeManager = SchemeManagerFactory.getInstance().create(FILE_SPEC, new NonLazySchemeProcessor<FileType, AbstractFileType>() {
       @NotNull
       @Override
@@ -798,7 +795,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
   @Override
   public void fireBeforeFileTypesChanged() {
     FileTypeEvent event = new FileTypeEvent(this, null, null);
-    myMessageBus.syncPublisher(TOPIC).beforeFileTypesChanged(event);
+    ApplicationManager.getApplication().getMessageBus().syncPublisher(TOPIC).beforeFileTypesChanged(event);
   }
 
 
@@ -809,14 +806,14 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
 
   private void fireFileTypesChanged(@Nullable FileType addedFileType, @Nullable FileType removedFileType) {
     myDetectionService.clearCaches();
-    myMessageBus.syncPublisher(TOPIC).fileTypesChanged(new FileTypeEvent(this, addedFileType, removedFileType));
+    ApplicationManager.getApplication().getMessageBus().syncPublisher(TOPIC).fileTypesChanged(new FileTypeEvent(this, addedFileType, removedFileType));
   }
 
   private final Map<FileTypeListener, MessageBusConnection> myAdapters = new HashMap<>();
 
   @Override
   public void addFileTypeListener(@NotNull FileTypeListener listener) {
-    final MessageBusConnection connection = myMessageBus.connect();
+    final MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect();
     connection.subscribe(TOPIC, listener);
     myAdapters.put(listener, connection);
   }
index cef00bcc1151f172ea6f9eb38231a0b9443b4b4b..8c0cc3fbe873dde06539ec9c05fcfd93822e45d6 100644 (file)
@@ -253,7 +253,7 @@ class KeymapManagerImpl : KeymapManagerEx(), PersistentStateComponent<Element> {
     listeners.removeAll { it is WeakKeymapManagerListener && it.isWrapped(listenerToRemove) }
   }
 
-  fun fireShortcutChanged(keymap: Keymap, actionId: String) {
+  internal fun fireShortcutChanged(keymap: Keymap, actionId: String) {
     ApplicationManager.getApplication().messageBus.syncPublisher(KeymapManagerListener.TOPIC).shortcutChanged(keymap, actionId)
   }
 }
index 99ace8df7765ce2a767c4b25c57789e6c1405313..acbbec75042c01505533d461d86b071e61e7be29 100644 (file)
@@ -22,7 +22,6 @@ import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.project.ProjectStoreOwner;
 import com.intellij.serviceContainer.ComponentManagerImpl;
 import com.intellij.util.messages.MessageBus;
-import com.intellij.util.messages.impl.MessageBusImpl;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.annotations.SystemIndependent;
@@ -43,6 +42,11 @@ final class DefaultProject extends UserDataHolderBase implements Project, Projec
       LOG.assertTrue(!ApplicationManager.getApplication().isDisposed(), "Application is being disposed!");
       return new ProjectImpl() {
         @Override
+        public boolean isParentLazyListenersIgnored() {
+          return true;
+        }
+
+        @Override
         public boolean isDefault() {
           return true;
         }
@@ -57,9 +61,8 @@ final class DefaultProject extends UserDataHolderBase implements Project, Projec
           return DefaultProject.this;
         }
 
-        @Nullable
         @Override
-        public String activityNamePrefix() {
+        public @Nullable String activityNamePrefix() {
           // exclude from measurement because default project initialization is not a sequential activity
           // (so, complicates timeline because not applicable)
           // for now we don't measure default project initialization at all, because it takes only ~10 ms
@@ -77,7 +80,7 @@ final class DefaultProject extends UserDataHolderBase implements Project, Projec
           registerServiceInstance(Project.class, DefaultProject.this, ComponentManagerImpl.getFakeCorePluginDescriptor());
 
           //noinspection unchecked
-          registerComponents((List<IdeaPluginDescriptorImpl>)PluginManagerCore.getLoadedPlugins(), false);
+          registerComponents((List<IdeaPluginDescriptorImpl>)PluginManagerCore.getLoadedPlugins());
           createComponents(null);
           Disposer.register(DefaultProject.this, this);
         }
@@ -98,14 +101,6 @@ final class DefaultProject extends UserDataHolderBase implements Project, Projec
         public int hashCode() {
           return DEFAULT_HASH_CODE;
         }
-
-        @NotNull
-        @Override
-        protected synchronized MessageBusImpl getOrCreateMessageBusUnderLock() {
-          MessageBusImpl messageBus = super.getOrCreateMessageBusUnderLock();
-          messageBus.setIgnoreParentLazyListeners(true);
-          return messageBus;
-        }
       };
     }
 
@@ -172,8 +167,7 @@ final class DefaultProject extends UserDataHolderBase implements Project, Projec
     Disposer.dispose(myDelegate);
   }
 
-  @NotNull
-  private ProjectEx getDelegate() {
+  private @NotNull ProjectEx getDelegate() {
     return myDelegate.get();
   }
 
@@ -183,8 +177,7 @@ final class DefaultProject extends UserDataHolderBase implements Project, Projec
 
   // delegates
   @Override
-  @NotNull
-  public String getName() {
+  public @NotNull String getName() {
     return ProjectImpl.TEMPLATE_PROJECT_NAME;
   }
 
@@ -195,34 +188,27 @@ final class DefaultProject extends UserDataHolderBase implements Project, Projec
   }
 
   @Override
-  @Nullable
-  @SystemIndependent
-  public String getBasePath() {
+  public @Nullable @SystemIndependent String getBasePath() {
     return null;
   }
 
   @Override
-  @Nullable
-  public VirtualFile getProjectFile() {
+  public @Nullable VirtualFile getProjectFile() {
     return null;
   }
 
   @Override
-  @Nullable
-  @SystemIndependent
-  public String getProjectFilePath() {
+  public @Nullable @SystemIndependent String getProjectFilePath() {
     return null;
   }
 
   @Override
-  @Nullable
-  public VirtualFile getWorkspaceFile() {
+  public @Nullable VirtualFile getWorkspaceFile() {
     return null;
   }
 
   @Override
-  @NotNull
-  public String getLocationHash() {
+  public @NotNull String getLocationHash() {
     return getName();
   }
 
@@ -254,9 +240,8 @@ final class DefaultProject extends UserDataHolderBase implements Project, Projec
   }
 
   @SuppressWarnings("deprecation")
-  @NotNull
   @Override
-  public <T> List<T> getComponentInstancesOfType(@NotNull Class<T> baseClass, boolean createIfNotYet) {
+  public @NotNull <T> List<T> getComponentInstancesOfType(@NotNull Class<T> baseClass, boolean createIfNotYet) {
     return getDelegate().getComponentInstancesOfType(baseClass, createIfNotYet);
   }
 
@@ -265,9 +250,8 @@ final class DefaultProject extends UserDataHolderBase implements Project, Projec
     return getDelegate().getService(serviceClass);
   }
 
-  @Nullable
   @Override
-  public <T> T getServiceIfCreated(@NotNull Class<T> serviceClass) {
+  public @Nullable <T> T getServiceIfCreated(@NotNull Class<T> serviceClass) {
     return getDelegate().getServiceIfCreated(serviceClass);
   }
 
@@ -277,20 +261,17 @@ final class DefaultProject extends UserDataHolderBase implements Project, Projec
   }
 
   @Override
-  @NotNull
-  public PicoContainer getPicoContainer() {
+  public @NotNull PicoContainer getPicoContainer() {
     return getDelegate().getPicoContainer();
   }
 
-  @NotNull
   @Override
-  public ExtensionsArea getExtensionArea() {
+  public @NotNull ExtensionsArea getExtensionArea() {
     return getDelegate().getExtensionArea();
   }
 
   @Override
-  @NotNull
-  public MessageBus getMessageBus() {
+  public @NotNull MessageBus getMessageBus() {
     return getDelegate().getMessageBus();
   }
 
@@ -300,14 +281,12 @@ final class DefaultProject extends UserDataHolderBase implements Project, Projec
   }
 
   @Override
-  @NotNull
-  public Condition<?> getDisposed() {
+  public @NotNull Condition<?> getDisposed() {
     return ApplicationManager.getApplication().getDisposed();
   }
 
-  @NotNull
   @Override
-  public IComponentStore getComponentStore() {
+  public @NotNull IComponentStore getComponentStore() {
     return ((ProjectStoreOwner)getDelegate()).getComponentStore();
   }
 }
index b625821e4c86bbc59efef998a29a6632a2e73b9e..4d3352dd529c771c7f2239c0d2d142830fd28297 100644 (file)
@@ -39,6 +39,7 @@ import com.intellij.psi.impl.DebugUtil;
 import com.intellij.serviceContainer.ComponentManagerImpl;
 import com.intellij.util.PathUtil;
 import com.intellij.util.TimedReference;
+import com.intellij.util.messages.impl.MessageBusEx;
 import org.jetbrains.annotations.*;
 
 import javax.swing.*;
@@ -130,6 +131,15 @@ public class ProjectImpl extends ComponentManagerImpl implements ProjectEx, Proj
       }
     }
 
+    // Must be not only on temporarilyDisposed = true, but also on temporarilyDisposed = false,
+    // because events fired for temporarilyDisposed project between project close and project open and it can lead to cache population.
+    // Message bus implementation can be complicated to add owner.isDisposed check before getting subscribers, but as bus is a very important subsystem,
+    // better to not add any non-production logic
+
+    // light project is not disposed, so, subscriber cache contains handlers that will handle events for a temporarily disposed project,
+    // so, we clear subscriber cache. `isDisposed` for project returns `true` if `temporarilyDisposed`, so, handler will be not added.
+    ((MessageBusEx)getMessageBus()).clearAllSubscriberCache();
+
     temporarilyDisposed = value;
   }
 
index 4d88ff8e997cc852140f4f20b02f0d86935ad3b4..ccc31c67d2b9fd7890a0cc02fd5c790fd74a5904 100644 (file)
@@ -54,7 +54,6 @@ import com.intellij.util.ArrayUtil;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.containers.UnsafeWeakList;
 import com.intellij.util.messages.MessageBusConnection;
-import com.intellij.util.messages.impl.MessageBusImpl;
 import com.intellij.util.ref.GCUtil;
 import com.intellij.util.ui.UIUtil;
 import org.jetbrains.annotations.*;
@@ -308,6 +307,7 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable {
     }
 
     Activity activity = StartUpMeasurer.startMainActivity("project before loaded callbacks");
+    //noinspection deprecation
     ApplicationManager.getApplication().getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).beforeProjectLoaded(project);
     activity.end();
 
@@ -622,9 +622,6 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable {
       if (!projectImpl.isTemporarilyDisposed()) {
         projectImpl.disposeEarlyDisposable();
         projectImpl.setTemporarilyDisposed(true);
-        // light project is not disposed, so, subscriber cache contains handlers that will handle events for a temporarily disposed project,
-        // so, we clear subscriber cache. `isDisposed` for project returns `true` if `temporarilyDisposed`, so, handler will be not added.
-        ((MessageBusImpl)projectImpl.getMessageBus()).clearAllSubscriberCache();
         removeFromOpened(project);
         updateTheOnlyProjectField();
         return true;
index 61c38580f3aaf86a83305a977ce78148a9b8057b..573f779ecf72a8008b69adbb9b12b5b9eb41b6ed 100644 (file)
@@ -26,7 +26,7 @@ class ProjectLoadHelper {
       var activity = createActivity(project) { "project ${Activities.REGISTER_COMPONENTS_SUFFIX}" }
       //  at this point of time plugins are already loaded by application - no need to pass indicator to getLoadedPlugins call
       @Suppress("UNCHECKED_CAST")
-      project.registerComponents(PluginManagerCore.getLoadedPlugins() as List<IdeaPluginDescriptorImpl>, notifyListeners = false)
+      project.registerComponents(PluginManagerCore.getLoadedPlugins() as List<IdeaPluginDescriptorImpl>)
 
       activity = activity?.endAndStart("projectComponentRegistered")
       runHandler(ProjectServiceContainerCustomizer.getEp()) {
index 6d1ce51fe98aa4b95531b0c7bba48c13e9bbe2a6..141da3f24e8e69af4f0996f7ee19d80cde1e24c9 100644 (file)
@@ -11,7 +11,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
 @ApiStatus.Internal
-public class CachedFileType {
+public final class CachedFileType {
   private static final ConcurrentMap<FileType, CachedFileType> ourInterner = new ConcurrentHashMap<>();
 
   private @Nullable FileType fileType;
@@ -20,42 +20,30 @@ public class CachedFileType {
     this.fileType = fileType;
   }
 
-  @Nullable
-  FileType getUpToDateOrNull() {
+  @Nullable FileType getUpToDateOrNull() {
     return fileType;
   }
 
   static CachedFileType forType(@NotNull FileType fileType) {
-    CachedFileType cached = ourInterner.get(fileType);
-    return cached != null ? cached : computeSynchronized(fileType);
-  }
-
-  private static CachedFileType computeSynchronized(FileType fileType) {
-    synchronized (ourInterner) {
-      return ourInterner.computeIfAbsent(fileType, CachedFileType::new);
-    }
+    return ourInterner.computeIfAbsent(fileType, CachedFileType::new);
   }
 
   public static void clearCache() {
-    synchronized (ourInterner) {
-      for (CachedFileType value : ourInterner.values()) {
-        // clear references to file types to aid plugin unloading
-        value.fileType = null;
-      }
-      ourInterner.clear();
-    }
+    ourInterner.forEach((type, cachedType) -> {
+      // clear references to file types to aid plugin unloading
+      cachedType.fileType = null;
+    });
+    ourInterner.clear();
   }
 
-  public static void remove(FileType type) {
-    synchronized (ourInterner) {
-      CachedFileType cached = ourInterner.get(type);
-      if (cached != null) {
-        cached.fileType = null;
-      }
+  public static void remove(@NotNull FileType type) {
+    CachedFileType cached = ourInterner.remove(type);
+    if (cached != null) {
+      cached.fileType = null;
     }
   }
 
-  public static class PsiListener implements PsiModificationTracker.Listener {
+  static final class PsiListener implements PsiModificationTracker.Listener {
     @Override
     public void modificationCountChanged() {
       clearCache();
index 963f17984b4885a7a47deaf333f8e1b3eb6de097..8ce42bb34fc99ce466e2c126ee88f41fde1d489b 100644 (file)
@@ -25,8 +25,7 @@ import java.awt.*;
 public abstract class EditorBasedWidget implements StatusBarWidget, FileEditorManagerListener {
   public static final String SWING_FOCUS_OWNER_PROPERTY = "focusOwner";
 
-  @NotNull
-  protected final Project myProject;
+  protected final @NotNull Project myProject;
 
   protected StatusBar myStatusBar;
   protected MessageBusConnection myConnection;
@@ -37,8 +36,7 @@ public abstract class EditorBasedWidget implements StatusBarWidget, FileEditorMa
     Disposer.register(project, this);
   }
 
-  @Nullable
-  protected Editor getEditor() {
+  protected @Nullable Editor getEditor() {
     Editor editor = StatusBarUtil.getCurrentTextEditor(myStatusBar);
     if (editor != null) {
       return editor;
@@ -56,8 +54,7 @@ public abstract class EditorBasedWidget implements StatusBarWidget, FileEditorMa
            WindowManager.getInstance().getStatusBar(editor.getComponent(), editor.getProject()) == myStatusBar;
   }
 
-  @Nullable
-  Component getFocusedComponent() {
+  @Nullable Component getFocusedComponent() {
     Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
     if (focusOwner == null) {
       IdeFocusManager focusManager = IdeFocusManager.getInstance(myProject);
@@ -69,23 +66,20 @@ public abstract class EditorBasedWidget implements StatusBarWidget, FileEditorMa
     return focusOwner;
   }
 
-  @Nullable
-  Editor getFocusedEditor() {
+  @Nullable Editor getFocusedEditor() {
     Component component = getFocusedComponent();
     Editor editor = component instanceof EditorComponentImpl ? ((EditorComponentImpl)component).getEditor() : getEditor();
     return editor != null && !editor.isDisposed() ? editor : null;
   }
 
-  @Nullable
-  protected VirtualFile getSelectedFile() {
-    final Editor editor = getEditor();
+  protected @Nullable VirtualFile getSelectedFile() {
+    Editor editor = getEditor();
     if (editor == null) return null;
     Document document = editor.getDocument();
     return FileDocumentManager.getInstance().getFile(document);
   }
 
-  @NotNull
-  protected final Project getProject() {
+  protected final @NotNull Project getProject() {
     return myProject;
   }
 
@@ -97,7 +91,9 @@ public abstract class EditorBasedWidget implements StatusBarWidget, FileEditorMa
     myStatusBar = statusBar;
     Disposer.register(myStatusBar, this);
 
-    if (myProject.isDisposed()) return;
+    if (myProject.isDisposed()) {
+      return;
+    }
 
     myConnection = myProject.getMessageBus().connect(this);
     myConnection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, this);
@@ -107,7 +103,6 @@ public abstract class EditorBasedWidget implements StatusBarWidget, FileEditorMa
   public void dispose() {
     myDisposed = true;
     myStatusBar = null;
-    myConnection = null;
   }
 
   protected final boolean isDisposed() {
index 3ec74454c17f3047ce3dab30b1e578f5bf26eed6..b34e7fe4328f7f4c7d91b4e40a12fb062822a732 100644 (file)
@@ -13,8 +13,10 @@ import com.intellij.openapi.progress.TaskInfo;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.MessageType;
 import com.intellij.openapi.ui.popup.BalloonHandler;
-import com.intellij.openapi.util.*;
+import com.intellij.openapi.util.Disposer;
 import com.intellij.openapi.util.NlsContexts.PopupContent;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.SystemInfo;
 import com.intellij.openapi.util.registry.Registry;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.wm.*;
@@ -390,11 +392,10 @@ public final class IdeStatusBarImpl extends JComponent implements Accessible, St
   }
 
   @Override
-  public void setInfo(@Nullable final String s, @Nullable final String requestor) {
+  public void setInfo(@Nullable String s, @Nullable String requestor) {
     UIUtil.invokeLaterIfNeeded(() -> {
       if (myInfoAndProgressPanel != null) {
-        Couple<String> pair = myInfoAndProgressPanel.setText(s, requestor);
-        myInfo = pair.first;
+        myInfo = myInfoAndProgressPanel.setText(s, requestor).first;
       }
     });
   }
index f550bac0c8601575c4a27a06a35b815cbd7f9734..8b5de9a57c92f8dbdfcebdafee877b8d3eb2ec68 100644 (file)
@@ -18,8 +18,11 @@ import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
 import com.intellij.openapi.project.ProjectUtil;
 import com.intellij.openapi.ui.MessageType;
 import com.intellij.openapi.ui.popup.*;
-import com.intellij.openapi.util.*;
+import com.intellij.openapi.util.Disposer;
 import com.intellij.openapi.util.NlsContexts.PopupContent;
+import com.intellij.openapi.util.NotNullLazyValue;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.SystemInfo;
 import com.intellij.openapi.util.registry.Registry;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.wm.CustomStatusBarWidget;
@@ -54,17 +57,14 @@ import java.lang.ref.WeakReference;
 import java.util.List;
 import java.util.*;
 
-import static com.intellij.icons.AllIcons.Process.*;
-
-public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidget {
+public final class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidget {
   private final ProcessPopup myPopup;
 
   private final StatusPanel myInfoPanel = new StatusPanel();
   private final JPanel myRefreshAndInfoPanel = new JPanel();
   private final NotNullLazyValue<AsyncProcessIcon> myProgressIcon = new NotNullLazyValue<AsyncProcessIcon>() {
-    @NotNull
     @Override
-    protected AsyncProcessIcon compute() {
+    protected @NotNull AsyncProcessIcon compute() {
       AsyncProcessIcon icon = new AsyncProcessIcon("Background process");
       icon.setOpaque(false);
 
@@ -88,8 +88,8 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
 
   private final List<ProgressIndicatorEx> myOriginals = new ArrayList<>();
   private final List<TaskInfo> myInfos = new ArrayList<>();
-  private final Map<InlineProgressIndicator, ProgressIndicatorEx> myInline2Original = new HashMap<>();
-  private final MultiValuesMap<ProgressIndicatorEx, MyInlineProgressIndicator> myOriginal2Inlines = new MultiValuesMap<>();
+  private final Map<InlineProgressIndicator, ProgressIndicatorEx> myInlineToOriginal = new HashMap<>();
+  private final Map<ProgressIndicatorEx, Set<MyInlineProgressIndicator>> myOriginalToInlines = new HashMap<>();
 
   private final MergingUpdateQueue myUpdateQueue;
   private final Alarm myQueryAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
@@ -170,8 +170,7 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
   }
 
   @Override
-  @NotNull
-  public String ID() {
+  public @NotNull String ID() {
     return "InfoAndProgress";
   }
 
@@ -189,11 +188,11 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
     setRefreshVisible(false);
     synchronized (myOriginals) {
       restoreEmptyStatus();
-      for (InlineProgressIndicator indicator : myInline2Original.keySet()) {
+      for (InlineProgressIndicator indicator : myInlineToOriginal.keySet()) {
         Disposer.dispose(indicator);
       }
-      myInline2Original.clear();
-      myOriginal2Inlines.clear();
+      myInlineToOriginal.clear();
+      myOriginalToInlines.clear();
 
       myDisposed = true;
     }
@@ -255,7 +254,7 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
 
   private void removeProgress(@NotNull MyInlineProgressIndicator progress) {
     synchronized (myOriginals) {
-      if (!myInline2Original.containsKey(progress)) return; // already disposed
+      if (!myInlineToOriginal.containsKey(progress)) return; // already disposed
 
       final boolean last = myOriginals.size() == 1;
       final boolean beforeLast = myOriginals.size() == 2;
@@ -293,16 +292,23 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
   }
 
   private ProgressIndicatorEx removeFromMaps(@NotNull MyInlineProgressIndicator progress) {
-    final ProgressIndicatorEx original = myInline2Original.get(progress);
+    final ProgressIndicatorEx original = myInlineToOriginal.get(progress);
 
-    myInline2Original.remove(progress);
+    myInlineToOriginal.remove(progress);
     synchronized (myDirtyIndicators) {
       myDirtyIndicators.remove(progress);
     }
 
-    myOriginal2Inlines.remove(original, progress);
-    if (myOriginal2Inlines.get(original) == null) {
-      final int originalIndex = myOriginals.indexOf(original);
+    Set<MyInlineProgressIndicator> set = myOriginalToInlines.get(original);
+    if (set != null) {
+      set.remove(progress);
+      if (set.isEmpty()) {
+        set = null;
+        myOriginalToInlines.remove(original);
+      }
+    }
+    if (set == null) {
+      int originalIndex = myOriginals.indexOf(original);
       myOriginals.remove(originalIndex);
       myInfos.remove(originalIndex);
     }
@@ -370,8 +376,7 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
     repaint();
   }
 
-  @NotNull
-  private String getMultiProgressLinkText() {
+  private @NotNull String getMultiProgressLinkText() {
     ProgressIndicatorEx latest = getLatestProgress();
     String latestText = latest == null ? null : latest.getText();
     if (StringUtil.isEmptyOrSpaces(latestText) || myPopup.isShowing()) {
@@ -482,8 +487,7 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
     }, Balloon.Position.above);
   }
 
-  @Nullable
-  private static BalloonLayoutImpl getBalloonLayout(@NotNull JRootPane pane) {
+  private static @Nullable BalloonLayoutImpl getBalloonLayout(@NotNull JRootPane pane) {
     Component parent = UIUtil.findUltimateParent(pane);
     if (parent instanceof IdeFrame) {
       return (BalloonLayoutImpl)((IdeFrame)parent).getBalloonLayout();
@@ -491,8 +495,7 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
     return null;
   }
 
-  @NotNull
-  private static Component getAnchor(@NotNull JRootPane pane) {
+  private static @NotNull Component getAnchor(@NotNull JRootPane pane) {
     Component tabWrapper = UIUtil.findComponentOfType(pane, TabbedPaneWrapper.TabWrapper.class);
     if (tabWrapper != null && tabWrapper.isShowing()) return tabWrapper;
     EditorsSplitters splitters = UIUtil.findComponentOfType(pane, EditorsSplitters.class);
@@ -510,14 +513,14 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
     return pane != null && pane.isBottomSideToolWindowsVisible();
   }
 
-  public Couple<String> setText(@Nullable final String text, @Nullable final String requestor) {
+  public @NotNull Pair<String, String> setText(@Nullable String text, @Nullable String requestor) {
     if (StringUtil.isEmpty(text) && !Objects.equals(requestor, myCurrentRequestor) && !EventLog.LOG_REQUESTOR.equals(requestor)) {
-      return Couple.of(myInfoPanel.getText(), myCurrentRequestor);
+      return new Pair<>(myInfoPanel.getText(), myCurrentRequestor);
     }
 
     boolean logMode = myInfoPanel.updateText(EventLog.LOG_REQUESTOR.equals(requestor) ? "" : text);
     myCurrentRequestor = logMode ? EventLog.LOG_REQUESTOR : requestor;
-    return Couple.of(text, requestor);
+    return new Pair<>(text, requestor);
   }
 
   void setRefreshVisible(final boolean visible) {
@@ -605,19 +608,19 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
     }
   }
 
-  @NotNull
-  private MyInlineProgressIndicator createInlineDelegate(@NotNull TaskInfo info, @NotNull ProgressIndicatorEx original, boolean compact) {
-    Collection<MyInlineProgressIndicator> inlines = myOriginal2Inlines.get(original);
-    if (inlines != null) {
+  private @NotNull MyInlineProgressIndicator createInlineDelegate(@NotNull TaskInfo info, @NotNull ProgressIndicatorEx original, boolean compact) {
+    Set<MyInlineProgressIndicator> inlines = myOriginalToInlines.computeIfAbsent(original, __ -> new HashSet<>());
+    if (!inlines.isEmpty()) {
       for (MyInlineProgressIndicator eachInline : inlines) {
-        if (eachInline.isCompact() == compact) return eachInline;
+        if (eachInline.isCompact() == compact) {
+          return eachInline;
+        }
       }
     }
 
     MyInlineProgressIndicator inline = new MyInlineProgressIndicator(compact, info, original);
-
-    myInline2Original.put(inline, original);
-    myOriginal2Inlines.put(original, inline);
+    myInlineToOriginal.put(inline, original);
+    inlines.add(inline);
 
     if (compact) {
       inline.getComponent().addMouseListener(new MouseAdapter() {
@@ -699,11 +702,11 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
     }
   }
 
-  private class MyInlineProgressIndicator extends InlineProgressIndicator {
+  private final class MyInlineProgressIndicator extends InlineProgressIndicator {
     private ProgressIndicatorEx myOriginal;
     private PresentationModeProgressPanel myPresentationModeProgressPanel;
 
-    MyInlineProgressIndicator(final boolean compact, @NotNull TaskInfo task, @NotNull ProgressIndicatorEx original) {
+    MyInlineProgressIndicator(boolean compact, @NotNull TaskInfo task, @NotNull ProgressIndicatorEx original) {
       super(compact, task);
       myOriginal = original;
       original.addStateDelegate(this);
@@ -763,18 +766,17 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
     }
 
     private void showPauseIcons(InplaceButton button) {
-      setIcons(button, ProgressPauseSmall, ProgressPause, ProgressPauseSmallHover, ProgressPauseHover);
+      setIcons(button, AllIcons.Process.ProgressPauseSmall, AllIcons.Process.ProgressPause, AllIcons.Process.ProgressPauseSmallHover, AllIcons.Process.ProgressPauseHover);
     }
     private void showResumeIcons(InplaceButton button) {
-      setIcons(button, ProgressResumeSmall, ProgressResume, ProgressResumeSmallHover, ProgressResumeHover);
+      setIcons(button, AllIcons.Process.ProgressResumeSmall, AllIcons.Process.ProgressResume, AllIcons.Process.ProgressResumeSmallHover, AllIcons.Process.ProgressResumeHover);
     }
 
     private void setIcons(InplaceButton button, Icon compactRegular, Icon regular, Icon compactHovered, Icon hovered) {
       button.setIcons(isCompact() ? compactRegular : regular, null, isCompact() ? compactHovered : hovered);
     }
 
-    @Nullable
-    private ProgressSuspender getSuspender() {
+    private @Nullable ProgressSuspender getSuspender() {
       ProgressIndicatorEx original = myOriginal;
       return original == null ? null : ProgressSuspender.getSuspender(original);
     }
@@ -792,7 +794,7 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
     }
 
     @Override
-    public void finish(@NotNull final TaskInfo task) {
+    public void finish(final @NotNull TaskInfo task) {
       super.finish(task);
       queueRunningUpdate(() -> removeProgress(this));
     }
@@ -817,7 +819,7 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
     }
 
     @Override
-    protected void queueRunningUpdate(@NotNull final Runnable update) {
+    protected void queueRunningUpdate(final @NotNull Runnable update) {
       myUpdateQueue.queue(new Update(new Object(), false, 0) {
         @Override
         public void run() {
@@ -850,10 +852,9 @@ public class InfoAndProgressPanel extends JPanel implements CustomStatusBarWidge
     myQueryAlarm.addRequest(this::runQuery, 2000);
   }
 
-  @NotNull
-  private Set<InlineProgressIndicator> getCurrentInlineIndicators() {
+  private @NotNull Set<InlineProgressIndicator> getCurrentInlineIndicators() {
     synchronized (myOriginals) {
-      return myInline2Original.keySet();
+      return myInlineToOriginal.keySet();
     }
   }
 }
index bda6794ff4d71442fe05b58e7f16fef807bfb58c..1befad759d78c21f67b9e6b0e135f9904281a63a 100644 (file)
     <listener class="com.intellij.notification.impl.MacEventReader" topic="com.intellij.notification.Notifications" os="mac" activeInHeadlessMode="false"/>
 
     <listener class="com.intellij.internal.performance.LatenciometerListener" topic="com.intellij.openapi.editor.actionSystem.LatencyListener"/>
+
+    <listener class="com.intellij.openapi.vfs.newvfs.impl.CachedFileType$PsiListener" topic="com.intellij.psi.util.PsiModificationTracker$Listener"/>
   </applicationListeners>
   <projectListeners>
-    <listener class="com.intellij.openapi.vfs.newvfs.impl.CachedFileType$PsiListener" topic="com.intellij.psi.util.PsiModificationTracker$Listener"/>
     <listener class="com.intellij.execution.lineMarker.RunnableStatusListener" topic="com.intellij.codeInsight.daemon.DaemonCodeAnalyzer$DaemonListener"/>
     <listener class="com.intellij.internal.statistic.collectors.fus.fileTypes.FileTypeUsageCounterCollector$MyFileEditorManagerListener" topic="com.intellij.openapi.fileEditor.FileEditorManagerListener"/>
 
index 635351aacbe25545525aca1268305c36e3d274a1..669fcb007bcfbe2d6980814b339e558f53d568e4 100644 (file)
@@ -49,7 +49,7 @@ class JBNavigateCommandTest {
 
   @Rule
   @JvmField
-  val busConnection = RecentProjectManagerListenerRule()
+  internal val busConnection = RecentProjectManagerListenerRule()
 
   fun getTestDataPath(): String {
     return "${PlatformTestUtil.getPlatformTestDataPath()}/commands/navigate/"
index 84f00ff34d404832c2d0d78620c1c34ab2ab159e..f5ab8908b787c3eeaa95c8767c7791581d50a02d 100644 (file)
@@ -6,11 +6,11 @@ import com.intellij.openapi.application.ApplicationManager
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.project.ProjectManager
 import com.intellij.openapi.project.ex.ProjectManagerEx
-import com.intellij.openapi.util.Disposer
 import com.intellij.testFramework.*
 import com.intellij.testFramework.assertions.Assertions.assertThat
 import com.intellij.util.PathUtil
 import com.intellij.util.containers.ContainerUtil
+import com.intellij.util.messages.SimpleMessageBusConnection
 import org.jdom.JDOMException
 import org.junit.ClassRule
 import org.junit.Rule
@@ -32,7 +32,7 @@ class RecentProjectsTest {
 
   @Rule
   @JvmField
-  val busConnection = RecentProjectManagerListenerRule()
+  internal val busConnection = RecentProjectManagerListenerRule()
 
   @Rule
   @JvmField
@@ -151,16 +151,16 @@ class RecentProjectsTest {
   }
 }
 
-class RecentProjectManagerListenerRule : ExternalResource() {
-  private val disposable = Disposer.newDisposable()
+internal class RecentProjectManagerListenerRule : ExternalResource() {
+  private var connection: SimpleMessageBusConnection? = null
 
   override fun before() {
-    val connection = ApplicationManager.getApplication().messageBus.connect()
-    connection.subscribe(ProjectManager.TOPIC, RecentProjectsManagerBase.MyProjectListener())
-    connection.subscribe(AppLifecycleListener.TOPIC, RecentProjectsManagerBase.MyAppLifecycleListener())
+    connection = ApplicationManager.getApplication().messageBus.simpleConnect()
+    connection!!.subscribe(ProjectManager.TOPIC, RecentProjectsManagerBase.MyProjectListener())
+    connection!!.subscribe(AppLifecycleListener.TOPIC, RecentProjectsManagerBase.MyAppLifecycleListener())
   }
 
   override fun after() {
-    Disposer.dispose(disposable)
+    connection?.disconnect()
   }
 }
\ No newline at end of file
index 087aa35268d97adffc9609e0e807c75ede44885b..d9cd2daefb811fbe8d87fd5f9ee45c009a678242 100644 (file)
@@ -1,19 +1,13 @@
 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
-
-/*
- * @author max
- */
 package com.intellij.util.messages.impl;
 
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.progress.ProcessCanceledException;
 import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Ref;
 import com.intellij.testFramework.PlatformTestUtil;
 import com.intellij.util.concurrency.AppExecutorUtil;
-import com.intellij.util.messages.ListenerDescriptor;
-import com.intellij.util.messages.MessageBusConnection;
-import com.intellij.util.messages.MessageBusOwner;
-import com.intellij.util.messages.Topic;
+import com.intellij.util.messages.*;
 import org.jetbrains.annotations.NotNull;
 import org.junit.After;
 import org.junit.Before;
@@ -32,7 +26,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 public class MessageBusTest implements MessageBusOwner {
-  private MessageBusImpl myBus;
+  private CompositeMessageBus myBus;
   private final List<String> myLog = new ArrayList<>();
   private Disposable myParentDisposable = Disposer.newDisposable();
 
@@ -46,6 +40,11 @@ public class MessageBusTest implements MessageBusOwner {
     return Disposer.isDisposed(myParentDisposable);
   }
 
+  @Override
+  public boolean isParentLazyListenersIgnored() {
+    return true;
+  }
+
   public interface T1Listener {
     void t11();
     void t12();
@@ -56,9 +55,9 @@ public class MessageBusTest implements MessageBusOwner {
     void t22();
   }
 
-  private static final Topic<T1Listener> TOPIC1 = new Topic<>("T1", T1Listener.class);
-  private static final Topic<T2Listener> TOPIC2 = new Topic<>("T2", T2Listener.class);
-  private static final Topic<Runnable> RUNNABLE_TOPIC = new Topic<>("runnableTopic", Runnable.class);
+  private static final Topic<T1Listener> TOPIC1 = new Topic<>("T1", T1Listener.class, Topic.BroadcastDirection.TO_CHILDREN);
+  private static final Topic<T2Listener> TOPIC2 = new Topic<>("T2", T2Listener.class, Topic.BroadcastDirection.TO_CHILDREN);
+  private static final Topic<Runnable> RUNNABLE_TOPIC = new Topic<>(Runnable.class, Topic.BroadcastDirection.TO_CHILDREN);
 
   private final class T1Handler implements T1Listener {
     private final String id;
@@ -273,7 +272,7 @@ public class MessageBusTest implements MessageBusOwner {
   @Test
   public void testPostingPerformanceWithLowListenerDensityInHierarchy() {
     //simulating million fileWithNoDocumentChanged events on refresh in a thousand-module project
-    MessageBusImpl childBus = new MessageBusImpl(this, myBus);
+    CompositeMessageBus childBus = new CompositeMessageBus(this, myBus);
     childBus.connect().subscribe(TOPIC1, new T1Listener() {
       @Override
       public void t11() {
@@ -315,7 +314,7 @@ public class MessageBusTest implements MessageBusOwner {
     final int threadsNumber = 10;
     final AtomicReference<Throwable> exception = new AtomicReference<>();
     final CountDownLatch latch = new CountDownLatch(threadsNumber);
-    MessageBusImpl parentBus = new MessageBusImpl.RootBus(createSimpleMessageBusOwner("parent"));
+    CompositeMessageBus parentBus = new MessageBusImpl.RootBus(createSimpleMessageBusOwner("parent"));
     Disposer.register(myParentDisposable, parentBus);
     List<Future<?>> threads = new ArrayList<>();
     final int iterationsNumber = 100;
@@ -327,7 +326,7 @@ public class MessageBusTest implements MessageBusOwner {
             if (exception.get() != null) {
               break;
             }
-            new MessageBusImpl(createSimpleMessageBusOwner(String.format("child-%s-%s", Thread.currentThread().getName(), remains)), parentBus);
+            new CompositeMessageBus(createSimpleMessageBusOwner(String.format("child-%s-%s", Thread.currentThread().getName(), remains)), parentBus);
           }
         }
         catch (Throwable e) {
@@ -417,4 +416,52 @@ public class MessageBusTest implements MessageBusOwner {
     myBus.syncPublisher(RUNNABLE_TOPIC).run();
     assertTrue(Disposer.isDisposed(disposable));
   }
+
+  @Test
+  public void subscriberCacheClearedOnChildBusDispose() {
+    // ensure that subscriber cache is cleared on child bus dispose
+    MessageBusImpl child = new MessageBusImpl(this, myBus);
+    Ref<Boolean> isDisposed = new Ref<>(false);
+    child.connect().subscribe(RUNNABLE_TOPIC, () -> {
+      if (isDisposed.get()) {
+        throw new IllegalStateException("already disposed");
+      }
+    });
+    myBus.syncPublisher(RUNNABLE_TOPIC).run();
+    Disposer.dispose(child);
+    isDisposed.set(true);
+    myBus.syncPublisher(RUNNABLE_TOPIC).run();
+  }
+
+  private static final Topic<Runnable> TO_PARENT_TOPIC = new Topic<>(Runnable.class, Topic.BroadcastDirection.TO_PARENT);
+
+  @Test
+  public void subscriberCacheClearedOnConnectionToParentBusForChildBusTopic() {
+    // ensure that subscriber cache is cleared on connection to app level bus for topic that published to project level bus with TO_PARENT direction.
+    MessageBus child = new CompositeMessageBus(this, myBus);
+    // call to compute cache
+    child.syncPublisher(TO_PARENT_TOPIC).run();
+
+    Ref<Boolean> isCalled = new Ref<>(false);
+    myBus.connect().subscribe(TO_PARENT_TOPIC, () -> {
+      isCalled.set(true);
+    });
+    child.syncPublisher(TO_PARENT_TOPIC).run();
+    assertThat(isCalled.get()).isTrue();
+  }
+
+  @Test
+  public void subscriberCacheClearedOnConnectionToChildrenBusFoRootBusTopic() {
+    // child must be created before to ensure that cache is not cleared on a new child
+    MessageBus child = new CompositeMessageBus(this, myBus);
+    // call to compute cache
+    myBus.syncPublisher(RUNNABLE_TOPIC).run();
+
+    Ref<Boolean> isCalled = new Ref<>(false);
+    child.connect().subscribe(RUNNABLE_TOPIC, () -> {
+      isCalled.set(true);
+    });
+    myBus.syncPublisher(RUNNABLE_TOPIC).run();
+    assertThat(isCalled.get()).isTrue();
+  }
 }