IntelliJ Platform Plugin SDK Help

Messaging Infrastructure

IntelliJ Platform's messaging infrastructure is an implementation of Publisher Subscriber Pattern that provides additional features like broadcasting on hierarchy and special nested events processing (a nested event is an event directly or indirectly fired from the callback of another event).

Design

The following sections describe the main components of the messaging API:

Topic

The Topic class serves as an endpoint at the messaging infrastructure. Clients are allowed to subscribe to a specific topic within a bus and send messages to that topic within that particular bus. To clarify the corresponding message bus, a Topic field declaration should be annotated with Topic.@AppLevel and/or Topic.@ProjectLevel.

com.intellij.util.messages.Topic+getDisplayName()+getBroadcastDirection()ListenerClass+method1()...+methodN()1

Topic Properties

Display name

Human-readable name used for logging/monitoring purposes.

Broadcast direction

See Broadcasting for more details. The default value is TO_CHILDREN.

Listener class

A business interface for a particular topic. Subscribers register an implementation of this interface at the messaging infrastructure. Publishers later retrieve objects that conform to the interface (IS-A) and call any methods defined on those implementations. The messaging infrastructure takes care of dispatching the message to all subscribers of the topic by calling the same method with the same arguments on the registered implementation callbacks.

Message Bus

MessageBus is the core of the messaging system. It is used in the following scenarios:

SubscriberCreate connectionNecessary for subscribingPublisherPublish

Connection

Connection is represented by MessageBusConnection class and manages all subscriptions for a particular client within a particular bus.

MessageBusMessageBusConnectionDefault Handler(Topic-Handler)1*0..1*

Connection stores topic-handler mappings - callbacks to invoke when message for the target topic is received (not more than one handler per topic within the same connection is allowed).

It's possible to specify default handler and subscribe to the target topic without explicitly provided callback. Connection will use that default handler when storing a topic-handler mapping.

It's possible to explicitly release acquired resources (see disconnect()). Also, it can be plugged to standard semi-automatic disposing (Disposable).

Messaging API Usage

The sample below assumes a Project-level topic.

Defining a Business Interface and a Topic

Create an interface with the business methods and a topic field bound to the business interface:

public interface ChangeActionNotifier { @Topic.ProjectLevel Topic<ChangeActionNotifier> CHANGE_ACTION_TOPIC = Topic.create("custom name", ChangeActionNotifier.class); void beforeAction(Context context); void afterAction(Context context); }

Subscribing to a Topic

Get a messagebus referenceCreatea connectionto the busSubscribe no connectionconnection exists
project.getMessageBus().connect().subscribe( ChangeActionNotifier.CHANGE_ACTION_TOPIC, new ChangeActionNotifier() { @Override public void beforeAction(Context context) { // Process 'before action' event. } @Override public void afterAction(Context context) { // Process 'after action' event. } });

MessageBus instances are available via ComponentManager.getMessageBus(). Many standard interfaces implement returning a message bus, e.g., Application.getMessageBus() and Project.getMessageBus().

Publishing Messages

Get messagebus referenceAsk the busfor a particulartopic's publisherCall targetmethod onpublisherMessaging callsthe same methodon target handlers
public void doChange(Context context) { ChangeActionNotifier publisher = project.getMessageBus() .syncPublisher(ChangeActionNotifier.CHANGE_ACTION_TOPIC); publisher.beforeAction(context); try { // do action } finally { publisher.afterAction(context); } }

Broadcasting

Message buses can be organised into hierarchies. Moreover, the IntelliJ Platform has them already:

application busproject busmodule bus**

That allows to notify subscribers registered in one message bus on messages sent to another message bus.

Example setup:

application busproject busconnection1connection2connection3topic1-handler1topic1-handler2topic1-handler3

The example setup presents a simple hierarchy (the application bus is a parent of the project bus) with three subscribers for the same topic.

If topic1 defines broadcast direction as TO_CHILDREN, we get the following:

  1. A message is sent to topic1 via application bus.

  2. handler1 is notified about the message.

  3. The message is delivered to the subscribers of the same topic within project bus (handler2 and handler3).

The main benefit of broadcasting is managing subscribers that are bound to child buses but interested in parent bus-level events. In the example above, we may want to have project-specific functionality that reacts to the application-level events. All we need to do is to subscribe to the target topic within the project bus. No hard reference to the project-level subscriber will be stored at application-level then, i.e., we just avoided memory leak on project re-opening.

Broadcast configuration is defined per-topic. The following options are available:

  • TO_CHILDREN (default)

  • TO_DIRECT_CHILDREN

  • NONE

  • TO_PARENT

See Topic.BroadcastDirection for detailed description of each option.

Nested Messages

Nested message is a message sent (directly or indirectly) during another message processing. The IntelliJ Platform's messaging infrastructure guarantees that all messages sent to particular topic will be delivered at the sending order.

Consider the following configuration:

busconnection1connection2topic-handler1topic-handler2

When a message is sent to the target topic, the following happens:

  • message1 is sent

  • handler1 receives message1 and sends message2 to the same topic

  • handler2 receives message1

  • handler2 receives message2

  • handler1 receives message2

Tips and Tricks

Relief Listeners Management

Messaging infrastructure is very light-weight, so it's possible to reuse it at local sub-systems in order to relieve Subscribers construction. Let's see what is necessary to do then:

  1. Define business interface to work with.

  2. Create shared message bus and topic that uses the interface above (shared here means that either subject or subscribers know about them).

A manual implementation would require:

  1. Define listener interface (business interface).

  2. Provide reference to the subject to all interested listeners.

  3. Add listeners storage and listeners management methods (add/remove) to the subject.

  4. Manually iterate all listeners and call target callback in all places where new event is fired.

Avoid Shared Data Modification from Subscribers

We had a problem in a situation when two subscribers tried to modify the same document (IDEA-71701).

The thing is that every document change is performed by the following scenario:

  1. before change event is sent to all document listeners and some of them publish new messages during that;

  2. actual change is performed;

  3. after change event is sent to all document listeners;

We had the following then:

  1. message1 is sent to the topic with two subscribers;

  2. message1 is queued for both subscribers;

  3. message1 delivery starts;

  4. subscriber1 receives message1;

  5. subscriber1 issues document modification request at particular range (e.g. document.delete(startOffset, endOffset));

  6. before change notification is sent to the document listeners;

  7. message2 is sent by one of the standard document listeners to another topic within the same message bus during before change processing;

  8. the bus tries to deliver all pending messages before queuing message2;

  9. subscriber2 receives message1 and also modifies a document;

  10. the call stack is unwound and actual change phase of document modification operation requested by subscriber1 begins;

The problem is that document range used by subscriber1 for initial modification request is invalid if subscriber2 has changed document's range before it.

Last modified: 18 December 2023