IntelliJ Platform Plugin SDK Help

Entity Mutation

All modifications of WorkspaceModel are executed through a special mutable version of the store, represented by the MutableEntityStorage interface. It can be used to build a storage from scratch or modify an existing storage in a way which requires reading its state after modifications. Instances of this interface aren't thread-safe.

All modifications inside the IDE process are executed via WorkspaceModel.update().

Two main modification scenarios exist: small lightweight and batch operations (for complex computations).

Lightweight Operations

Adding Entities

To add a new entity to the storage, create it by calling the companion object of its interface and then pass it to the MutableEntityStorage.addEntity() function:

// mandatory properties are passed as parameters val module = ModuleEntity(moduleName, dependencies, entitySource) { // optional properties can be initialized in the lambda // passed as the last parameter type = ModuleTypeId.JAVA_MODULE } WorkspaceModel.getInstance(project).update("Add module") { builder -> builder.addEntity(module) }

An entire tree of entities can be prepared first, then add the root to the storage, and MutableEntityStorage.addEntity() will automatically add all the children.

Modifying and Removing Entities

To modify or remove an entity, find its instance in the MutableEntityStorage instance. This can be done by:

WorkspaceModel.getInstance(project).updateProjectModel("Update module") { builder -> val module = ModuleId(moduleName).resolve(builder) ?: ... val groupPath = module.groupPath ?: ... builder.removeEntity(groupPath) // a special extension function 'modifyEntity' // is generated for each entity type builder.modifyModuleEntity(module) { name = prefix + name } }

Adding and Removing Child Entities

There are two equivalent ways to add a child entity to an existing parent entity.

val contentRoot = ContentRootEntity(url, emptyList(), entitySource) { this.module = module } builder.addEntity(contentRoot)
val contentRoot = ContentRootEntity(url, emptyList(), entitySource) builder.modifyContentRootEntity(module) { this.contentRoots = this.contentRoots + contentRoot }

To remove a child entity, it is enough to call MutableEntityStorage.removeEntity() for it, the reference in its parent will be updated automatically. Also, if the reference to the parent is declared as non-null in the child interface, it is enough to modify the reference to the children in the parent entity:

builder.modifyModuleEntity(module) { contentRoots = contentRoots.filter { it.url != contentUrlToRemove } }

When performing this for a child with nullable reference to the parent, the child will be detached from the parent but won't be removed from the storage.

Batch Operations

Besides operation with individual entities, MutableEntityStorage supports two batch operations: applyChangesFrom() and replaceBySource().

Apply Changes From

Each instance of MutableEntityStorage records changes made in it: addition, modification, and removal of entities. Such changes made in one instance may be applied to a different instance by calling its applyChangesFrom() function.

The diagram below shows a schematic example of this operation. Yellow indicates changed entities, red deleted ones, and green new ones.

WorkspaceModel
applyChangesFrom()
MutableEntityStorage
ModuleEntity
ContentRootEntity1
ContentRootEntity2
SourceRootEntity1
SourceRootEntity2
SourceRootEntity3
SourceRootEntity4
Original
ContentRootEntity1
ModuleEntity
ContentRootEntity2
SourceRootEntity1
SourceRootEntity2
SourceRootEntity3
Result
ModuleEntity
ContentRootEntity1
ContentRootEntity2
SourceRootEntity2
SourceRootEntity3
SourceRootEntity4

Use Cases

Some exemplary use cases are listed below.

Parallel Filling of the Storage

When the configuration of a project is read from *.iml files, the IDE creates a separate empty MutableEntityStorage for each file and runs tasks which parse an *.iml file and load entities from it to the corresponding storage concurrently. When the tasks finish, their results are merged into the single storage via applyChangesFrom().

Accumulating Changes

The user performs modifications in the Project Structure dialog. Changes shouldn't be applied immediately, so they're accumulated in a builder and then added to the main storage using the applyChangesFrom() when the user presses the Apply button.

Replace By Source

Partially replace the storage with the new one based on the predicate on WorkspaceEntity.entitySource. This operation updates the part of the storage affected by the change, trying to minimize the number of changes in the entities.

Usually, entities in the workspace model storage are created from data loaded from some configuration files. When these configuration files change, the corresponding entities must be updated in the storage. It would be rather hard and error-prone to analyze what exactly was changed in the files since the previous version, so a different approach is used. Each entity must specify its WorkspaceEntity.entitySource describing where the entity comes from.

The diagram below shows a schematic example of the MutableEntityStorage.replaceBySource() operation. The color represents the type of entity source. In this example it is called with the predicate { entitySource is YellowEntitySource }. In the resulting storage we can see that the function automatically calculates the changes for entities with YellowEntitySource and doesn't touch entities not matching the predicate.

WorkspaceModel

replaceBySource()

MutableEntityStorage
ModuleEntity
ContentRootEntity3
ContentRootEntity1
SourceRootEntity3
SourceRootEntity1
SourceRootEntity4
Original
ModuleEntity
ContentRootEntity1
ContentRootEntity2
SourceRootEntity1
SourceRootEntity2
SourceRootEntity
Result
ModuleEntity
ContentRootEntity1
ContentRootEntity3
ContentRootEntity2
SourceRootEntity3
SourceRootEntity1
SourceRootEntity4
SourceRootEntity2
SourceRootEntity

Use Cases

Some exemplary use cases are listed below.

Importing a Project Model from External System

In a Maven project, the project model can be created each time we detect the pom.xml file was changed. The new project model is applied to the main storage using the replaceBySource() operation. This will affect only changed entities created from Maven.

Working with JPS Build System

For entities loaded from configuration files from the .idea directory, WorkspaceEntity.entitySource points to the corresponding XML file. When the IDE detects changes in some configuration files, it loads new entities from the created and modified files to a separate MutableEntityStorage instance. Then it calls replaceBySource() passing a filter which accepts entity sources corresponding to the all created, modified, and deleted files and the prepared MutableEntityStorage instance.

Last modified: 15 August 2024