diff --git a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/project/ProjectRootManagerBridge.kt b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/project/ProjectRootManagerBridge.kt index c29fa25f0376..6a03c1a62f63 100644 --- a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/project/ProjectRootManagerBridge.kt +++ b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/project/ProjectRootManagerBridge.kt @@ -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) { diff --git a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/sdk/GlobalSdkBridgesLoader.kt b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/sdk/GlobalSdkBridgesLoader.kt index 325aa02168ee..26344ea1de52 100644 --- a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/sdk/GlobalSdkBridgesLoader.kt +++ b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/sdk/GlobalSdkBridgesLoader.kt @@ -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, List>>, builder: MutableEntityStorage) { + override fun initializeBridges(changes: Map, List>>, builder: MutableEntityStorage) { @Suppress("UNCHECKED_CAST") val sdkChanges = (changes[SdkEntity::class.java] as? List>) ?: emptyList() val addChanges = sdkChanges.filterIsInstance>() diff --git a/platform/lang-impl/testSources/com/intellij/openapi/roots/LibraryTableTestCase.kt b/platform/lang-impl/testSources/com/intellij/openapi/roots/LibraryTableTestCase.kt index eca3dde2a7df..69801ab12329 100644 --- a/platform/lang-impl/testSources/com/intellij/openapi/roots/LibraryTableTestCase.kt +++ b/platform/lang-impl/testSources/com/intellij/openapi/roots/LibraryTableTestCase.kt @@ -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 diff --git a/platform/platform-impl/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsGlobalModelSynchronizerImpl.kt b/platform/platform-impl/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsGlobalModelSynchronizerImpl.kt index 09f39202e20e..5d280bed7489 100644 --- a/platform/platform-impl/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsGlobalModelSynchronizerImpl.kt +++ b/platform/platform-impl/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsGlobalModelSynchronizerImpl.kt @@ -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() } } } diff --git a/platform/projectModel-api/src/com/intellij/openapi/roots/libraries/CustomLibraryTable.java b/platform/projectModel-api/src/com/intellij/openapi/roots/libraries/CustomLibraryTable.java new file mode 100644 index 000000000000..01fede22dc98 --- /dev/null +++ b/platform/projectModel-api/src/com/intellij/openapi/roots/libraries/CustomLibraryTable.java @@ -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. + *

