diff --git a/platform/core-api/src/com/intellij/util/messages/impl/Message.java b/platform/core-api/src/com/intellij/util/messages/impl/Message.java index c25aa6c5b24c..6e002644bf63 100644 --- a/platform/core-api/src/com/intellij/util/messages/impl/Message.java +++ b/platform/core-api/src/com/intellij/util/messages/impl/Message.java @@ -6,13 +6,14 @@ import com.intellij.util.messages.Topic; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.Method; +import java.lang.invoke.MethodHandle; import java.util.Arrays; import java.util.List; final class Message { final Topic topic; - final Method listenerMethod; + final String methodName; + final MethodHandle method; final Object[] args; final List handlers; final @Nullable ClientId clientId; @@ -21,10 +22,10 @@ final class Message { // see note about pumpMessages in createPublisher (invoking job handlers can be stopped and continued as part of another pumpMessages call) int currentHandlerIndex; - Message(@NotNull Topic topic, @NotNull Method listenerMethod, Object[] args, @NotNull List handlers) { + Message(@NotNull Topic topic, @NotNull MethodHandle method, @NotNull String methodName, Object[] args, @NotNull List handlers) { this.topic = topic; - listenerMethod.setAccessible(true); - this.listenerMethod = listenerMethod; + this.method = method; + this.methodName = methodName; this.args = args; this.handlers = handlers; clientId = ClientId.getCurrentOrNull(); @@ -34,7 +35,7 @@ final class Message { public String toString() { return "Message(" + "topic=" + topic + - ", listenerMethod=" + listenerMethod + + ", method=" + methodName + ", args=" + Arrays.toString(args) + ", handlers=" + handlers + ')'; diff --git a/platform/core-api/src/com/intellij/util/messages/impl/MessageBusImpl.java b/platform/core-api/src/com/intellij/util/messages/impl/MessageBusImpl.java index 58a10fbc007f..94c0ed5cf90c 100644 --- a/platform/core-api/src/com/intellij/util/messages/impl/MessageBusImpl.java +++ b/platform/core-api/src/com/intellij/util/messages/impl/MessageBusImpl.java @@ -16,6 +16,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; +import java.lang.invoke.MethodHandle; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @@ -127,13 +128,10 @@ public class MessageBusImpl implements MessageBus { public final @NotNull L syncPublisher(@NotNull Topic topic) { checkNotDisposed(); //noinspection unchecked - return (L)publisherCache.computeIfAbsent(topic, this::createPublisherInvocationHandler); - } - - private @NotNull L createPublisherInvocationHandler(@NotNull Topic topic) { - Class listenerClass = topic.getListenerClass(); - //noinspection unchecked - return (L)Proxy.newProxyInstance(listenerClass.getClassLoader(), new Class[]{listenerClass}, createPublisher(topic, topic.getBroadcastDirection())); + return (L)publisherCache.computeIfAbsent(topic, topic1 -> { + Class listenerClass = topic1.getListenerClass(); + return Proxy.newProxyInstance(listenerClass.getClassLoader(), new Class[]{listenerClass}, createPublisher(topic1, topic1.getBroadcastDirection())); + }); } @NotNull @@ -163,7 +161,7 @@ public class MessageBusImpl implements MessageBus { @Override public final Object invoke(Object proxy, Method method, Object[] args) { - if (method.getDeclaringClass().getName().equals("java.lang.Object")) { + if (method.getDeclaringClass() == Object.class) { return EventDispatcher.handleObjectMethod(proxy, args, method.getName()); } @@ -213,14 +211,14 @@ public class MessageBusImpl implements MessageBus { @Nullable JobQueue jobQueue, @Nullable MessageDeliveryListener messageDeliveryListener, @Nullable List exceptions) { + MethodHandle methodHandle = MethodHandleCache.compute(method, args); if (jobQueue == null) { for (L handler : handlers) { - exceptions = invokeListener(method, args, topic, handler, messageDeliveryListener, exceptions); + exceptions = invokeListener(methodHandle, method.getName(), args, topic, handler, messageDeliveryListener, exceptions); } } else { - Message message = new Message<>(topic, method, args, handlers); - jobQueue.queue.offerLast(message); + jobQueue.queue.offerLast(new Message<>(topic, methodHandle, method.getName(), args, handlers)); } return exceptions; } @@ -423,7 +421,7 @@ public class MessageBusImpl implements MessageBus { } job.currentHandlerIndex++; - exceptions = invokeListener(job.listenerMethod, job.args, job.topic, handlers.get(index), messageDeliveryListener, exceptions); + exceptions = invokeListener(job.method, job.methodName, job.args, job.topic, handlers.get(index), 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; @@ -620,7 +618,7 @@ public class MessageBusImpl implements MessageBus { job.handlers.addAll(connectionHandlers); } else { - filteredJob = new Message<>(job.topic, job.listenerMethod, job.args, connectionHandlers); + filteredJob = new Message<>(job.topic, job.method, job.methodName, job.args, connectionHandlers); } if (newJobs == null) { newJobs = new SmartList<>(); @@ -638,7 +636,8 @@ public class MessageBusImpl implements MessageBus { } // args is not null - private static @Nullable List invokeListener(@NotNull Method method, + private static @Nullable List invokeListener(@NotNull MethodHandle methodHandle, + @NotNull String methodName, Object[] args, @NotNull Topic topic, @NotNull L handler, @@ -646,23 +645,38 @@ public class MessageBusImpl implements MessageBus { @Nullable List exceptions) { try { if (handler instanceof MessageHandler) { - ((MessageHandler)handler).handle(method, args); + ((MessageHandler)handler).handle(methodHandle, args); } else if (messageDeliveryListener == null) { - method.invoke(handler, args); + invokeMethod(handler, args, methodHandle); } else { long startTime = System.nanoTime(); - method.invoke(handler, args); - messageDeliveryListener.messageDelivered(topic, method.getName(), handler, System.nanoTime() - startTime); + invokeMethod(handler, args, methodHandle); + messageDeliveryListener.messageDelivered(topic, methodName, handler, System.nanoTime() - startTime); } } + catch (AbstractMethodError e) { + // do nothing for AbstractMethodError. This listener just does not implement something newly added yet. + } catch (Throwable e) { - exceptions = EventDispatcher.handleException(e, exceptions); + if (exceptions == null) { + exceptions = new ArrayList<>(); + } + exceptions.add(e); } return exceptions; } + private static void invokeMethod(@NotNull Object handler, Object[] args, MethodHandle methodHandle) throws Throwable { + if (args == null) { + methodHandle.invoke(handler); + } + else { + methodHandle.bindTo(handler).invokeExact(args); + } + } + protected void disconnectPluginConnections(@NotNull Predicate> predicate) { for (MessageHandlerHolder holder : subscribers) { holder.disconnectIfNeeded(predicate); diff --git a/platform/core-api/src/com/intellij/util/messages/impl/MethodHandleCache.java b/platform/core-api/src/com/intellij/util/messages/impl/MethodHandleCache.java new file mode 100644 index 000000000000..d5296a409f68 --- /dev/null +++ b/platform/core-api/src/com/intellij/util/messages/impl/MethodHandleCache.java @@ -0,0 +1,39 @@ +// 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 org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +final class MethodHandleCache { + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + private static final ClassValue> CACHE = new ConcurrentMapClassValue(); + + static @NotNull MethodHandle compute(@NotNull Method method, Object @Nullable [] args) { + // method name cannot be used as key because for one class maybe several methods with the same name and different set of parameters + return CACHE.get(method.getDeclaringClass()).computeIfAbsent(method, method1 -> { + method1.setAccessible(true); + MethodHandle result; + try { + result = LOOKUP.unreflect(method1); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + return args == null ? result : result.asSpreader(Object[].class, args.length); + }); + } + + // as static to ensure that class doesn't reference anything else (otherwise maybe memory leak) + private static final class ConcurrentMapClassValue extends ClassValue> { + @Override + protected ConcurrentMap computeValue(@NotNull Class type) { + return new ConcurrentHashMap<>(8); + } + } +} diff --git a/platform/extensions/src/com/intellij/util/messages/MessageBusConnection.java b/platform/extensions/src/com/intellij/util/messages/MessageBusConnection.java index cb79b93ff3e0..ac3c776d666c 100644 --- a/platform/extensions/src/com/intellij/util/messages/MessageBusConnection.java +++ b/platform/extensions/src/com/intellij/util/messages/MessageBusConnection.java @@ -12,11 +12,11 @@ import org.jetbrains.annotations.Nullable; */ public interface MessageBusConnection extends SimpleMessageBusConnection, Disposable { /** - * Subscribes to the target topic within the current connection using {@link #setDefaultHandler(MessageHandler) default handler}. + * Subscribes to the target topic within the current connection using {@link #setDefaultHandler(Runnable) default handler}. * * @param topic target endpoint * @param interface for working with the target topic - * @throws IllegalStateException if {@link #setDefaultHandler(MessageHandler) default handler} hasn't been defined or + * @throws IllegalStateException if {@link #setDefaultHandler(Runnable) default handler} hasn't been defined or * has incompatible type with the {@link Topic#getListenerClass() topic's business interface} * or if target topic is already subscribed within the current connection */ @@ -29,6 +29,10 @@ public interface MessageBusConnection extends SimpleMessageBusConnection, Dispos */ void setDefaultHandler(@Nullable MessageHandler handler); + default void setDefaultHandler(@NotNull Runnable runnable) { + setDefaultHandler((event, params) -> runnable.run()); + } + /** * Forces to process any queued but not delivered events. * diff --git a/platform/extensions/src/com/intellij/util/messages/MessageHandler.java b/platform/extensions/src/com/intellij/util/messages/MessageHandler.java index 631b0d560dfd..b70d721090a7 100644 --- a/platform/extensions/src/com/intellij/util/messages/MessageHandler.java +++ b/platform/extensions/src/com/intellij/util/messages/MessageHandler.java @@ -3,7 +3,7 @@ package com.intellij.util.messages; import org.jetbrains.annotations.NotNull; -import java.lang.reflect.Method; +import java.lang.invoke.MethodHandle; /** * Defines contract for generic messages subscriber processor. @@ -16,5 +16,5 @@ public interface MessageHandler { * @param event information about target method called by the publisher * @param params called method arguments */ - void handle(@NotNull Method event, Object... params); + void handle(@NotNull MethodHandle event, Object... params); } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/render/DocRenderItem.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/render/DocRenderItem.java index 142586aa9bbd..53a8b1a4fd9c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/render/DocRenderItem.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/render/DocRenderItem.java @@ -134,7 +134,7 @@ public final class DocRenderItem { else { if (editor.getUserData(LISTENERS_DISPOSABLE) == null) { MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect(); - connection.setDefaultHandler((event, params) -> updateInlays(editor, true)); + connection.setDefaultHandler(() -> updateInlays(editor, true)); connection.subscribe(EditorColorsManager.TOPIC); connection.subscribe(LafManagerListener.TOPIC); EditorFactory.getInstance().addEditorFactoryListener(new EditorFactoryListener() { diff --git a/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleManagerComponent.java b/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleManagerComponent.java index fcb28d9346d5..0eb8948f6a00 100644 --- a/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleManagerComponent.java +++ b/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleManagerComponent.java @@ -46,7 +46,7 @@ public class ModuleManagerComponent extends ModuleManagerImpl { super(project); myMessageBusConnection = project.getMessageBus().connect(this); - myMessageBusConnection.setDefaultHandler((event, params) -> cleanCachedStuff()); + myMessageBusConnection.setDefaultHandler(() -> cleanCachedStuff()); myMessageBusConnection.subscribe(ProjectTopics.PROJECT_ROOTS); // default project doesn't have modules diff --git a/platform/platform-tests/testSrc/com/intellij/util/messages/impl/MessageBusTest.java b/platform/platform-tests/testSrc/com/intellij/util/messages/impl/MessageBusTest.java index 95c63f54207b..5ca04708324a 100644 --- a/platform/platform-tests/testSrc/com/intellij/util/messages/impl/MessageBusTest.java +++ b/platform/platform-tests/testSrc/com/intellij/util/messages/impl/MessageBusTest.java @@ -284,13 +284,13 @@ public class MessageBusTest implements MessageBusOwner { public void t12() { } }); - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < 1_000; i++) { new MessageBusImpl(this, childBus); } - PlatformTestUtil.assertTiming("Too long", 3000, () -> { + PlatformTestUtil.assertTiming("Too long", 3_000, () -> { T1Listener publisher = myBus.syncPublisher(TOPIC1); - for (int i = 0; i < 1000000; i++) { + for (int i = 0; i < 1_000_000; i++) { publisher.t11(); } });