[workspace model] IJPL-169 Migration of custom libraries to WSM

Prior to this change custom libraries stored in `LibraryTableBase` and it was the latest peace of libraries that was not stored in WSM. Custom libraries also very similar to the global one, so some common code was extracted

GitOrigin-RevId: 147f5f24047b7e3820918592bfb3500ab34b97a8
This commit is contained in:
Mikhail Mazurkevich
2023-12-21 01:13:14 +00:00
committed by intellij-monorepo-bot
parent 7bc51722c6
commit 400ae4e157
21 changed files with 761 additions and 444 deletions
@@ -7,9 +7,10 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.impl.OrderRootsCache
import com.intellij.openapi.roots.impl.ProjectRootManagerComponent
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar
import com.intellij.openapi.util.EmptyRunnable
import com.intellij.platform.workspace.jps.entities.LibraryTableId
import com.intellij.util.indexing.BuildableRootsChangeRescanningInfo
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.LibraryBridge
import com.intellij.workspaceModel.ide.impl.legacyBridge.module.roots.OrderRootsCacheBridge
import com.intellij.workspaceModel.ide.legacyBridge.ModuleDependencyIndex
import com.intellij.workspaceModel.ide.legacyBridge.ModuleDependencyListener
@@ -77,9 +78,9 @@ class ProjectRootManagerBridge(project: Project, coroutineScope: CoroutineScope)
}
private fun shouldListen(library: Library): Boolean {
//project and global level libraries are stored in WorkspaceModel, and changes in their roots are handled by RootsChangeWatcher
return library.table?.tableLevel != LibraryTablesRegistrar.PROJECT_LEVEL &&
library.table?.tableLevel != LibraryTablesRegistrar.APPLICATION_LEVEL
//project, global and custom level libraries are stored in WorkspaceModel, and changes in their roots are handled by RootsChangeWatcher
val libraryTableId = (library as? LibraryBridge)?.libraryId?.tableId ?: return true
return libraryTableId !is LibraryTableId.ProjectLibraryTableId && libraryTableId !is LibraryTableId.GlobalLibraryTableId
}
override fun referencedSdkAdded(sdk: Sdk) {
@@ -36,7 +36,7 @@ class GlobalSdkBridgeInitializer : BridgeInitializer {
class GlobalSdkBridgesLoader: GlobalSdkTableBridge {
override fun initializeSdkBridgesAfterLoading(mutableStorage: MutableEntityStorage,
override fun initializeBridgesAfterLoading(mutableStorage: MutableEntityStorage,
initialEntityStorage: VersionedEntityStorage): () -> Unit {
val sdks = mutableStorage
.entities(SdkEntity::class.java)
@@ -51,7 +51,7 @@ class GlobalSdkBridgesLoader: GlobalSdkTableBridge {
return {}
}
override fun initializeSdkBridges(changes: Map<Class<*>, List<EntityChange<*>>>, builder: MutableEntityStorage) {
override fun initializeBridges(changes: Map<Class<*>, List<EntityChange<*>>>, builder: MutableEntityStorage) {
@Suppress("UNCHECKED_CAST")
val sdkChanges = (changes[SdkEntity::class.java] as? List<EntityChange<SdkEntity>>) ?: emptyList()
val addChanges = sdkChanges.filterIsInstance<EntityChange.Added<SdkEntity>>()
@@ -2,7 +2,6 @@
package com.intellij.openapi.roots
import com.intellij.openapi.application.runWriteActionAndWait
import com.intellij.openapi.roots.impl.libraries.CustomLibraryTableImpl
import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.LibraryTable
@@ -17,7 +16,6 @@ import com.intellij.testFramework.rules.ProjectModelRule
import com.intellij.workspaceModel.ide.impl.legacyBridge.LegacyBridgeModifiableBase
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.LibraryBridgeImpl
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assume
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
@@ -190,8 +188,6 @@ abstract class LibraryTableTestCase {
@Test
fun `check events count at library update`() {
Assume.assumeFalse("Test isn't applicable for CustomLibraryTable", libraryTable is CustomLibraryTableImpl)
var eventsCount = 0
val libraryNames = listOf("a", "b", "c")
projectModel.project.messageBus.connect().subscribe(WorkspaceModelTopics.CHANGED, object : WorkspaceModelChangeListener {
@@ -222,8 +218,6 @@ abstract class LibraryTableTestCase {
@Test
fun `use single builder at library update`() {
Assume.assumeFalse("Test isn't applicable for CustomLibraryTable", libraryTable is CustomLibraryTableImpl)
val libraryNames = listOf("a", "b", "c")
edit { model ->
val mutableStorage = (model as LegacyBridgeModifiableBase).diff
@@ -17,8 +17,7 @@ import com.intellij.workspaceModel.ide.*
import com.intellij.workspaceModel.ide.impl.GlobalWorkspaceModel
import com.intellij.workspaceModel.ide.impl.jpsMetrics
import com.intellij.workspaceModel.ide.impl.legacyBridge.sdk.SdkBridgeImpl.Companion.sdkMap
import com.intellij.workspaceModel.ide.legacyBridge.GlobalLibraryTableBridge
import com.intellij.workspaceModel.ide.legacyBridge.GlobalSdkTableBridge
import com.intellij.workspaceModel.ide.legacyBridge.GlobalEntityBridgeAndEventHandler
import io.opentelemetry.api.metrics.Meter
import kotlinx.coroutines.*
import org.jdom.Element
@@ -156,13 +155,10 @@ class JpsGlobalModelSynchronizerImpl(private val coroutineScope: CoroutineScope)
private fun bridgesInitializationCallback(mutableStorage: MutableEntityStorage,
initialEntityStorage: VersionedEntityStorage): () -> Unit {
val sdkCallback = GlobalSdkTableBridge.getInstance().initializeSdkBridgesAfterLoading(mutableStorage,
initialEntityStorage)
val librariesCallback = GlobalLibraryTableBridge.getInstance().initializeLibraryBridgesAfterLoading(mutableStorage,
initialEntityStorage)
val callbacks = GlobalEntityBridgeAndEventHandler.getAllGlobalEntityHandlers()
.map { it.initializeBridgesAfterLoading(mutableStorage, initialEntityStorage) }
return {
sdkCallback.invoke()
librariesCallback.invoke()
callbacks.forEach { it.invoke() }
}
}
@@ -0,0 +1,18 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.roots.libraries;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import org.jdom.Element;
/**
* The CustomLibraryTable interface extends the LibraryTable interface and represents a custom library table.
* It provides methods to read and write the library table from/to an XML element.
* <p>
* The direct usage of these methods has to be provided by the client, not by the platform,
* so client's code is responsible for serialization of custom libraries to/from disk
*/
public interface CustomLibraryTable extends LibraryTable {
void readExternal(final Element element) throws InvalidDataException;
void writeExternal(final Element element) throws WriteExternalException;
}
@@ -28,13 +28,19 @@
<applicationService serviceInterface="com.intellij.openapi.roots.libraries.LibraryTablesRegistrar"
serviceImplementation="com.intellij.openapi.roots.impl.libraries.LibraryTablesRegistrarImpl"/>
<!-- Global Workspace Model -->
<applicationService serviceInterface="com.intellij.workspaceModel.ide.legacyBridge.GlobalLibraryTableBridge"
serviceImplementation="com.intellij.workspaceModel.ide.impl.legacyBridge.library.GlobalLibraryTableBridgeImpl"/>
<workspace.bridgeInitializer
implementation="com.intellij.workspaceModel.ide.impl.legacyBridge.library.GlobalLibraryTableBridgeInitializer"/>
<applicationService serviceImplementation="com.intellij.workspaceModel.ide.impl.GlobalWorkspaceModel"/>
<applicationService serviceInterface="com.intellij.workspaceModel.ide.JpsGlobalModelSynchronizer"
serviceImplementation="com.intellij.workspaceModel.ide.impl.jps.serialization.JpsGlobalModelSynchronizerImpl"/>
<applicationService serviceImplementation="com.intellij.workspaceModel.ide.impl.GlobalWorkspaceModel"/>
<!-- Global Workspace Model Libraries -->
<applicationService serviceInterface="com.intellij.workspaceModel.ide.legacyBridge.GlobalLibraryTableBridge"
serviceImplementation="com.intellij.workspaceModel.ide.impl.legacyBridge.library.GlobalLibraryTableBridgeImpl"/>
<!-- Custom Workspace Model Libraries -->
<registryKey key="workspace.model.custom.library.bridge" defaultValue="true"
description="Enables new implementation for Custom libraries which works via Workspace Model"
restartRequired="true"/>
<!-- Global or Custom Workspace Model Libraries -->
<workspace.bridgeInitializer
implementation="com.intellij.workspaceModel.ide.impl.legacyBridge.library.GlobalAndCustomLibraryTableBridgeInitializer"/>
<projectService serviceInterface="com.intellij.openapi.roots.FileIndexFacade"
serviceImplementation="com.intellij.openapi.roots.impl.ProjectFileIndexFacade"/>
@@ -1,12 +1,72 @@
// 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.roots.impl.libraries
import com.intellij.openapi.Disposable
import com.intellij.openapi.roots.libraries.CustomLibraryTable
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.LibraryTable
import com.intellij.openapi.roots.libraries.LibraryTablePresentation
import com.intellij.openapi.util.Disposer
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.CustomLibraryTableBridgeImpl
import com.intellij.workspaceModel.ide.legacyBridge.CustomLibraryTableBridge
import org.jdom.Element
class CustomLibraryTableImpl(private val level: String, private val presentation: LibraryTablePresentation) : LibraryTableBase() {
class CustomLibraryTableImpl(level: String, presentation: LibraryTablePresentation) : CustomLibraryTable, Disposable {
private val delegate: CustomLibraryTable = if (CustomLibraryTableBridge.isEnabled()) {
CustomLibraryTableBridgeImpl(level, presentation)
}
else {
LegacyCustomLibraryTable(level, presentation)
}
override fun getLibraries(): Array<Library> = delegate.libraries
override fun createLibrary(): Library = delegate.createLibrary()
override fun createLibrary(name: String?): Library = delegate.createLibrary(name)
override fun removeLibrary(library: Library) = delegate.removeLibrary(library)
override fun getLibraryIterator(): MutableIterator<Library> = delegate.getLibraryIterator()
override fun getLibraryByName(name: String): Library? = delegate.getLibraryByName(name)
override fun getTableLevel(): String = delegate.tableLevel
override fun getPresentation(): LibraryTablePresentation = delegate.presentation
override fun isEditable(): Boolean = false
override fun getModifiableModel(): LibraryTable.ModifiableModel = delegate.modifiableModel
override fun addListener(listener: LibraryTable.Listener) = delegate.addListener(listener)
override fun addListener(listener: LibraryTable.Listener, parentDisposable: Disposable) = delegate.addListener(listener, parentDisposable)
override fun removeListener(listener: LibraryTable.Listener) = delegate.removeListener(listener)
override fun readExternal(element: Element): Unit = delegate.readExternal(element)
override fun writeExternal(element: Element): Unit = delegate.writeExternal(element)
internal fun getDelegate(): LibraryTable = delegate
override fun dispose() {
if (delegate is Disposable) Disposer.dispose(delegate)
}
}
private class LegacyCustomLibraryTable(private val level: String, private val presentation: LibraryTablePresentation)
: LibraryTableBase(), CustomLibraryTable {
override fun getTableLevel(): String = level
override fun getPresentation(): LibraryTablePresentation = presentation
override fun isEditable(): Boolean = false
override fun readExternal(element: Element) {
super.readExternal(element)
}
override fun writeExternal(element: Element) {
super.writeExternal(element)
}
}
@@ -21,7 +21,7 @@ import java.util.concurrent.ConcurrentHashMap;
final class LibraryTablesRegistrarImpl extends LibraryTablesRegistrar implements Disposable {
private static final ExtensionPointName<CustomLibraryTableDescription> CUSTOM_TABLES_EP = new ExtensionPointName<>("com.intellij.customLibraryTable");
private final Map<String, LibraryTableBase> customLibraryTables = new ConcurrentHashMap<>();
private final Map<String, LibraryTable> customLibraryTables = new ConcurrentHashMap<>();
private volatile boolean extensionLoaded = false;
private final Object extensionLoadingLock = new Object();
@@ -55,7 +55,7 @@ final class LibraryTablesRegistrarImpl extends LibraryTablesRegistrar implements
return getCustomLibrariesMap().get(level);
}
public @NotNull Map<String, LibraryTableBase> getCustomLibrariesMap() {
private @NotNull Map<String, LibraryTable> getCustomLibrariesMap() {
if (extensionLoaded) {
return customLibraryTables;
}
@@ -65,15 +65,15 @@ final class LibraryTablesRegistrarImpl extends LibraryTablesRegistrar implements
CUSTOM_TABLES_EP.getPoint().addExtensionPointListener(new ExtensionPointListener<>() {
@Override
public void extensionAdded(@NotNull CustomLibraryTableDescription extension, @NotNull PluginDescriptor pluginDescriptor) {
LibraryTableBase table = new CustomLibraryTableImpl(extension.getTableLevel(), extension.getPresentation());
LibraryTable table = new CustomLibraryTableImpl(extension.getTableLevel(), extension.getPresentation());
customLibraryTables.put(extension.getTableLevel(), table);
}
@Override
public void extensionRemoved(@NotNull CustomLibraryTableDescription extension, @NotNull PluginDescriptor pluginDescriptor) {
LibraryTableBase table = customLibraryTables.remove(extension.getTableLevel());
if (table != null) {
Disposer.dispose(table);
LibraryTable table = customLibraryTables.remove(extension.getTableLevel());
if (table instanceof Disposable disposable) {
Disposer.dispose(disposable);
}
}
}, true, null);
@@ -90,8 +90,10 @@ final class LibraryTablesRegistrarImpl extends LibraryTablesRegistrar implements
@Override
public void dispose() {
for (LibraryTableBase value : customLibraryTables.values()) {
Disposer.dispose(value);
for (LibraryTable table : customLibraryTables.values()) {
if (table instanceof Disposable disposable) {
Disposer.dispose(disposable);
}
}
customLibraryTables.clear();
}
@@ -9,6 +9,7 @@ import com.intellij.openapi.vfs.newvfs.events.*
import com.intellij.platform.backend.workspace.WorkspaceModel
import com.intellij.platform.backend.workspace.virtualFile
import com.intellij.platform.workspace.jps.entities.LibraryEntity
import com.intellij.platform.workspace.jps.entities.LibraryTableId
import com.intellij.platform.workspace.storage.EntityReference
import com.intellij.platform.workspace.storage.EntityStorage
import com.intellij.platform.workspace.storage.WorkspaceEntity
@@ -20,6 +21,8 @@ import com.intellij.util.io.URLUtil
import com.intellij.workspaceModel.core.fileIndex.EntityStorageKind
import com.intellij.workspaceModel.ide.getInstance
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.GlobalLibraryTableBridgeImpl
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.LibraryBridgeImpl
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryTableBridgeImpl.Companion.libraryMap
import com.intellij.workspaceModel.ide.legacyBridge.GlobalLibraryTableBridge
import java.util.*
@@ -190,15 +193,14 @@ private class VfsChangeApplierImpl(
}
indexData.updateDirtyEntities()
// Keep old behaviour for global libraries
// Keep old behaviour for global and custom libraries
if (affectedEntities.isNotEmpty()) {
val entityStorage = WorkspaceModel.getInstance(project).currentSnapshot
val globalLibraryTableBridge = GlobalLibraryTableBridge.getInstance() as GlobalLibraryTableBridgeImpl
affectedEntities.forEach { entityRef ->
val libraryEntity = (entityRef.resolve(entityStorage) as? LibraryEntity) ?: return@forEach
if (libraryEntity.tableId.level != LibraryTablesRegistrar.APPLICATION_LEVEL) return@forEach
globalLibraryTableBridge.fireRootSetChanged(libraryEntity, entityStorage)
if (libraryEntity.tableId !is LibraryTableId.GlobalLibraryTableId) return@forEach
(entityStorage.libraryMap.getDataByEntity(libraryEntity) as? LibraryBridgeImpl)?.fireRootSetChanged()
}
}
}
@@ -11,23 +11,23 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.registry.Registry
import com.intellij.platform.backend.workspace.GlobalWorkspaceModelCache
import com.intellij.workspaceModel.ide.JpsGlobalModelSynchronizer
import com.intellij.platform.backend.workspace.WorkspaceModel
import com.intellij.platform.diagnostic.telemetry.helpers.addElapsedTimeMillis
import com.intellij.platform.diagnostic.telemetry.helpers.addMeasuredTimeMillis
import com.intellij.platform.workspace.jps.JpsGlobalFileEntitySource
import com.intellij.platform.workspace.jps.entities.*
import com.intellij.util.concurrency.annotations.RequiresWriteLock
import com.intellij.workspaceModel.ide.*
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryTableBridgeImpl.Companion.libraryMap
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryTableBridgeImpl.Companion.mutableLibraryMap
import com.intellij.workspaceModel.ide.legacyBridge.GlobalLibraryTableBridge
import com.intellij.platform.workspace.storage.*
import com.intellij.platform.workspace.storage.impl.VersionedEntityStorageImpl
import com.intellij.platform.workspace.storage.impl.assertConsistency
import com.intellij.platform.workspace.storage.url.VirtualFileUrl
import com.intellij.platform.workspace.storage.url.VirtualFileUrlManager
import com.intellij.util.concurrency.ThreadingAssertions
import com.intellij.util.concurrency.annotations.RequiresWriteLock
import com.intellij.workspaceModel.ide.JpsGlobalModelSynchronizer
import com.intellij.workspaceModel.ide.getGlobalInstance
import com.intellij.workspaceModel.ide.getInstance
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryTableBridgeImpl.Companion.libraryMap
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryTableBridgeImpl.Companion.mutableLibraryMap
import com.intellij.workspaceModel.ide.legacyBridge.GlobalEntityBridgeAndEventHandler
import com.intellij.workspaceModel.ide.legacyBridge.GlobalSdkTableBridge
import io.opentelemetry.api.metrics.Meter
import org.jetbrains.annotations.ApiStatus
@@ -143,27 +143,25 @@ class GlobalWorkspaceModel : Disposable {
@RequiresWriteLock
private fun initializeBridges(change: Map<Class<*>, List<EntityChange<*>>>, builder: MutableEntityStorage) {
ThreadingAssertions.assertWriteAccess()
logErrorOnEventHandling {
GlobalLibraryTableBridge.getInstance().initializeLibraryBridges(change, builder)
}
logErrorOnEventHandling {
GlobalSdkTableBridge.getInstance().initializeSdkBridges(change, builder)
GlobalEntityBridgeAndEventHandler.getAllGlobalEntityHandlers().forEach {
logErrorOnEventHandling {
it.initializeBridges(change, builder)
}
}
}
private fun onBeforeChanged(change: VersionedStorageChange) {
ThreadingAssertions.assertWriteAccess()
GlobalSdkTableBridge.getInstance().handleBeforeChangeEvents(change)
GlobalLibraryTableBridge.getInstance().handleBeforeChangeEvents(change)
GlobalEntityBridgeAndEventHandler.getAllGlobalEntityHandlers().forEach { it.handleBeforeChangeEvents(change) }
}
@RequiresWriteLock
private fun onChanged(change: VersionedStorageChange) {
ThreadingAssertions.assertWriteAccess()
GlobalSdkTableBridge.getInstance().handleChangedEvents(change)
GlobalLibraryTableBridge.getInstance().handleChangedEvents(change)
GlobalEntityBridgeAndEventHandler.getAllGlobalEntityHandlers().forEach { it.handleChangedEvents(change) }
globalWorkspaceModelCache?.scheduleCacheSave()
isFromGlobalWorkspaceModel = true
@@ -212,6 +210,7 @@ class GlobalWorkspaceModel : Disposable {
private fun copyEntitiesToEmptyStorage(storage: EntityStorage, vfuManager: VirtualFileUrlManager): MutableEntityStorage {
val mutableEntityStorage = MutableEntityStorage.create()
// Copying global libraries
// TODO:: Check heree that we also copying custom libraries
storage.entities(LibraryEntity::class.java).forEach { libraryEntity ->
if (!globalEntitiesFilter.invoke(libraryEntity.entitySource)) return@forEach
val libraryRootsCopy = libraryEntity.roots.map { root ->
@@ -0,0 +1,139 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.ide.impl.legacyBridge.library
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.roots.libraries.CustomLibraryTable
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.LibraryTable
import com.intellij.openapi.roots.libraries.LibraryTablePresentation
import com.intellij.openapi.util.Disposer
import com.intellij.platform.workspace.jps.entities.LibraryEntity
import com.intellij.platform.workspace.jps.entities.LibraryTableId
import com.intellij.platform.workspace.jps.serialization.impl.JpsLibraryEntitiesSerializer
import com.intellij.platform.workspace.storage.*
import com.intellij.platform.workspace.storage.url.VirtualFileUrlManager
import com.intellij.workspaceModel.ide.getGlobalInstance
import com.intellij.workspaceModel.ide.impl.GlobalWorkspaceModel
import com.intellij.workspaceModel.ide.legacyBridge.CustomLibraryTableBridge
import org.jdom.Element
import org.jetbrains.jps.model.serialization.library.JpsLibraryTableSerializer
import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer
internal class CustomLibraryTableBridgeImpl(private val level: String, private val presentation: LibraryTablePresentation)
: CustomLibraryTableBridge, CustomLibraryTable, Disposable {
private val libraryTableId = LibraryTableId.GlobalLibraryTableId(tableLevel)
private val libraryTableDelegate = GlobalLibraryTableDelegate(this, libraryTableId)
override fun initializeBridgesAfterLoading(mutableStorage: MutableEntityStorage,
initialEntityStorage: VersionedEntityStorage): () -> Unit {
return libraryTableDelegate.initializeLibraryBridgesAfterLoading(mutableStorage, initialEntityStorage)
}
override fun initializeBridges(changes: Map<Class<*>, List<EntityChange<*>>>, builder: MutableEntityStorage) {
libraryTableDelegate.initializeLibraryBridges(changes, builder)
}
override fun handleBeforeChangeEvents(event: VersionedStorageChange) {
libraryTableDelegate.handleBeforeChangeEvents(event)
}
override fun handleChangedEvents(event: VersionedStorageChange) {
libraryTableDelegate.handleChangedEvents(event)
}
override fun getLibraries(): Array<Library> = libraryTableDelegate.getLibraries()
override fun getLibraryIterator(): Iterator<Library> = getLibraries().iterator()
override fun getLibraryByName(name: String): Library? = libraryTableDelegate.getLibraryByName(name)
override fun createLibrary(): Library = createLibrary(null)
override fun createLibrary(name: String?): Library = libraryTableDelegate.createLibrary(name)
override fun removeLibrary(library: Library): Unit = libraryTableDelegate.removeLibrary(library)
override fun getTableLevel(): String = level
override fun getPresentation(): LibraryTablePresentation = presentation
override fun getModifiableModel(): LibraryTable.ModifiableModel = GlobalOrCustomModifiableLibraryTableBridgeImpl(this)
override fun isEditable(): Boolean = false
override fun dispose() {
if (libraries.isEmpty()) {
Disposer.dispose(libraryTableDelegate)
return
}
val runnable: () -> Unit = {
// We need to remove all related libraries from the [GlobalWorkspaceModel] e.g. extension point and related [CustomLibraryTable] can be unloaded
val modifiableModel = modifiableModel
libraries.forEach { modifiableModel.removeLibrary(it) }
modifiableModel.commit()
Disposer.dispose(libraryTableDelegate)
}
val application = ApplicationManager.getApplication()
if (application.isWriteAccessAllowed) {
runnable.invoke()
}
else {
application.invokeLater {
application.runWriteAction {
runnable.invoke()
}
}
}
}
override fun readExternal(libraryTableTag: Element) {
val mutableEntityStorage = MutableEntityStorage.create()
libraryTableTag.getChildren(JpsLibraryTableSerializer.LIBRARY_TAG).forEach { libraryTag ->
val name = libraryTag.getAttributeValue(JpsModuleRootModelSerializer.NAME_ATTRIBUTE)
val libraryEntity = JpsLibraryEntitiesSerializer.loadLibrary(name, libraryTag, libraryTableId, LegacyCustomLibraryEntitySource,
VirtualFileUrlManager.getGlobalInstance())
mutableEntityStorage.addEntity(libraryEntity)
}
if (!mutableEntityStorage.hasChanges()) return
val runnable: () -> Unit = {
GlobalWorkspaceModel.getInstance().updateModel("Custom library table ${libraryTableId.level} update") { builder ->
builder.replaceBySource({ it is LegacyCustomLibraryEntitySource }, mutableEntityStorage)
}
}
val application = ApplicationManager.getApplication()
if (application.isWriteAccessAllowed) {
runnable.invoke()
}
else {
application.invokeLater {
application.runWriteAction {
runnable.invoke()
}
}
}
}
override fun writeExternal(element: Element) {
GlobalWorkspaceModel.getInstance().currentSnapshot.entities(LibraryEntity::class.java)
.filter { it.tableId == libraryTableId }
.sortedBy { it.name }
.forEach { libraryEntity ->
val libraryTag = JpsLibraryEntitiesSerializer.saveLibrary(libraryEntity, null, false)
element.addContent(libraryTag)
}
}
override fun addListener(listener: LibraryTable.Listener) = libraryTableDelegate.addListener(listener)
override fun addListener(listener: LibraryTable.Listener, parentDisposable: Disposable): Unit = libraryTableDelegate.addListener(listener, parentDisposable)
override fun removeListener(listener: LibraryTable.Listener) = libraryTableDelegate.removeListener(listener)
}
object LegacyCustomLibraryEntitySource: EntitySource
@@ -0,0 +1,60 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.ide.impl.legacyBridge.library
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.LibraryTable
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar
import com.intellij.platform.backend.workspace.BridgeInitializer
import com.intellij.platform.diagnostic.telemetry.helpers.addMeasuredTimeMillis
import com.intellij.platform.workspace.jps.entities.LibraryEntity
import com.intellij.platform.workspace.jps.entities.LibraryTableId
import com.intellij.platform.workspace.storage.EntityChange
import com.intellij.platform.workspace.storage.MutableEntityStorage
import com.intellij.workspaceModel.ide.impl.GlobalWorkspaceModel
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryTableBridgeImpl.Companion.mutableLibraryMap
class GlobalAndCustomLibraryTableBridgeInitializer : BridgeInitializer {
override fun isEnabled(): Boolean = true
// Handle the initialization of all global and custom libraries
override fun initializeBridges(project: Project,
changes: Map<Class<*>, List<EntityChange<*>>>,
builder: MutableEntityStorage) = GlobalLibraryTableBridgeImpl.initializeLibraryBridgesTimeMs.addMeasuredTimeMillis {
val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage
@Suppress("UNCHECKED_CAST")
val libraryChanges = (changes[LibraryEntity::class.java] as? List<EntityChange<LibraryEntity>>) ?: emptyList()
val addChanges = libraryChanges.filterGlobalOrCustomLibraryChanges().filterIsInstance<EntityChange.Added<LibraryEntity>>()
for (addChange in addChanges) {
// Will initialize the bridge if missing
builder.mutableLibraryMap.getOrPutDataByEntity(addChange.entity) {
LibraryBridgeImpl(
libraryTable = getGlobalOrCustomLibraryTable(addChange.entity.symbolicId.tableId.level),
project = null,
initialId = addChange.entity.symbolicId,
initialEntityStorage = entityStorage,
targetBuilder = builder
)
}
}
}
private fun getGlobalOrCustomLibraryTable(tableId: String): LibraryTable {
val libraryTablesRegistrar = LibraryTablesRegistrar.getInstance()
return when (tableId) {
LibraryTablesRegistrar.APPLICATION_LEVEL -> libraryTablesRegistrar.libraryTable
else -> libraryTablesRegistrar.getCustomLibraryTableByLevel(tableId)!!
}
}
}
private fun List<EntityChange<LibraryEntity>>.filterGlobalOrCustomLibraryChanges(): List<EntityChange<LibraryEntity>> {
return filter {
when (it) {
is EntityChange.Added -> it.entity.tableId is LibraryTableId.GlobalLibraryTableId
is EntityChange.Removed -> it.entity.tableId is LibraryTableId.GlobalLibraryTableId
is EntityChange.Replaced -> it.oldEntity.tableId is LibraryTableId.GlobalLibraryTableId
}
}
}
@@ -2,271 +2,76 @@
package com.intellij.workspaceModel.ide.impl.legacyBridge.library
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.LibraryTable
import com.intellij.openapi.roots.libraries.LibraryTablePresentation
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar
import com.intellij.openapi.util.Disposer
import com.intellij.platform.backend.workspace.BridgeInitializer
import com.intellij.platform.diagnostic.telemetry.helpers.addMeasuredTimeMillis
import com.intellij.platform.workspace.jps.entities.LibraryEntity
import com.intellij.platform.workspace.jps.entities.LibraryId
import com.intellij.platform.workspace.jps.entities.LibraryTableId
import com.intellij.platform.workspace.jps.serialization.impl.LibraryNameGenerator
import com.intellij.platform.workspace.storage.*
import com.intellij.platform.workspace.storage.EntityChange
import com.intellij.platform.workspace.storage.MutableEntityStorage
import com.intellij.platform.workspace.storage.VersionedEntityStorage
import com.intellij.platform.workspace.storage.VersionedStorageChange
import com.intellij.projectModel.ProjectModelBundle
import com.intellij.util.EventDispatcher
import com.intellij.workspaceModel.ide.impl.GlobalWorkspaceModel
import com.intellij.workspaceModel.ide.impl.jpsMetrics
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.GlobalLibraryTableBridgeImpl.Companion.initializeLibraryBridgesTimeMs
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryTableBridgeImpl.Companion.libraryMap
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryTableBridgeImpl.Companion.mutableLibraryMap
import com.intellij.workspaceModel.ide.legacyBridge.GlobalLibraryTableBridge
import io.opentelemetry.api.metrics.Meter
import java.util.concurrent.atomic.AtomicLong
class GlobalLibraryTableBridgeInitializer : BridgeInitializer {
override fun isEnabled(): Boolean = true
override fun initializeBridges(project: Project,
changes: Map<Class<*>,
List<EntityChange<*>>>,
builder: MutableEntityStorage) = initializeLibraryBridgesTimeMs.addMeasuredTimeMillis {
val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage
@Suppress("UNCHECKED_CAST")
val libraryChanges = (changes[LibraryEntity::class.java] as? List<EntityChange<LibraryEntity>>) ?: emptyList()
val addChanges = libraryChanges.filterGlobalLibraryChanges().filterIsInstance<EntityChange.Added<LibraryEntity>>()
for (addChange in addChanges) {
// Will initialize the bridge if missing
builder.mutableLibraryMap.getOrPutDataByEntity(addChange.entity) {
LibraryBridgeImpl(
libraryTable = GlobalLibraryTableBridge.getInstance(),
project = null,
initialId = addChange.entity.symbolicId,
initialEntityStorage = entityStorage,
targetBuilder = builder
)
}
}
}
}
class GlobalLibraryTableBridgeImpl : GlobalLibraryTableBridge, Disposable {
private val dispatcher = EventDispatcher.create(LibraryTable.Listener::class.java)
private val libraryTableDelegate = GlobalLibraryTableDelegate(this, LibraryTableId.GlobalLibraryTableId(LibraryTablesRegistrar.APPLICATION_LEVEL))
override fun initializeLibraryBridges(
changes: Map<Class<*>,
List<EntityChange<*>>>,
builder: MutableEntityStorage) = initializeLibraryBridgesTimeMs.addMeasuredTimeMillis {
val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage
@Suppress("UNCHECKED_CAST")
val libraryChanges = (changes[LibraryEntity::class.java] as? List<EntityChange<LibraryEntity>>) ?: emptyList()
val addChanges = libraryChanges.filterGlobalLibraryChanges().filterIsInstance<EntityChange.Added<LibraryEntity>>()
for (addChange in addChanges) {
// Will initialize the bridge if missing
builder.mutableLibraryMap.getOrPutDataByEntity(addChange.entity) {
LibraryBridgeImpl(
libraryTable = this@GlobalLibraryTableBridgeImpl,
project = null,
initialId = addChange.entity.symbolicId,
initialEntityStorage = entityStorage,
targetBuilder = builder
)
}
}
override fun initializeBridges(changes: Map<Class<*>, List<EntityChange<*>>>,
builder: MutableEntityStorage) = initializeLibraryBridgesTimeMs.addMeasuredTimeMillis {
libraryTableDelegate.initializeLibraryBridges(changes, builder)
}
override fun initializeLibraryBridgesAfterLoading(
mutableStorage: MutableEntityStorage,
initialEntityStorage: VersionedEntityStorage
): () -> Unit = initializeLibraryBridgesAfterLoadingTimeMs.addMeasuredTimeMillis {
val libraries = mutableStorage
.entities(LibraryEntity::class.java)
.filter { it.tableId is LibraryTableId.GlobalLibraryTableId }
.filter { mutableStorage.libraryMap.getDataByEntity(it) == null }
.map { libraryEntity ->
Pair(libraryEntity, LibraryBridgeImpl(
libraryTable = this@GlobalLibraryTableBridgeImpl,
project = null,
initialId = libraryEntity.symbolicId,
initialEntityStorage = initialEntityStorage,
targetBuilder = null
))
}
.toList()
LOG.debug("Initial load of application-level libraries")
if (libraries.isEmpty()) {
return@addMeasuredTimeMillis {}
}
for ((entity, library) in libraries) {
mutableStorage.mutableLibraryMap.addIfAbsent(entity, library)
}
val action: () -> Unit = {
// TODO:: Check should we fire this event
val application = ApplicationManager.getApplication()
if (application.isWriteAccessAllowed) {
for ((_, library) in libraries) {
dispatcher.multicaster.afterLibraryAdded(library)
}
}
else {
application.invokeLater {
runWriteAction {
for ((_, library) in libraries) {
dispatcher.multicaster.afterLibraryAdded(library)
}
}
}
}
}
return@addMeasuredTimeMillis action
override fun initializeBridgesAfterLoading(mutableStorage: MutableEntityStorage,
initialEntityStorage: VersionedEntityStorage): () -> Unit
= initializeLibraryBridgesAfterLoadingTimeMs.addMeasuredTimeMillis {
return@addMeasuredTimeMillis libraryTableDelegate.initializeLibraryBridgesAfterLoading(mutableStorage, initialEntityStorage)
}
override fun handleBeforeChangeEvents(event: VersionedStorageChange) = handleBeforeChangeEventsTimeMs.addMeasuredTimeMillis {
val removeChanges = event.getChanges(LibraryEntity::class.java).filterGlobalLibraryChanges()
.filterIsInstance<EntityChange.Removed<LibraryEntity>>()
if (removeChanges.isEmpty()) return@addMeasuredTimeMillis
for (change in removeChanges) {
val library = event.storageBefore.libraryMap.getDataByEntity(change.entity)
LOG.debug { "Fire 'beforeLibraryRemoved' event for ${change.entity.name}, library = $library" }
if (library != null) {
dispatcher.multicaster.beforeLibraryRemoved(library)
}
}
libraryTableDelegate.handleBeforeChangeEvents(event)
}
override fun handleChangedEvents(event: VersionedStorageChange) = handleChangedEventsTimeMs.addMeasuredTimeMillis {
val changes = event.getChanges(LibraryEntity::class.java)
.filterGlobalLibraryChanges()
// Since the listener is not deprecated, it will be better to keep the order of events as remove -> replace -> add
.orderToRemoveReplaceAdd()
if (changes.isEmpty()) return@addMeasuredTimeMillis
val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage
for (change in changes) {
LOG.debug { "Process global library change $change" }
when (change) {
is EntityChange.Added -> {
val alreadyCreatedLibrary = event.storageAfter.libraryMap.getDataByEntity(change.entity) as? LibraryBridgeImpl
?: error("Library bridge should be created in `before` method")
alreadyCreatedLibrary.entityStorage = entityStorage
alreadyCreatedLibrary.clearTargetBuilder()
dispatcher.multicaster.afterLibraryAdded(alreadyCreatedLibrary)
}
is EntityChange.Removed -> {
val library = event.storageBefore.libraryMap.getDataByEntity(change.entity)
if (library != null) {
// TODO There won't be any content in libraryImpl as EntityStore's current was already changed
dispatcher.multicaster.afterLibraryRemoved(library)
Disposer.dispose(library)
}
}
is EntityChange.Replaced -> {
val idBefore = change.oldEntity.symbolicId
val idAfter = change.newEntity.symbolicId
if (idBefore != idAfter) {
val library = event.storageBefore.libraryMap.getDataByEntity(change.oldEntity) as? LibraryBridgeImpl
if (library != null) {
library.entityId = idAfter
dispatcher.multicaster.afterLibraryRenamed(library, LibraryNameGenerator.getLegacyLibraryName(idBefore))
}
}
}
}
}
}
fun fireRootSetChanged(libraryEntity: LibraryEntity, entityStorage: EntityStorage) {
(entityStorage.libraryMap.getDataByEntity(libraryEntity) as? LibraryBridgeImpl)?.fireRootSetChanged()
libraryTableDelegate.handleChangedEvents(event)
}
override fun getLibraries(): Array<Library> = getLibrariesTimeMs.addMeasuredTimeMillis {
val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage
val storage = entityStorage.current
val libraryEntitySequence = storage.entities(
LibraryEntity::class.java).filter { it.tableId::class == LibraryTableId.GlobalLibraryTableId::class }.toList()
val libs: Array<Library> = libraryEntitySequence
.mapNotNull { storage.libraryMap.getDataByEntity(it) }
.toList().toTypedArray()
return@addMeasuredTimeMillis libs
return@addMeasuredTimeMillis libraryTableDelegate.getLibraries()
}
override fun createLibrary(): Library {
return createLibrary(null)
}
override fun createLibrary(name: String?): Library = createLibraryTimeMs.addMeasuredTimeMillis {
if (name == null) error("Creating unnamed global libraries is unsupported")
if (getLibraryByName(name) != null) {
error("Application library named $name already exists")
}
val modifiableModel = modifiableModel
modifiableModel.createLibrary(name)
modifiableModel.commit()
val newLibrary = getLibraryByName(name)
if (newLibrary == null) {
error("Library $name was not created")
}
return@addMeasuredTimeMillis newLibrary
}
override fun removeLibrary(library: Library) {
val modifiableModel = modifiableModel
modifiableModel.removeLibrary(library)
modifiableModel.commit()
}
override fun getLibraryIterator(): Iterator<Library> {
return libraries.iterator()
}
override fun getLibraryIterator(): Iterator<Library> = libraries.iterator()
override fun getLibraryByName(name: String): Library? = getLibraryByNameTimeMs.addMeasuredTimeMillis {
val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage
val libraryId = LibraryId(name, LibraryTableId.GlobalLibraryTableId(LibraryTablesRegistrar.APPLICATION_LEVEL))
val library = entityStorage.current.resolve(libraryId)?.let { entity ->
entityStorage.current.libraryMap.getDataByEntity(entity)
}
return@addMeasuredTimeMillis library
return@addMeasuredTimeMillis libraryTableDelegate.getLibraryByName(name)
}
override fun createLibrary(): Library = createLibrary(null)
override fun createLibrary(name: String?): Library = createLibraryTimeMs.addMeasuredTimeMillis {
return@addMeasuredTimeMillis libraryTableDelegate.createLibrary(name)
}
override fun removeLibrary(library: Library): Unit = libraryTableDelegate.removeLibrary(library)
override fun getTableLevel(): String = LibraryTablesRegistrar.APPLICATION_LEVEL
override fun getPresentation(): LibraryTablePresentation = GLOBAL_LIBRARY_TABLE_PRESENTATION
override fun getModifiableModel(): LibraryTable.ModifiableModel = GlobalModifiableLibraryTableBridgeImpl(this)
override fun dispose() {
for (library in libraries) {
Disposer.dispose(library)
}
}
override fun getModifiableModel(): LibraryTable.ModifiableModel = GlobalOrCustomModifiableLibraryTableBridgeImpl(this)
override fun addListener(listener: LibraryTable.Listener) = dispatcher.addListener(listener)
override fun addListener(listener: LibraryTable.Listener, parentDisposable: Disposable) {
dispatcher.addListener(listener, parentDisposable)
}
override fun dispose(): Unit = Disposer.dispose(libraryTableDelegate)
override fun removeListener(listener: LibraryTable.Listener) = dispatcher.removeListener(listener)
override fun addListener(listener: LibraryTable.Listener) = libraryTableDelegate.addListener(listener)
override fun addListener(listener: LibraryTable.Listener, parentDisposable: Disposable): Unit = libraryTableDelegate.addListener(listener, parentDisposable)
override fun removeListener(listener: LibraryTable.Listener) = libraryTableDelegate.removeListener(listener)
companion object {
private val GLOBAL_LIBRARY_TABLE_PRESENTATION: LibraryTablePresentation = object : LibraryTablePresentation() {
@@ -283,8 +88,6 @@ class GlobalLibraryTableBridgeImpl : GlobalLibraryTableBridge, Disposable {
}
}
private val LOG = logger<GlobalLibraryTableBridgeImpl>()
internal val initializeLibraryBridgesTimeMs: AtomicLong = AtomicLong()
private val initializeLibraryBridgesAfterLoadingTimeMs: AtomicLong = AtomicLong()
private val handleBeforeChangeEventsTimeMs: AtomicLong = AtomicLong()
@@ -322,13 +125,3 @@ class GlobalLibraryTableBridgeImpl : GlobalLibraryTableBridge, Disposable {
}
}
}
private fun List<EntityChange<LibraryEntity>>.filterGlobalLibraryChanges(): List<EntityChange<LibraryEntity>> {
return filter {
when (it) {
is EntityChange.Added -> it.entity.tableId is LibraryTableId.GlobalLibraryTableId
is EntityChange.Removed -> it.entity.tableId is LibraryTableId.GlobalLibraryTableId
is EntityChange.Replaced -> it.oldEntity.tableId is LibraryTableId.GlobalLibraryTableId
}
}
}
@@ -0,0 +1,224 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.ide.impl.legacyBridge.library
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.LibraryTable
import com.intellij.openapi.util.Disposer
import com.intellij.platform.workspace.jps.entities.LibraryEntity
import com.intellij.platform.workspace.jps.entities.LibraryId
import com.intellij.platform.workspace.jps.entities.LibraryTableId
import com.intellij.platform.workspace.jps.serialization.impl.LibraryNameGenerator
import com.intellij.platform.workspace.storage.*
import com.intellij.util.EventDispatcher
import com.intellij.workspaceModel.ide.impl.GlobalWorkspaceModel
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryTableBridgeImpl.Companion.libraryMap
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryTableBridgeImpl.Companion.mutableLibraryMap
internal class GlobalLibraryTableDelegate(private val libraryTable: LibraryTable, private val libraryTableId: LibraryTableId): Disposable {
private val dispatcher = EventDispatcher.create(LibraryTable.Listener::class.java)
internal fun initializeLibraryBridges(changes: Map<Class<*>, List<EntityChange<*>>>, builder: MutableEntityStorage) {
val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage
@Suppress("UNCHECKED_CAST")
val libraryChanges = (changes[LibraryEntity::class.java] as? List<EntityChange<LibraryEntity>>) ?: emptyList()
val addChanges = libraryChanges.filterLibraryChanges(libraryTableId).filterIsInstance<EntityChange.Added<LibraryEntity>>()
for (addChange in addChanges) {
// Will initialize the bridge if missing
builder.mutableLibraryMap.getOrPutDataByEntity(addChange.entity) {
LibraryBridgeImpl(
libraryTable = libraryTable,
project = null,
initialId = addChange.entity.symbolicId,
initialEntityStorage = entityStorage,
targetBuilder = builder
)
}
}
}
internal fun initializeLibraryBridgesAfterLoading(mutableStorage: MutableEntityStorage,
initialEntityStorage: VersionedEntityStorage): () -> Unit {
val libraries = mutableStorage
.entities(LibraryEntity::class.java)
.filter { it.tableId == libraryTableId }
.filter { mutableStorage.libraryMap.getDataByEntity(it) == null }
.map { libraryEntity ->
Pair(libraryEntity, LibraryBridgeImpl(
libraryTable = libraryTable,
project = null,
initialId = libraryEntity.symbolicId,
initialEntityStorage = initialEntityStorage,
targetBuilder = null
))
}
.toList()
LOG.debug("Initial load of ${libraryTableId.level}-level libraries")
if (libraries.isEmpty()) {
return {}
}
for ((entity, library) in libraries) {
mutableStorage.mutableLibraryMap.addIfAbsent(entity, library)
}
val action: () -> Unit = {
// TODO:: Check should we fire this event
val application = ApplicationManager.getApplication()
if (application.isWriteAccessAllowed) {
for ((_, library) in libraries) {
dispatcher.multicaster.afterLibraryAdded(library)
}
}
else {
application.invokeLater {
runWriteAction {
for ((_, library) in libraries) {
dispatcher.multicaster.afterLibraryAdded(library)
}
}
}
}
}
return action
}
internal fun handleBeforeChangeEvents(event: VersionedStorageChange) {
val removeChanges = event.getChanges(LibraryEntity::class.java).filterLibraryChanges(libraryTableId)
.filterIsInstance<EntityChange.Removed<LibraryEntity>>()
if (removeChanges.isEmpty()) return
for (change in removeChanges) {
val library = event.storageBefore.libraryMap.getDataByEntity(change.entity)
LOG.debug { "Fire 'beforeLibraryRemoved' event for ${change.entity.name}, library = $library" }
if (library != null) {
dispatcher.multicaster.beforeLibraryRemoved(library)
}
}
}
internal fun handleChangedEvents(event: VersionedStorageChange) {
val changes = event.getChanges(LibraryEntity::class.java).filterLibraryChanges(libraryTableId)
// Since the listener is not deprecated, it will be better to keep the order of events as remove -> replace -> add
.orderToRemoveReplaceAdd()
if (changes.isEmpty()) return
val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage
for (change in changes) {
LOG.debug { "Process ${libraryTableId.level} library change $change" }
when (change) {
is EntityChange.Added -> {
val alreadyCreatedLibrary = event.storageAfter.libraryMap.getDataByEntity(change.entity) as? LibraryBridgeImpl
?: error("Library bridge should be created in `before` method")
alreadyCreatedLibrary.entityStorage = entityStorage
alreadyCreatedLibrary.clearTargetBuilder()
dispatcher.multicaster.afterLibraryAdded(alreadyCreatedLibrary)
}
is EntityChange.Removed -> {
val library = event.storageBefore.libraryMap.getDataByEntity(change.entity)
if (library != null) {
// TODO There won't be any content in libraryImpl as EntityStore's current was already changed
dispatcher.multicaster.afterLibraryRemoved(library)
Disposer.dispose(library)
}
}
is EntityChange.Replaced -> {
val idBefore = change.oldEntity.symbolicId
val idAfter = change.newEntity.symbolicId
if (idBefore != idAfter) {
val library = event.storageBefore.libraryMap.getDataByEntity(change.oldEntity) as? LibraryBridgeImpl
if (library != null) {
library.entityId = idAfter
dispatcher.multicaster.afterLibraryRenamed(library, LibraryNameGenerator.getLegacyLibraryName(idBefore))
}
}
}
}
}
}
fun fireRootSetChanged(libraryEntity: LibraryEntity, entityStorage: EntityStorage) {
(entityStorage.libraryMap.getDataByEntity(libraryEntity) as? LibraryBridgeImpl)?.fireRootSetChanged()
}
internal fun createLibrary(name: String?): Library {
if (name == null) error("Creating unnamed ${libraryTableId.level} libraries is unsupported")
if (getLibraryByName(name) != null) {
error("${libraryTableId.level} library named $name already exists")
}
val modifiableModel = libraryTable.modifiableModel
modifiableModel.createLibrary(name)
modifiableModel.commit()
val newLibrary = getLibraryByName(name)
if (newLibrary == null) {
error("Library $name was not created")
}
return newLibrary
}
internal fun removeLibrary(library: Library) {
val modifiableModel = libraryTable.modifiableModel
modifiableModel.removeLibrary(library)
modifiableModel.commit()
}
internal fun getLibraries(): Array<Library> {
val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage
val storage = entityStorage.current
val libraryEntitySequence = storage.entities(LibraryEntity::class.java).filter { it.tableId == libraryTableId }.toList()
val libs: Array<Library> = libraryEntitySequence.mapNotNull { storage.libraryMap.getDataByEntity(it) }
.toList().toTypedArray()
return libs
}
internal fun getLibraryByName(name: String): Library? {
val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage
val libraryId = LibraryId(name, libraryTableId)
val library = entityStorage.current.resolve(libraryId)?.let { entity ->
entityStorage.current.libraryMap.getDataByEntity(entity)
}
return library
}
override fun dispose() {
for (library in getLibraries()) {
Disposer.dispose(library)
}
}
internal fun addListener(listener: LibraryTable.Listener) = dispatcher.addListener(listener)
internal fun addListener(listener: LibraryTable.Listener, parentDisposable: Disposable) {
dispatcher.addListener(listener, parentDisposable)
}
internal fun removeListener(listener: LibraryTable.Listener) = dispatcher.removeListener(listener)
companion object {
private val LOG = logger<GlobalLibraryTableDelegate>()
}
}
private fun List<EntityChange<LibraryEntity>>.filterLibraryChanges(libraryTableId: LibraryTableId): List<EntityChange<LibraryEntity>> {
return filter {
val tableId = when (it) {
is EntityChange.Added -> it.entity.tableId
is EntityChange.Removed -> it.entity.tableId
is EntityChange.Replaced -> it.oldEntity.tableId
}
tableId == libraryTableId
}
}
@@ -4,7 +4,6 @@ package com.intellij.workspaceModel.ide.impl.legacyBridge.library
import com.intellij.openapi.roots.ProjectModelExternalSource
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.LibraryTable
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar
import com.intellij.openapi.roots.libraries.PersistentLibraryKind
import com.intellij.openapi.util.Disposer
import com.intellij.platform.workspace.jps.entities.LibraryEntity
@@ -20,10 +19,12 @@ import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryT
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryTableBridgeImpl.Companion.mutableLibraryMap
import org.jetbrains.jps.model.serialization.library.JpsLibraryTableSerializer
internal class GlobalModifiableLibraryTableBridgeImpl(private val libraryTable: LibraryTable):
internal class GlobalOrCustomModifiableLibraryTableBridgeImpl(private val libraryTable: LibraryTable):
LegacyBridgeModifiableBase(MutableEntityStorage.from(GlobalWorkspaceModel.getInstance().currentSnapshot), true),
LibraryTable.ModifiableModel {
private val myAddedLibraries = mutableListOf<LibraryBridgeImpl>()
private val libraryTableId = LibraryTableId.GlobalLibraryTableId(libraryTable.tableLevel)
override fun createLibrary(name: String?): Library {
return createLibrary(name = name, type = null)
@@ -34,11 +35,9 @@ internal class GlobalModifiableLibraryTableBridgeImpl(private val libraryTable:
}
override fun createLibrary(name: String?, type: PersistentLibraryKind<*>?, externalSource: ProjectModelExternalSource?): Library {
if (name.isNullOrBlank()) error("Application Library must have a name")
if (name.isNullOrBlank()) error("${libraryTableId.level} library must have a name")
assertModelIsLive()
val libraryTableId = LibraryTableId.GlobalLibraryTableId(LibraryTablesRegistrar.APPLICATION_LEVEL)
val libraryEntity = diff addEntity LibraryEntity(name, libraryTableId, emptyList(),
LegacyBridgeJpsEntitySourceFactory.createEntitySourceForGlobalLibrary())
@@ -77,7 +76,7 @@ internal class GlobalModifiableLibraryTableBridgeImpl(private val libraryTable:
}
override fun commit() {
GlobalWorkspaceModel.getInstance().updateModel("Global library table commit") {
GlobalWorkspaceModel.getInstance().updateModel("${libraryTableId.level} library table commit") {
it.addDiff(diff)
}
libraries.forEach { library -> (library as LibraryBridgeImpl).clearTargetBuilder() }
@@ -86,19 +85,18 @@ internal class GlobalModifiableLibraryTableBridgeImpl(private val libraryTable:
override fun getLibraryIterator(): Iterator<Library> = libraries.iterator()
override fun getLibraryByName(name: String): Library? {
val libraryEntity = diff.resolve(LibraryId(name, LibraryTableId.GlobalLibraryTableId(LibraryTablesRegistrar.APPLICATION_LEVEL))) ?: return null
val libraryEntity = diff.resolve(LibraryId(name, libraryTableId)) ?: return null
return diff.libraryMap.getDataByEntity(libraryEntity)
}
override fun getLibraries(): Array<Library> {
return diff.entities(LibraryEntity::class.java).filter { it.tableId::class == LibraryTableId.GlobalLibraryTableId::class }
return diff.entities(LibraryEntity::class.java).filter { it.tableId == libraryTableId }
.mapNotNull { diff.libraryMap.getDataByEntity(it) }
.toList().toTypedArray()
}
override fun isChanged(): Boolean = diff.hasChanges()
override fun dispose() {
modelIsCommittedOrDisposed = true
@@ -0,0 +1,13 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.ide.legacyBridge
import com.intellij.openapi.roots.libraries.LibraryTable
import com.intellij.openapi.util.registry.Registry
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
interface CustomLibraryTableBridge: GlobalEntityBridgeAndEventHandler, LibraryTable {
companion object {
fun isEnabled(): Boolean = Registry.`is`("workspace.model.custom.library.bridge", true)
}
}
@@ -0,0 +1,31 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.ide.legacyBridge
import com.intellij.openapi.roots.impl.libraries.CustomLibraryTableImpl
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar
import com.intellij.platform.workspace.storage.EntityChange
import com.intellij.platform.workspace.storage.MutableEntityStorage
import com.intellij.platform.workspace.storage.VersionedEntityStorage
import com.intellij.platform.workspace.storage.VersionedStorageChange
interface GlobalEntityBridgeAndEventHandler {
fun initializeBridgesAfterLoading(mutableStorage: MutableEntityStorage, initialEntityStorage: VersionedEntityStorage): () -> Unit
fun initializeBridges(changes: Map<Class<*>, List<EntityChange<*>>>, builder: MutableEntityStorage)
fun handleBeforeChangeEvents(event: VersionedStorageChange)
fun handleChangedEvents(event: VersionedStorageChange)
companion object {
fun getAllGlobalEntityHandlers(): List<GlobalEntityBridgeAndEventHandler> {
val result = mutableListOf<GlobalEntityBridgeAndEventHandler>()
result.add(GlobalLibraryTableBridge.getInstance())
result.add(GlobalSdkTableBridge.getInstance())
if (CustomLibraryTableBridge.isEnabled()) {
LibraryTablesRegistrar.getInstance().customLibraryTables.forEach { customLibraryTable ->
customLibraryTable as CustomLibraryTableImpl
result.add(customLibraryTable.getDelegate() as CustomLibraryTableBridge)
}
}
return result
}
}
}
@@ -4,21 +4,13 @@ package com.intellij.workspaceModel.ide.legacyBridge
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.roots.libraries.LibraryTable
import com.intellij.platform.workspace.storage.EntityChange
import com.intellij.platform.workspace.storage.MutableEntityStorage
import com.intellij.platform.workspace.storage.VersionedEntityStorage
import com.intellij.platform.workspace.storage.VersionedStorageChange
import org.jetbrains.annotations.ApiStatus
/**
* Utility interface to provide bridge behaviour from entities to [com.intellij.openapi.roots.libraries.Library]
*/
@ApiStatus.Internal
interface GlobalLibraryTableBridge : LibraryTable {
fun initializeLibraryBridgesAfterLoading(mutableStorage: MutableEntityStorage, initialEntityStorage: VersionedEntityStorage): () -> Unit
fun initializeLibraryBridges(changes: Map<Class<*>, List<EntityChange<*>>>, builder: MutableEntityStorage)
fun handleBeforeChangeEvents(event: VersionedStorageChange)
fun handleChangedEvents(event: VersionedStorageChange)
interface GlobalLibraryTableBridge : GlobalEntityBridgeAndEventHandler, LibraryTable {
companion object {
fun getInstance(): GlobalLibraryTableBridge = ApplicationManager.getApplication().service()
}
@@ -4,18 +4,10 @@ package com.intellij.workspaceModel.ide.legacyBridge
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.util.registry.Registry
import com.intellij.platform.workspace.storage.EntityChange
import com.intellij.platform.workspace.storage.MutableEntityStorage
import com.intellij.platform.workspace.storage.VersionedEntityStorage
import com.intellij.platform.workspace.storage.VersionedStorageChange
interface GlobalSdkTableBridge {
fun initializeSdkBridgesAfterLoading(mutableStorage: MutableEntityStorage,
initialEntityStorage: VersionedEntityStorage): () -> Unit
fun initializeSdkBridges(changes: Map<Class<*>, List<EntityChange<*>>>, builder: MutableEntityStorage)
fun handleBeforeChangeEvents(event: VersionedStorageChange)
fun handleChangedEvents(event: VersionedStorageChange)
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
interface GlobalSdkTableBridge: GlobalEntityBridgeAndEventHandler {
companion object {
fun getInstance(): GlobalSdkTableBridge = ApplicationManager.getApplication().service()
fun isEnabled(): Boolean = Registry.`is`("workspace.model.global.sdk.bridge", true)
@@ -72,8 +72,10 @@ internal class JpsGlobalLibrariesFileSerializer(entitySource: JpsGlobalFileEntit
JpsFileEntityTypeSerializer<LibraryEntity> {
override val isExternalStorage: Boolean
get() = false
/* Working only with global libraries omitting the custom one */
override val entityFilter: (LibraryEntity) -> Boolean
get() = { it.tableId is LibraryTableId.GlobalLibraryTableId }
get() = { it.tableId == libraryTableId }
override fun deleteObsoleteFile(fileUrl: String, writer: JpsFileContentWriter) {
writer.saveComponent(fileUrl, LIBRARY_TABLE_COMPONENT_NAME, null)
@@ -122,7 +124,7 @@ internal class JpsLibrariesExternalFileSerializer(private val externalFile: JpsP
}
}
internal open class JpsLibraryEntitiesSerializer(override val fileUrl: VirtualFileUrl,
open class JpsLibraryEntitiesSerializer(override val fileUrl: VirtualFileUrl,
override val internalEntitySource: JpsFileEntitySource,
protected val libraryTableId: LibraryTableId) : JpsFileEntitiesSerializer<LibraryEntity> {
open val isExternalStorage: Boolean
@@ -198,6 +200,117 @@ internal open class JpsLibraryEntitiesSerializer(override val fileUrl: VirtualFi
override fun toString(): String = "${javaClass.simpleName.substringAfterLast('.')}($fileUrl)"
companion object {
fun saveLibrary(library: LibraryEntity, externalSystemId: String?, isExternalStorage: Boolean): Element {
val libraryTag = Element(LIBRARY_TAG)
val legacyName = LibraryNameGenerator.getLegacyLibraryName(library.symbolicId)
if (legacyName != null) {
libraryTag.setAttribute(NAME_ATTRIBUTE, legacyName)
}
val customProperties = library.libraryProperties
if (customProperties != null) {
libraryTag.setAttribute(TYPE_ATTRIBUTE, customProperties.libraryType)
val propertiesXmlTag = customProperties.propertiesXmlTag
if (propertiesXmlTag != null) {
libraryTag.addContent(JDOMUtil.load(propertiesXmlTag))
}
}
if (externalSystemId != null) {
val attributeName =
if (isExternalStorage) SerializationConstants.EXTERNAL_SYSTEM_ID_ATTRIBUTE
else SerializationConstants.EXTERNAL_SYSTEM_ID_IN_INTERNAL_STORAGE_ATTRIBUTE
libraryTag.setAttribute(attributeName, externalSystemId)
}
val rootsMap = library.roots.groupByTo(HashMap()) { it.type }
ROOT_TYPES_TO_WRITE_EMPTY_TAG.forEach {
rootsMap.putIfAbsent(it, ArrayList())
}
val jarDirectoriesTags = ArrayList<Element>()
rootsMap.entries.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.key.name }).forEach { (rootType, roots) ->
val rootTypeTag = Element(rootType.name)
roots.forEach {
rootTypeTag.addContent(Element(ROOT_TAG).setAttribute(JpsModuleRootModelSerializer.URL_ATTRIBUTE, it.url.url))
}
roots.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.url.url }).forEach {
if (it.inclusionOptions != LibraryRoot.InclusionOptions.ROOT_ITSELF) {
val jarDirectoryTag = Element(JAR_DIRECTORY_TAG)
jarDirectoryTag.setAttribute(JpsModuleRootModelSerializer.URL_ATTRIBUTE, it.url.url)
jarDirectoryTag.setAttribute(RECURSIVE_ATTRIBUTE,
(it.inclusionOptions == LibraryRoot.InclusionOptions.ARCHIVES_UNDER_ROOT_RECURSIVELY).toString())
if (rootType.name != DEFAULT_JAR_DIRECTORY_TYPE) {
jarDirectoryTag.setAttribute(TYPE_ATTRIBUTE, rootType.name)
}
jarDirectoriesTags.add(jarDirectoryTag)
}
}
libraryTag.addContent(rootTypeTag)
}
val excludedRoots = library.excludedRoots
if (excludedRoots.isNotEmpty()) {
val excludedTag = Element("excluded")
excludedRoots.forEach {
excludedTag.addContent(Element(ROOT_TAG).setAttribute(JpsModuleRootModelSerializer.URL_ATTRIBUTE, it.url.url))
}
libraryTag.addContent(excludedTag)
}
jarDirectoriesTags.forEach {
libraryTag.addContent(it)
}
return libraryTag
}
fun loadLibrary(name: String, libraryElement: Element, libraryTableId: LibraryTableId, source: EntitySource,
virtualFileManager: VirtualFileUrlManager): LibraryEntity {
val roots = ArrayList<LibraryRoot>()
val excludedRoots = ArrayList<VirtualFileUrl>()
val jarDirectories = libraryElement.getChildren(JAR_DIRECTORY_TAG).associateBy(
{
Pair(it.getAttributeValue(JpsModuleRootModelSerializer.TYPE_ATTRIBUTE) ?: DEFAULT_JAR_DIRECTORY_TYPE,
it.getAttributeValueStrict(JpsModuleRootModelSerializer.URL_ATTRIBUTE))
},
{
if (it.getAttributeValue(RECURSIVE_ATTRIBUTE)?.toBoolean() == true) LibraryRoot.InclusionOptions.ARCHIVES_UNDER_ROOT_RECURSIVELY
else LibraryRoot.InclusionOptions.ARCHIVES_UNDER_ROOT
}
)
val type = libraryElement.getAttributeValue("type")
var properties: String? = null
for (childElement in libraryElement.children) {
when (childElement.name) {
"excluded" -> excludedRoots.addAll(
childElement.getChildren(JpsJavaModelSerializerExtension.ROOT_TAG)
.map { it.getAttributeValueStrict(JpsModuleRootModelSerializer.URL_ATTRIBUTE) }
.map { virtualFileManager.fromUrl(it) }
)
PROPERTIES_TAG -> {
properties = JDOMUtil.write(childElement)
}
JAR_DIRECTORY_TAG -> {
}
else -> {
val rootType = childElement.name
for (rootTag in childElement.getChildren(JpsJavaModelSerializerExtension.ROOT_TAG)) {
val url = rootTag.getAttributeValueStrict(JpsModuleRootModelSerializer.URL_ATTRIBUTE)
val inclusionOptions = jarDirectories[Pair(rootType, url)] ?: LibraryRoot.InclusionOptions.ROOT_ITSELF
roots.add(LibraryRoot(virtualFileManager.fromUrl(url), libraryRootTypes[rootType]!!, inclusionOptions))
}
}
}
}
val libProperties = type?.let {
LibraryPropertiesEntity(type, source) {
this.propertiesXmlTag = properties
}
}
val excludes = excludedRoots.map { ExcludeUrlEntity(it, source) }
val libraryEntity = LibraryEntity(name, libraryTableId, roots, source) {
this.excludedRoots = excludes
this.libraryProperties = libProperties
}
return libraryEntity
}
private val loadEntitiesTimeMs: AtomicLong = AtomicLong()
private val saveEntitiesTimeMs: AtomicLong = AtomicLong()
@@ -221,122 +334,6 @@ internal open class JpsLibraryEntitiesSerializer(override val fileUrl: VirtualFi
}
private const val DEFAULT_JAR_DIRECTORY_TYPE = "CLASSES"
internal fun loadLibrary(name: String,
libraryElement: Element,
libraryTableId: LibraryTableId,
source: EntitySource,
virtualFileManager: VirtualFileUrlManager): LibraryEntity {
val roots = ArrayList<LibraryRoot>()
val excludedRoots = ArrayList<VirtualFileUrl>()
val jarDirectories = libraryElement.getChildren(JAR_DIRECTORY_TAG).associateBy(
{
Pair(it.getAttributeValue(JpsModuleRootModelSerializer.TYPE_ATTRIBUTE) ?: DEFAULT_JAR_DIRECTORY_TYPE,
it.getAttributeValueStrict(JpsModuleRootModelSerializer.URL_ATTRIBUTE))
},
{
if (it.getAttributeValue(RECURSIVE_ATTRIBUTE)?.toBoolean() == true) LibraryRoot.InclusionOptions.ARCHIVES_UNDER_ROOT_RECURSIVELY
else LibraryRoot.InclusionOptions.ARCHIVES_UNDER_ROOT
}
)
val type = libraryElement.getAttributeValue("type")
var properties: String? = null
for (childElement in libraryElement.children) {
when (childElement.name) {
"excluded" -> excludedRoots.addAll(
childElement.getChildren(JpsJavaModelSerializerExtension.ROOT_TAG)
.map { it.getAttributeValueStrict(JpsModuleRootModelSerializer.URL_ATTRIBUTE) }
.map { virtualFileManager.fromUrl(it) }
)
PROPERTIES_TAG -> {
properties = JDOMUtil.write(childElement)
}
JAR_DIRECTORY_TAG -> {
}
else -> {
val rootType = childElement.name
for (rootTag in childElement.getChildren(JpsJavaModelSerializerExtension.ROOT_TAG)) {
val url = rootTag.getAttributeValueStrict(JpsModuleRootModelSerializer.URL_ATTRIBUTE)
val inclusionOptions = jarDirectories[Pair(rootType, url)] ?: LibraryRoot.InclusionOptions.ROOT_ITSELF
roots.add(LibraryRoot(virtualFileManager.fromUrl(url), libraryRootTypes[rootType]!!, inclusionOptions))
}
}
}
}
val libProperties = type?.let {
LibraryPropertiesEntity(type, source) {
this.propertiesXmlTag = properties
}
}
val excludes = excludedRoots.map { ExcludeUrlEntity(it, source) }
val libraryEntity = LibraryEntity(name, libraryTableId, roots, source) {
this.excludedRoots = excludes
this.libraryProperties = libProperties
}
return libraryEntity
}
private val libraryRootTypes = ConcurrentFactoryMap.createMap<String, LibraryRootTypeId> { LibraryRootTypeId(it) }
internal fun saveLibrary(library: LibraryEntity, externalSystemId: String?, isExternalStorage: Boolean): Element {
val libraryTag = Element(LIBRARY_TAG)
val legacyName = LibraryNameGenerator.getLegacyLibraryName(library.symbolicId)
if (legacyName != null) {
libraryTag.setAttribute(NAME_ATTRIBUTE, legacyName)
}
val customProperties = library.libraryProperties
if (customProperties != null) {
libraryTag.setAttribute(TYPE_ATTRIBUTE, customProperties.libraryType)
val propertiesXmlTag = customProperties.propertiesXmlTag
if (propertiesXmlTag != null) {
libraryTag.addContent(JDOMUtil.load(propertiesXmlTag))
}
}
if (externalSystemId != null) {
val attributeName =
if (isExternalStorage) SerializationConstants.EXTERNAL_SYSTEM_ID_ATTRIBUTE
else SerializationConstants.EXTERNAL_SYSTEM_ID_IN_INTERNAL_STORAGE_ATTRIBUTE
libraryTag.setAttribute(attributeName, externalSystemId)
}
val rootsMap = library.roots.groupByTo(HashMap()) { it.type }
ROOT_TYPES_TO_WRITE_EMPTY_TAG.forEach {
rootsMap.putIfAbsent(it, ArrayList())
}
val jarDirectoriesTags = ArrayList<Element>()
rootsMap.entries.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.key.name }).forEach { (rootType, roots) ->
val rootTypeTag = Element(rootType.name)
roots.forEach {
rootTypeTag.addContent(Element(ROOT_TAG).setAttribute(JpsModuleRootModelSerializer.URL_ATTRIBUTE, it.url.url))
}
roots.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.url.url }).forEach {
if (it.inclusionOptions != LibraryRoot.InclusionOptions.ROOT_ITSELF) {
val jarDirectoryTag = Element(JAR_DIRECTORY_TAG)
jarDirectoryTag.setAttribute(JpsModuleRootModelSerializer.URL_ATTRIBUTE, it.url.url)
jarDirectoryTag.setAttribute(RECURSIVE_ATTRIBUTE,
(it.inclusionOptions == LibraryRoot.InclusionOptions.ARCHIVES_UNDER_ROOT_RECURSIVELY).toString())
if (rootType.name != DEFAULT_JAR_DIRECTORY_TYPE) {
jarDirectoryTag.setAttribute(TYPE_ATTRIBUTE, rootType.name)
}
jarDirectoriesTags.add(jarDirectoryTag)
}
}
libraryTag.addContent(rootTypeTag)
}
val excludedRoots = library.excludedRoots
if (excludedRoots.isNotEmpty()) {
val excludedTag = Element("excluded")
excludedRoots.forEach {
excludedTag.addContent(Element(ROOT_TAG).setAttribute(JpsModuleRootModelSerializer.URL_ATTRIBUTE, it.url.url))
}
libraryTag.addContent(excludedTag)
}
jarDirectoriesTags.forEach {
libraryTag.addContent(it)
}
return libraryTag
}
private val ROOT_TYPES_TO_WRITE_EMPTY_TAG = listOf("CLASSES", "SOURCES", "JAVADOC").map { libraryRootTypes[it]!! }
@@ -442,7 +442,7 @@ internal open class ModuleImlFileEntitiesSerializer(internal val modulePath: Mod
val name = LibraryNameGenerator.generateUniqueLibraryName(originalName) { it in moduleLibraryNames }
moduleLibraryNames.add(name)
val tableId = LibraryTableId.ModuleLibraryTableId(moduleId)
val library = loadLibrary(name, libraryElement, tableId, contentRootEntitySource, virtualFileManager)
val library = JpsLibraryEntitiesSerializer.loadLibrary(name, libraryElement, tableId, contentRootEntitySource, virtualFileManager)
val libraryId = LibraryId(name, tableId)
moduleLibrariesCollector[libraryId] = library
ModuleDependencyItem.Exportable.LibraryDependency(libraryId, dependencyElement.isExported(), dependencyElement.readScope())
@@ -816,7 +816,7 @@ internal open class ModuleImlFileEntitiesSerializer(internal val modulePath: Mod
if (moduleLibrary != null) {
createOrderEntryTag(MODULE_LIBRARY_TYPE).apply {
setExportedAndScopeAttributes(dependencyItem)
addContent(saveLibrary(moduleLibrary, null, false))
addContent(JpsLibraryEntitiesSerializer.saveLibrary(moduleLibrary, null, false))
}
}
else {