+ * 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; +} diff --git a/platform/projectModel-impl/resources/META-INF/ProjectModelImpl.xml b/platform/projectModel-impl/resources/META-INF/ProjectModelImpl.xml index c14099a1148d..07df9b2510c7 100644 --- a/platform/projectModel-impl/resources/META-INF/ProjectModelImpl.xml +++ b/platform/projectModel-impl/resources/META-INF/ProjectModelImpl.xml @@ -28,13 +28,19 @@ - - + - + + + + + + diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/CustomLibraryTableImpl.kt b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/CustomLibraryTableImpl.kt index 2b0b93a741d5..4922b35d42e4 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/CustomLibraryTableImpl.kt +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/CustomLibraryTableImpl.kt @@ -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 = 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 = 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) + } } \ No newline at end of file diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryTablesRegistrarImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryTablesRegistrarImpl.java index 83ff13570b0f..595d51c718ce 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryTablesRegistrarImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryTablesRegistrarImpl.java @@ -21,7 +21,7 @@ import java.util.concurrent.ConcurrentHashMap; final class LibraryTablesRegistrarImpl extends LibraryTablesRegistrar implements Disposable { private static final ExtensionPointName CUSTOM_TABLES_EP = new ExtensionPointName<>("com.intellij.customLibraryTable"); - private final Map customLibraryTables = new ConcurrentHashMap<>(); + private final Map 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 getCustomLibrariesMap() { + private @NotNull Map 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(); } diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/core/fileIndex/impl/NonExistingWorkspaceRootsRegistry.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/core/fileIndex/impl/NonExistingWorkspaceRootsRegistry.kt index 4e6a52ae48ca..c4a681ef9a55 100644 --- a/platform/projectModel-impl/src/com/intellij/workspaceModel/core/fileIndex/impl/NonExistingWorkspaceRootsRegistry.kt +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/core/fileIndex/impl/NonExistingWorkspaceRootsRegistry.kt @@ -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() } } } diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/GlobalWorkspaceModel.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/GlobalWorkspaceModel.kt index 3b81abfbca23..6b41a8c3c1bb 100644 --- a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/GlobalWorkspaceModel.kt +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/GlobalWorkspaceModel.kt @@ -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, List>>, 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 -> diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/CustomLibraryTableBridgeImpl.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/CustomLibraryTableBridgeImpl.kt new file mode 100644 index 000000000000..86fd0ca61b1f --- /dev/null +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/CustomLibraryTableBridgeImpl.kt @@ -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, List>>, 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 = libraryTableDelegate.getLibraries() + + override fun getLibraryIterator(): Iterator = 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 \ No newline at end of file diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalAndCustomLibraryTableBridgeInitializer.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalAndCustomLibraryTableBridgeInitializer.kt new file mode 100644 index 000000000000..5c80122925e7 --- /dev/null +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalAndCustomLibraryTableBridgeInitializer.kt @@ -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, List>>, + builder: MutableEntityStorage) = GlobalLibraryTableBridgeImpl.initializeLibraryBridgesTimeMs.addMeasuredTimeMillis { + val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage + + @Suppress("UNCHECKED_CAST") + val libraryChanges = (changes[LibraryEntity::class.java] as? List>) ?: emptyList() + val addChanges = libraryChanges.filterGlobalOrCustomLibraryChanges().filterIsInstance>() + + 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>.filterGlobalOrCustomLibraryChanges(): List> { + 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 + } + } +} \ No newline at end of file diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalLibraryTableBridgeImpl.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalLibraryTableBridgeImpl.kt index 2c15b61f8a94..a185df8fc308 100644 --- a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalLibraryTableBridgeImpl.kt +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalLibraryTableBridgeImpl.kt @@ -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, - List>>, - builder: MutableEntityStorage) = initializeLibraryBridgesTimeMs.addMeasuredTimeMillis { - val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage - - @Suppress("UNCHECKED_CAST") - val libraryChanges = (changes[LibraryEntity::class.java] as? List>) ?: emptyList() - val addChanges = libraryChanges.filterGlobalLibraryChanges().filterIsInstance>() - - 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, - List>>, - builder: MutableEntityStorage) = initializeLibraryBridgesTimeMs.addMeasuredTimeMillis { - val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage - - @Suppress("UNCHECKED_CAST") - val libraryChanges = (changes[LibraryEntity::class.java] as? List>) ?: emptyList() - val addChanges = libraryChanges.filterGlobalLibraryChanges().filterIsInstance>() - - 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, List>>, + 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>() - 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 = 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 = 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 { - return libraries.iterator() - } + override fun getLibraryIterator(): Iterator = 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() - 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>.filterGlobalLibraryChanges(): List> { - 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 - } - } -} diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalLibraryTableDelegate.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalLibraryTableDelegate.kt new file mode 100644 index 000000000000..6292caed5f74 --- /dev/null +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalLibraryTableDelegate.kt @@ -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, List>>, builder: MutableEntityStorage) { + val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage + + @Suppress("UNCHECKED_CAST") + val libraryChanges = (changes[LibraryEntity::class.java] as? List>) ?: emptyList() + val addChanges = libraryChanges.filterLibraryChanges(libraryTableId).filterIsInstance>() + + 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>() + 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 { + val entityStorage = GlobalWorkspaceModel.getInstance().entityStorage + val storage = entityStorage.current + val libraryEntitySequence = storage.entities(LibraryEntity::class.java).filter { it.tableId == libraryTableId }.toList() + val libs: Array = 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() + } +} + +private fun List>.filterLibraryChanges(libraryTableId: LibraryTableId): List> { + 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 + } +} \ No newline at end of file diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalModifiableLibraryTableBridgeImpl.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalOrCustomModifiableLibraryTableBridgeImpl.kt similarity index 87% rename from platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalModifiableLibraryTableBridgeImpl.kt rename to platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalOrCustomModifiableLibraryTableBridgeImpl.kt index a13ce152d826..edc567d040a4 100644 --- a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalModifiableLibraryTableBridgeImpl.kt +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/library/GlobalOrCustomModifiableLibraryTableBridgeImpl.kt @@ -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() + 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 = 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 { - 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 diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/CustomLibraryTableBridge.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/CustomLibraryTableBridge.kt new file mode 100644 index 000000000000..a7cca1d8a7cc --- /dev/null +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/CustomLibraryTableBridge.kt @@ -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) + } +} \ No newline at end of file diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/GlobalEntityBridgeAndEventHandler.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/GlobalEntityBridgeAndEventHandler.kt new file mode 100644 index 000000000000..49fb9bdda891 --- /dev/null +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/GlobalEntityBridgeAndEventHandler.kt @@ -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, List>>, builder: MutableEntityStorage) + fun handleBeforeChangeEvents(event: VersionedStorageChange) + fun handleChangedEvents(event: VersionedStorageChange) + + companion object { + fun getAllGlobalEntityHandlers(): List { + val result = mutableListOf() + 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 + } + } +} \ No newline at end of file diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/GlobalLibraryTableBridge.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/GlobalLibraryTableBridge.kt index fc5af84595d1..9b9be32b26b6 100644 --- a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/GlobalLibraryTableBridge.kt +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/GlobalLibraryTableBridge.kt @@ -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, List>>, builder: MutableEntityStorage) - fun handleBeforeChangeEvents(event: VersionedStorageChange) - fun handleChangedEvents(event: VersionedStorageChange) +interface GlobalLibraryTableBridge : GlobalEntityBridgeAndEventHandler, LibraryTable { companion object { fun getInstance(): GlobalLibraryTableBridge = ApplicationManager.getApplication().service() } diff --git a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/GlobalSdkTableBridge.kt b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/GlobalSdkTableBridge.kt index c85057fd8508..366142f1e2e9 100644 --- a/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/GlobalSdkTableBridge.kt +++ b/platform/projectModel-impl/src/com/intellij/workspaceModel/ide/legacyBridge/GlobalSdkTableBridge.kt @@ -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, List>>, 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) diff --git a/platform/workspace/jps/src/com/intellij/platform/workspace/jps/serialization/impl/JpsLibraryEntitiesSerializer.kt b/platform/workspace/jps/src/com/intellij/platform/workspace/jps/serialization/impl/JpsLibraryEntitiesSerializer.kt index a27baafb735a..1909c93d52af 100644 --- a/platform/workspace/jps/src/com/intellij/platform/workspace/jps/serialization/impl/JpsLibraryEntitiesSerializer.kt +++ b/platform/workspace/jps/src/com/intellij/platform/workspace/jps/serialization/impl/JpsLibraryEntitiesSerializer.kt @@ -72,8 +72,10 @@ internal class JpsGlobalLibrariesFileSerializer(entitySource: JpsGlobalFileEntit JpsFileEntityTypeSerializer { 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 { 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() + 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() + val excludedRoots = ArrayList() + 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() - val excludedRoots = ArrayList() - 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 { 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() - 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]!! } diff --git a/platform/workspace/jps/src/com/intellij/platform/workspace/jps/serialization/impl/ModuleImlFileEntitiesSerializer.kt b/platform/workspace/jps/src/com/intellij/platform/workspace/jps/serialization/impl/ModuleImlFileEntitiesSerializer.kt index 8e20d7cc47d4..a4783f2f53d8 100644 --- a/platform/workspace/jps/src/com/intellij/platform/workspace/jps/serialization/impl/ModuleImlFileEntitiesSerializer.kt +++ b/platform/workspace/jps/src/com/intellij/platform/workspace/jps/serialization/impl/ModuleImlFileEntitiesSerializer.kt @@ -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 {