mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Support message bus listeners for dynamic plugins
GitOrigin-RevId: f2f7a011eb15782f81144f324defde85e2ce2d59
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c45fbce546
commit
7c6aaa7e88
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
@@ -56,7 +57,7 @@ final class MessageBusConnectionImpl implements MessageBusConnection {
|
||||
}
|
||||
|
||||
// avoid notifyOnSubscription and map modification for each handler
|
||||
<L> void subscribe(@NotNull Topic<L> topic, @NotNull List<Object> handlers) {
|
||||
<L> void subscribe(@NotNull Topic<L> topic, @NotNull Collection<Object> handlers) {
|
||||
boolean notifyBusAboutTopic = false;
|
||||
synchronized (myPendingMessages) {
|
||||
Object currentHandler = mySubscriptions.get(topic);
|
||||
|
||||
@@ -4,12 +4,15 @@ 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.PluginDescriptor;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ConcurrencyUtil;
|
||||
import com.intellij.util.EventDispatcher;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.FactoryMap;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.lang.CompoundRuntimeException;
|
||||
import com.intellij.util.messages.ListenerDescriptor;
|
||||
import com.intellij.util.messages.MessageBus;
|
||||
@@ -65,7 +68,7 @@ public class MessageBusImpl implements MessageBus {
|
||||
private final Disposable myConnectionDisposable;
|
||||
private MessageDeliveryListener myMessageDeliveryListener;
|
||||
|
||||
private final MessageBusConnectionImpl myLazyConnection;
|
||||
private final Map<PluginDescriptor, MessageBusConnectionImpl> myLazyConnections;
|
||||
|
||||
public MessageBusImpl(@NotNull MessageBusOwner owner, @NotNull MessageBusImpl parentBus) {
|
||||
myOwner = owner;
|
||||
@@ -79,7 +82,7 @@ public class MessageBusImpl implements MessageBus {
|
||||
LOG.assertTrue(parentBus.myChildBuses.contains(this));
|
||||
myRootBus.clearSubscriberCache();
|
||||
// only for project
|
||||
myLazyConnection = parentBus.myParentBus == null ? connect() : null;
|
||||
myLazyConnections = parentBus.myParentBus == null ? FactoryMap.create((key) -> connect()) : null;
|
||||
}
|
||||
|
||||
// root message bus constructor
|
||||
@@ -88,7 +91,7 @@ public class MessageBusImpl implements MessageBus {
|
||||
myConnectionDisposable = Disposer.newDisposable(myOwner.toString());
|
||||
myOrder = ArrayUtil.EMPTY_INT_ARRAY;
|
||||
myRootBus = (RootBus)this;
|
||||
myLazyConnection = connect();
|
||||
myLazyConnections = FactoryMap.create((key) -> connect());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,9 +100,12 @@ public class MessageBusImpl implements MessageBus {
|
||||
@ApiStatus.Internal
|
||||
public void setLazyListeners(@NotNull ConcurrentMap<String, List<ListenerDescriptor>> map) {
|
||||
if (myTopicClassToListenerClass != Collections.<String, List<ListenerDescriptor>>emptyMap()) {
|
||||
throw new IllegalStateException("Already set: "+myTopicClassToListenerClass);
|
||||
myTopicClassToListenerClass.putAll(map);
|
||||
myPublishers.clear();
|
||||
}
|
||||
else {
|
||||
myTopicClassToListenerClass = map;
|
||||
}
|
||||
myTopicClassToListenerClass = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -204,10 +210,10 @@ public class MessageBusImpl implements MessageBus {
|
||||
|
||||
List<ListenerDescriptor> listenerDescriptors = myTopicClassToListenerClass.remove(listenerClass.getName());
|
||||
if (listenerDescriptors != null) {
|
||||
List<Object> listeners = new ArrayList<>(listenerDescriptors.size());
|
||||
MultiMap<PluginDescriptor, Object> listenerMap = new MultiMap<>();
|
||||
for (ListenerDescriptor listenerDescriptor : listenerDescriptors) {
|
||||
try {
|
||||
listeners.add(myOwner.createListener(listenerDescriptor));
|
||||
listenerMap.putValue(listenerDescriptor.pluginDescriptor, myOwner.createListener(listenerDescriptor));
|
||||
}
|
||||
catch (ExtensionNotApplicableException ignore) {
|
||||
}
|
||||
@@ -219,8 +225,10 @@ public class MessageBusImpl implements MessageBus {
|
||||
}
|
||||
}
|
||||
|
||||
if (!listeners.isEmpty()) {
|
||||
myLazyConnection.subscribe(topic, listeners);
|
||||
if (!listenerMap.isEmpty()) {
|
||||
for (Map.Entry<PluginDescriptor, Collection<Object>> entry : listenerMap.entrySet()) {
|
||||
myLazyConnections.get(entry.getKey()).subscribe(topic, entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +238,14 @@ public class MessageBusImpl implements MessageBus {
|
||||
return publisher;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public void unsubscribePluginListeners(PluginDescriptor pluginDescriptor) {
|
||||
MessageBusConnectionImpl connection = myLazyConnections.remove(pluginDescriptor);
|
||||
if (connection != null) {
|
||||
Disposer.dispose(connection);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private <L> InvocationHandler createTopicHandler(@NotNull Topic<L> topic) {
|
||||
return (proxy, method, args) -> {
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.intellij.util.ArrayUtil
|
||||
import com.intellij.util.MemoryDumpHelper
|
||||
import com.intellij.util.SystemProperties
|
||||
import com.intellij.util.messages.Topic
|
||||
import com.intellij.util.messages.impl.MessageBusImpl
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.xmlb.BeanBinding
|
||||
import java.text.SimpleDateFormat
|
||||
@@ -174,6 +175,7 @@ object DynamicPlugins {
|
||||
for (appServiceInstance in appServiceInstances) {
|
||||
application.stateStore.unloadComponent(appServiceInstance)
|
||||
}
|
||||
(application.messageBus as MessageBusImpl).unsubscribePluginListeners(loadedPluginDescriptor)
|
||||
|
||||
for (project in openProjects) {
|
||||
val projectServiceInstances = (project as ProjectImpl).unloadServices(pluginDescriptor.project)
|
||||
@@ -187,6 +189,8 @@ object DynamicPlugins {
|
||||
module.stateStore.unloadComponent(moduleServiceInstance)
|
||||
}
|
||||
}
|
||||
|
||||
(project.messageBus as MessageBusImpl).unsubscribePluginListeners(loadedPluginDescriptor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,8 +221,10 @@ object DynamicPlugins {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun loadPlugin(pluginDescriptor: IdeaPluginDescriptorImpl, enable: Boolean) {
|
||||
PluginManagerCore.initClassLoader(pluginDescriptor)
|
||||
fun loadPlugin(pluginDescriptor: IdeaPluginDescriptorImpl, wasDisabled: Boolean) {
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||
PluginManagerCore.initClassLoader(pluginDescriptor)
|
||||
}
|
||||
|
||||
val application = ApplicationManager.getApplication() as ApplicationImpl
|
||||
application.runWriteAction {
|
||||
@@ -232,7 +238,7 @@ object DynamicPlugins {
|
||||
(ActionManager.getInstance() as ActionManagerImpl).registerPluginActions(pluginDescriptor)
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
if (wasDisabled) {
|
||||
// Update list of disabled plugins
|
||||
PluginManagerCore.setPlugins(PluginManagerCore.getPlugins())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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.
|
||||
package com.intellij.ide.plugins
|
||||
|
||||
import com.github.marschall.memoryfilesystem.MemoryFileSystemBuilder
|
||||
import com.intellij.ide.ui.UISettings
|
||||
import com.intellij.ide.ui.UISettingsListener
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.testFramework.LightPlatformTestCase
|
||||
import com.intellij.util.io.write
|
||||
import org.junit.Test
|
||||
|
||||
class DynamicPluginsTest : LightPlatformTestCase() {
|
||||
companion object {
|
||||
val receivedNotifications = mutableListOf<UISettings>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLoadListeners() {
|
||||
receivedNotifications.clear()
|
||||
|
||||
MemoryFileSystemBuilder.newEmpty().build(DynamicPluginsTest::class.java.simpleName).use { fs ->
|
||||
ApplicationManager.getApplication().messageBus.syncPublisher(UISettingsListener.TOPIC).uiSettingsChanged(UISettings())
|
||||
|
||||
val pluginFile = fs.getPath("/plugin/META-INF/plugin.xml")
|
||||
pluginFile.write("""
|
||||
<idea-plugin>
|
||||
<name>testLoadListeners</name>
|
||||
<applicationListeners>
|
||||
<listener class="${MyUISettingsListener::class.java.name}" topic="com.intellij.ide.ui.UISettingsListener"/>
|
||||
</applicationListeners>
|
||||
</idea-plugin>""")
|
||||
val descriptor = loadDescriptorInTest(pluginFile.parent.parent)
|
||||
descriptor.setLoader(DynamicPlugins::class.java.classLoader)
|
||||
DynamicPlugins.loadPlugin(descriptor, false)
|
||||
ApplicationManager.getApplication().messageBus.syncPublisher(UISettingsListener.TOPIC).uiSettingsChanged(UISettings())
|
||||
assertEquals(1, receivedNotifications.size)
|
||||
|
||||
DynamicPlugins.unloadPlugin(descriptor, false)
|
||||
ApplicationManager.getApplication().messageBus.syncPublisher(UISettingsListener.TOPIC).uiSettingsChanged(UISettings())
|
||||
assertEquals(1, receivedNotifications.size)
|
||||
}
|
||||
}
|
||||
|
||||
private class MyUISettingsListener : UISettingsListener {
|
||||
override fun uiSettingsChanged(uiSettings: UISettings) {
|
||||
receivedNotifications.add(uiSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ class PluginDescriptorTest {
|
||||
|
||||
@Test
|
||||
fun testDescriptorLoading() {
|
||||
val descriptor = loadDescriptor("asp.jar")
|
||||
val descriptor = loadDescriptorInTest("asp.jar")
|
||||
assertThat(descriptor).isNotNull()
|
||||
assertThat(descriptor.pluginId.idString).isEqualTo("com.jetbrains.plugins.asp")
|
||||
assertThat(descriptor.name).isEqualTo("ASP")
|
||||
@@ -44,14 +44,14 @@ class PluginDescriptorTest {
|
||||
|
||||
@Test
|
||||
fun testOptionalDescriptors() {
|
||||
val descriptor = loadDescriptor("family")
|
||||
val descriptor = loadDescriptorInTest("family")
|
||||
assertThat(descriptor).isNotNull()
|
||||
assertThat(descriptor.optionalConfigs.size).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultipleOptionalDescriptors() {
|
||||
val descriptor = loadDescriptor("multipleOptionalDescriptors")
|
||||
val descriptor = loadDescriptorInTest("multipleOptionalDescriptors")
|
||||
assertThat(descriptor).isNotNull()
|
||||
val ids = descriptor.optionalConfigs.keys
|
||||
assertThat(ids).hasSize(2)
|
||||
@@ -61,20 +61,20 @@ class PluginDescriptorTest {
|
||||
@Test
|
||||
fun testMalformedDescriptor() {
|
||||
@Suppress("GrazieInspection")
|
||||
assertThatThrownBy { loadDescriptor("malformed") }
|
||||
assertThatThrownBy { loadDescriptorInTest("malformed") }
|
||||
.hasMessageContaining("Content is not allowed in prolog.")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAnonymousDescriptor() {
|
||||
val descriptor = loadDescriptor("anonymous")
|
||||
val descriptor = loadDescriptorInTest("anonymous")
|
||||
assertThat(descriptor.pluginId).isNull()
|
||||
assertThat(descriptor.name).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCyclicOptionalDeps() {
|
||||
assertThatThrownBy { loadDescriptor("cyclicOptionalDeps") }
|
||||
assertThatThrownBy { loadDescriptorInTest("cyclicOptionalDeps") }
|
||||
.hasMessage("Plugin someId optional descriptors form a cycle: a.xml, b.xml")
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ class PluginDescriptorTest {
|
||||
|
||||
@Test
|
||||
fun testDuplicateDependency() {
|
||||
val descriptor = loadDescriptor("duplicateDependency")
|
||||
val descriptor = loadDescriptorInTest("duplicateDependency")
|
||||
assertThat(descriptor).isNotNull()
|
||||
assertThat(
|
||||
descriptor.optionalDependentPluginIds).isEmpty()
|
||||
@@ -129,7 +129,7 @@ class PluginDescriptorTest {
|
||||
|
||||
@Test
|
||||
fun testPluginNameAsId() {
|
||||
val descriptor = loadDescriptor("noId")
|
||||
val descriptor = loadDescriptorInTest("noId")
|
||||
assertThat(descriptor).isNotNull()
|
||||
assertThat(descriptor.pluginId.idString).isEqualTo(descriptor.name)
|
||||
}
|
||||
@@ -143,7 +143,7 @@ class PluginDescriptorTest {
|
||||
<vendor>JetBrains</vendor>
|
||||
<product-descriptor code="IJ" release-date="20190811" release-version="42"/>
|
||||
</idea-plugin>""")
|
||||
val descriptor = loadDescriptor(pluginFile.parent.parent)
|
||||
val descriptor = loadDescriptorInTest(pluginFile.parent.parent)
|
||||
assertThat(descriptor).isNotNull()
|
||||
assertThat(descriptor.vendor).isEqualTo("JetBrains")
|
||||
assertThat(SimpleDateFormat("yyyyMMdd", Locale.US).format(descriptor.releaseDate)).isEqualTo("20190811")
|
||||
@@ -302,14 +302,14 @@ class PluginDescriptorTest {
|
||||
fun componentConfig() {
|
||||
val pluginFile = inMemoryFs.fs.getPath("/plugin/META-INF/plugin.xml")
|
||||
pluginFile.write("<idea-plugin>\n <id>bar</id>\n <project-components>\n <component>\n <implementation-class>com.intellij.ide.favoritesTreeView.FavoritesManager</implementation-class>\n <option name=\"workspace\" value=\"true\"/>\n </component>\n\n \n </project-components>\n</idea-plugin>")
|
||||
val descriptor = loadDescriptor(pluginFile.parent.parent)
|
||||
val descriptor = loadDescriptorInTest(pluginFile.parent.parent)
|
||||
assertThat(descriptor).isNotNull
|
||||
assertThat(descriptor.projectContainerDescriptor.components!![0].options).isEqualTo(Collections.singletonMap("workspace", "true"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPluginIdAsName() {
|
||||
val descriptor = loadDescriptor("noName")
|
||||
val descriptor = loadDescriptorInTest("noName")
|
||||
assertThat(descriptor).isNotNull()
|
||||
assertThat(descriptor.name).isEqualTo(descriptor.pluginId.idString)
|
||||
}
|
||||
@@ -357,13 +357,13 @@ class PluginDescriptorTest {
|
||||
<id>ID</id>
|
||||
<name>A</name>
|
||||
</idea-plugin>""")
|
||||
val impl1 = loadDescriptor(fs.getPath("/"))
|
||||
val impl1 = loadDescriptorInTest(fs.getPath("/"))
|
||||
tempFile.write("""
|
||||
<idea-plugin>
|
||||
<id>ID</id>
|
||||
<name>B</name>
|
||||
</idea-plugin>""")
|
||||
val impl2 = loadDescriptor(fs.getPath("/"))
|
||||
val impl2 = loadDescriptorInTest(fs.getPath("/"))
|
||||
TestCase.assertEquals(impl1, impl2)
|
||||
TestCase.assertEquals(impl1.hashCode(), impl2.hashCode())
|
||||
TestCase.assertNotSame(impl1.name, impl2.name)
|
||||
@@ -377,11 +377,11 @@ private fun writeDescriptor(id: String, pluginDir: Path, @Language("xml") data:
|
||||
private val testDataPath: String
|
||||
get() = PlatformTestUtil.getPlatformTestDataPath() + "plugins/pluginDescriptor"
|
||||
|
||||
private fun loadDescriptor(dirName: String): IdeaPluginDescriptorImpl {
|
||||
return loadDescriptor(Paths.get(testDataPath, dirName))
|
||||
private fun loadDescriptorInTest(dirName: String): IdeaPluginDescriptorImpl {
|
||||
return loadDescriptorInTest(Paths.get(testDataPath, dirName))
|
||||
}
|
||||
|
||||
private fun loadDescriptor(dir: Path): IdeaPluginDescriptorImpl {
|
||||
internal fun loadDescriptorInTest(dir: Path): IdeaPluginDescriptorImpl {
|
||||
assertThat(dir).exists()
|
||||
PluginManagerCore.ourPluginError = null
|
||||
val parentContext = DescriptorListLoadingContext.createSingleDescriptorContext(emptySet())
|
||||
|
||||
-1
@@ -155,7 +155,6 @@ abstract class PlatformComponentManagerImpl @JvmOverloads constructor(internal v
|
||||
StartUpMeasurer.setCurrentState(LoadingState.COMPONENTS_REGISTERED)
|
||||
}
|
||||
|
||||
// todo support lazy listeners for dynamically loaded plugins
|
||||
// ensure that messageBus is created, regardless of lazy listeners map state
|
||||
val messageBus = messageBus as MessageBusImpl
|
||||
if (map != null) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework" />
|
||||
<orderEntry type="library" name="JDOM" level="project" />
|
||||
<orderEntry type="library" exported="" name="assertJ" level="project" />
|
||||
<orderEntry type="library" name="memoryfilesystem" level="project" />
|
||||
<orderEntry type="library" exported="" name="memoryfilesystem" level="project" />
|
||||
<orderEntry type="library" name="snakeyaml" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
Reference in New Issue
Block a user