diff --git a/java/compiler/impl/intellij.java.compiler.impl.iml b/java/compiler/impl/intellij.java.compiler.impl.iml index 44263219e4ee..c730e8d01463 100644 --- a/java/compiler/impl/intellij.java.compiler.impl.iml +++ b/java/compiler/impl/intellij.java.compiler.impl.iml @@ -37,6 +37,7 @@ + diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerImpl.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerImpl.java index 4e1d70728f2e..2f0915076ec5 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerImpl.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerImpl.java @@ -107,8 +107,8 @@ public final class ArtifactManagerImpl extends ArtifactManager implements Persis ArtifactState saveArtifact(Artifact artifact) { ArtifactState artifactState; - if (artifact instanceof InvalidArtifact) { - artifactState = ((InvalidArtifact)artifact).getState(); + if (artifact instanceof InvalidArtifactImpl) { + artifactState = ((InvalidArtifactImpl)artifact).getState(); } else { artifactState = new ArtifactState(); @@ -261,10 +261,10 @@ public final class ArtifactManagerImpl extends ArtifactManager implements Persis return artifact; } - private InvalidArtifact createInvalidArtifact(ArtifactState state, - ProjectModelExternalSource externalSource, - @Nls(capitalization = Nls.Capitalization.Sentence) String errorMessage) { - final InvalidArtifact artifact = new InvalidArtifact(state, errorMessage, externalSource); + private InvalidArtifactImpl createInvalidArtifact(ArtifactState state, + ProjectModelExternalSource externalSource, + @Nls(capitalization = Nls.Capitalization.Sentence) String errorMessage) { + final InvalidArtifactImpl artifact = new InvalidArtifactImpl(state, errorMessage, externalSource); ProjectLoadingErrorsNotifier.getInstance(myProject).registerError(new ArtifactLoadingErrorDescription(myProject, artifact)); UnknownFeaturesCollector.getInstance(myProject).registerUnknownFeature(FEATURE_TYPE, state.getArtifactType(), "Artifact"); return artifact; diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactVirtualFileListener.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactVirtualFileListener.java index 770421ac1b5a..8908f2f6dc7d 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactVirtualFileListener.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactVirtualFileListener.java @@ -10,6 +10,7 @@ import com.intellij.openapi.vfs.newvfs.events.VFileEvent; import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent; import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent; import com.intellij.packaging.artifacts.Artifact; +import com.intellij.packaging.artifacts.ArtifactManager; import com.intellij.packaging.artifacts.ModifiableArtifactModel; import com.intellij.packaging.impl.elements.FileOrDirectoryCopyPackagingElement; import com.intellij.psi.util.CachedValue; @@ -21,11 +22,11 @@ import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.List; -final class ArtifactVirtualFileListener implements BulkFileListener { +public final class ArtifactVirtualFileListener implements BulkFileListener { private final CachedValue> myParentPathsToArtifacts; - private final ArtifactManagerImpl myArtifactManager; + private final ArtifactManager myArtifactManager; - ArtifactVirtualFileListener(@NotNull Project project, @NotNull ArtifactManagerImpl artifactManager) { + public ArtifactVirtualFileListener(@NotNull Project project, @NotNull ArtifactManager artifactManager) { myArtifactManager = artifactManager; myParentPathsToArtifacts = CachedValuesManager.getManager(project).createCachedValue(() -> { diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/DynamicArtifactExtensionsLoader.kt b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/DynamicArtifactExtensionsLoader.kt index fae20a7ca154..97c4e22f7ea9 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/DynamicArtifactExtensionsLoader.kt +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/DynamicArtifactExtensionsLoader.kt @@ -19,7 +19,7 @@ internal class DynamicArtifactExtensionsLoader(private val artifactManager: Arti override fun extensionAdded(extension: ArtifactType, pluginDescriptor: PluginDescriptor) { runWriteAction { reloadArtifacts(artifactManager.allArtifactsIncludingInvalid.filter { - (it as? InvalidArtifact)?.state?.artifactType == extension.id + (it as? InvalidArtifactImpl)?.state?.artifactType == extension.id }) } } @@ -47,7 +47,7 @@ internal class DynamicArtifactExtensionsLoader(private val artifactManager: Arti override fun extensionAdded(extension: ArtifactPropertiesProvider, pluginDescriptor: PluginDescriptor) { runWriteAction { reloadArtifacts(artifactManager.allArtifactsIncludingInvalid.filter { artifact -> - (artifact as? InvalidArtifact)?.state?.propertiesList?.any { it.id == extension.id } ?: false + (artifact as? InvalidArtifactImpl)?.state?.propertiesList?.any { it.id == extension.id } ?: false }) } } diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifact.kt b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifact.kt new file mode 100644 index 000000000000..e61eaf3b675a --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifact.kt @@ -0,0 +1,10 @@ +// Copyright 2000-2021 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.packaging.impl.artifacts + +import com.intellij.packaging.artifacts.Artifact +import org.jetbrains.annotations.Nls + +interface InvalidArtifact: Artifact { + @Nls(capitalization = Nls.Capitalization.Sentence) + fun getErrorMessage(): String +} diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifact.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifactImpl.java similarity index 74% rename from java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifact.java rename to java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifactImpl.java index 695a7b3fd33f..1ec2ad8f05f2 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifact.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifactImpl.java @@ -21,13 +21,13 @@ import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.model.serialization.artifact.ArtifactState; -public class InvalidArtifact extends ArtifactImpl { +public class InvalidArtifactImpl extends ArtifactImpl implements InvalidArtifact { private final ArtifactState myState; private final @Nls(capitalization = Nls.Capitalization.Sentence) String myErrorMessage; - public InvalidArtifact(@NotNull ArtifactState state, - @Nls(capitalization = Nls.Capitalization.Sentence) String errorMessage, - ProjectModelExternalSource externalSource) { + public InvalidArtifactImpl(@NotNull ArtifactState state, + @Nls(capitalization = Nls.Capitalization.Sentence) String errorMessage, + ProjectModelExternalSource externalSource) { super(state.getName(), InvalidArtifactType.getInstance(), false, @@ -38,11 +38,12 @@ public class InvalidArtifact extends ArtifactImpl { myErrorMessage = errorMessage; } - public @Nls(capitalization = Nls.Capitalization.Sentence) String getErrorMessage() { + @Override + public @Nls(capitalization = Nls.Capitalization.Sentence) @NotNull String getErrorMessage() { return myErrorMessage; } - public ArtifactState getState() { + public @NotNull ArtifactState getState() { return myState; } } diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/UnknownPackagingElementTypeException.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/UnknownPackagingElementTypeException.java index 32d5015c3a96..bbf226025f1b 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/UnknownPackagingElementTypeException.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/UnknownPackagingElementTypeException.java @@ -15,10 +15,10 @@ */ package com.intellij.packaging.impl.artifacts; -class UnknownPackagingElementTypeException extends Exception { +public class UnknownPackagingElementTypeException extends Exception { private final String myTypeId; - UnknownPackagingElementTypeException(String typeId) { + public UnknownPackagingElementTypeException(String typeId) { myTypeId = typeId; } diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/ArtifactBridge.kt b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/ArtifactBridge.kt new file mode 100644 index 000000000000..4adad05f6e87 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/ArtifactBridge.kt @@ -0,0 +1,238 @@ +// Copyright 2000-2021 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.packaging.impl.artifacts.workspacemodel + +import com.intellij.configurationStore.deserializeInto +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectModelExternalSource +import com.intellij.openapi.util.JDOMUtil +import com.intellij.openapi.util.UserDataHolderBase +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.packaging.artifacts.* +import com.intellij.packaging.elements.CompositePackagingElement +import com.intellij.packaging.elements.PackagingElement +import com.intellij.packaging.impl.artifacts.InvalidArtifactType +import com.intellij.packaging.impl.artifacts.workspacemodel.ArtifactManagerBridge.Companion.artifactsMap +import com.intellij.util.EventDispatcher +import com.intellij.util.xmlb.XmlSerializer +import com.intellij.workspaceModel.ide.JpsImportedEntitySource +import com.intellij.workspaceModel.ide.WorkspaceModel +import com.intellij.workspaceModel.ide.getInstance +import com.intellij.workspaceModel.ide.impl.virtualFile +import com.intellij.workspaceModel.ide.toExternalSource +import com.intellij.workspaceModel.storage.CachedValue +import com.intellij.workspaceModel.storage.VersionedEntityStorage +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder +import com.intellij.workspaceModel.storage.bridgeEntities.* +import com.intellij.workspaceModel.storage.impl.VersionedEntityStorageOnBuilder +import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager +import org.jetbrains.annotations.NonNls +import org.jetbrains.jps.util.JpsPathUtil + +open class ArtifactBridge( + _artifactId: ArtifactId, + var entityStorage: VersionedEntityStorage, + val project: Project, + val eventDispatcher: EventDispatcher?, +) : ModifiableArtifact, UserDataHolderBase() { + + private val diffOrNull: WorkspaceEntityStorageBuilder? + get() { + val storage = entityStorage + if (storage is VersionedEntityStorageOnBuilder) { + return storage.builder + } + return null + } + + private val diff: WorkspaceEntityStorageBuilder + get() = diffOrNull ?: error("") + + private var artifactIdRaw = _artifactId + + // Artifacts may be renamed in two ways: using legacy bridge and workspace model directly. If the artifact renamed via the bridge, it's + // easy to update this property. + // For renaming via the model artifact id supposed to be extracted from external mappings. + // For cases when bridge is created, but not yet saved in external mappings, we extract the actifactId via [artifactIdRaw] + // There is only one known case when this logic may fail: if modifiable artifact is created (which is not stored in external mappings + // and not supposed to be) and the name of the artifact is modified directly in diff. However, we assume that this case isn't possible. + val artifactId: ArtifactId + get() = entityStorage.cachedValue(CachedValue { + val persistentId = (it.artifactsMap.getEntities(this@ArtifactBridge).singleOrNull() as? ArtifactEntity)?.persistentId() + if (persistentId != null) { + artifactIdRaw = persistentId + } + artifactIdRaw + }) + + val elementsWithDiff = mutableSetOf>() + + override fun getExternalSource(): ProjectModelExternalSource? { + val artifactEntity = entityStorage.current.resolve(artifactId) ?: error("") + return (artifactEntity.entitySource as? JpsImportedEntitySource)?.toExternalSource() + } + + override fun getArtifactType(): ArtifactType { + if (this is InvalidArtifactBridge) return InvalidArtifactType.getInstance() + + val current = entityStorage.current + val artifactEntity = current.resolve(artifactId) ?: error("") + val type = ArtifactType.findById(artifactEntity.artifactType) + return if (type == null) { + if (this !is InvalidArtifactBridge) error("") + InvalidArtifactType.getInstance() + } + else type + } + + override fun getName(): String { + return artifactId.name + } + + override fun isBuildOnMake(): Boolean { + val artifactEntity = entityStorage.current.resolve(artifactId) ?: error("") + return artifactEntity.includeInProjectBuild + } + + override fun getRootElement(): CompositePackagingElement<*> { + val current = entityStorage.current + val artifactEntity = current.resolve(artifactId) ?: error("") + val rootElement = artifactEntity.rootElement + val compositeElement = rootElement.toCompositeElement(project, entityStorage.base) + if (!compositeElement.hasStorage() || (compositeElement.storageIsStore() && diffOrNull != null)) { + compositeElement.setStorage(entityStorage, project, elementsWithDiff, PackagingElementInitializer) + if (entityStorage is VersionedEntityStorageOnBuilder) { + elementsWithDiff += compositeElement + } + } + + return compositeElement + } + + override fun getOutputPath(): String? { + val artifactEntity = entityStorage.current.resolve(artifactId) ?: error("Cannot find an artifact by id: $artifactId") + return artifactEntity.outputUrl?.url?.let { JpsPathUtil.urlToOsPath(it) } + } + + override fun getPropertiesProviders(): MutableCollection { + val storage = this.entityStorage.current + val artifactEntity = storage.resolve(artifactId) ?: error("") + return artifactEntity.customProperties.map { ArtifactPropertiesProvider.findById(it.providerType)!! }.toMutableList() + } + + override fun getProperties(propertiesProvider: ArtifactPropertiesProvider): ArtifactProperties<*>? { + val storage = this.entityStorage.current + val artifactEntity = storage.resolve(artifactId) ?: error("") + val providerId = propertiesProvider.id + val customProperty = artifactEntity.customProperties.find { it.providerType == providerId } ?: return null + val propertiesXmlTag = customProperty.propertiesXmlTag ?: return null + + val createdProperties: ArtifactProperties = propertiesProvider.createProperties(this.artifactType) as ArtifactProperties + val state = createdProperties.state!! + JDOMUtil.load(propertiesXmlTag).deserializeInto(state) + + createdProperties.loadState(state) + + return createdProperties + } + + override fun getOutputFile(): VirtualFile? { + val storage = this.entityStorage.current + val artifactEntity = storage.resolve(artifactId) ?: error("") + val outputUrl = artifactEntity.outputUrl + return outputUrl?.virtualFile + } + + override fun getOutputFilePath(): String? { + val storage = this.entityStorage.current + val artifactEntity = storage.resolve(artifactId) ?: error("") + val outputUrl = artifactEntity.outputUrl + if (outputUrl == null) return null + + val rootElement = artifactEntity.rootElement + val path = JpsPathUtil.urlToPath(outputUrl.url) + return if (rootElement is ArchivePackagingElementEntity) path + "/" + rootElement.fileName else path + } + + override fun setBuildOnMake(enabled: Boolean) { + val entity = diff.resolve(artifactId) ?: error("") + diff.modifyEntity(ModifiableArtifactEntity::class.java, entity) { + this.includeInProjectBuild = enabled + } + } + + override fun setOutputPath(outputPath: String?) { + val outputUrl = outputPath?.let { VirtualFileUrlManager.getInstance(project).fromPath(it) } + val entity = diff.resolve(artifactId) ?: error("") + diff.modifyEntity(ModifiableArtifactEntity::class.java, entity) { + this.outputUrl = outputUrl + } + } + + override fun setName(name: String) { + val entity = diff.resolve(artifactId) ?: error("") + val oldName = artifactId.name + diff.modifyEntity(ModifiableArtifactEntity::class.java, entity) { + this.name = name + } + this.artifactIdRaw = ArtifactId(name) + eventDispatcher?.multicaster?.artifactChanged(this, oldName) + } + + override fun setRootElement(root: CompositePackagingElement<*>) { + val entity = diff.resolve(artifactId) ?: error("") + val rootEntity = root.getOrAddEntity(diff, entity.entitySource, project) as CompositePackagingElementEntity + + root.forThisAndFullTree { + it.setStorage(this.entityStorage, this.project, elementsWithDiff, PackagingElementInitializer) + if (this.entityStorage is VersionedEntityStorageOnBuilder) { + elementsWithDiff += it + } + } + diff.modifyEntity(ModifiableArtifactEntity::class.java, entity) { + this.rootElement = rootEntity + } + } + + override fun setProperties(provider: ArtifactPropertiesProvider, properties: ArtifactProperties<*>?) { + if (properties == null) { + val entity = diff.resolve(artifactId) ?: error("") + val filtered = entity.customProperties.filterNot { it.providerType == provider.id } + diff.modifyEntity(ModifiableArtifactEntity::class.java, entity) { + this.customProperties = filtered + } + } + else { + val state = properties.state + val tag = if (state != null) { + val element = XmlSerializer.serialize(state) + element.name = "options" + JDOMUtil.write(element) + } else null + + val entity = diff.resolve(artifactId) ?: error("") + diff.addArtifactPropertiesEntity(entity, provider.id, tag, entity.entitySource) + } + } + + override fun setArtifactType(selected: ArtifactType) { + val entity = diff.resolve(artifactId) ?: error("") + diff.modifyEntity(ModifiableArtifactEntity::class.java, entity) { + this.artifactType = selected.id + } + } + + fun copyFrom(modified: ArtifactBridge) { + this.artifactIdRaw = modified.artifactId + } + + fun setActualStorage() { + if (entityStorage is VersionedEntityStorageOnBuilder) { + entityStorage = WorkspaceModel.getInstance(project).entityStorage + } + } + + @NonNls + override fun toString(): String { + return "artifact:${artifactId.name}" + } +} diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/ArtifactManagerBridge.kt b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/ArtifactManagerBridge.kt new file mode 100644 index 000000000000..b30b6617e581 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/ArtifactManagerBridge.kt @@ -0,0 +1,320 @@ +// Copyright 2000-2021 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.packaging.impl.artifacts.workspacemodel + +import com.intellij.compiler.server.BuildManager +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ex.ProjectRootManagerEx +import com.intellij.openapi.util.JDOMUtil +import com.intellij.openapi.util.ModificationTracker +import com.intellij.openapi.util.SimpleModificationTracker +import com.intellij.openapi.util.ThrowableComputable +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.packaging.artifacts.* +import com.intellij.packaging.elements.CompositePackagingElement +import com.intellij.packaging.elements.PackagingElement +import com.intellij.packaging.elements.PackagingElementFactory +import com.intellij.packaging.elements.PackagingElementResolvingContext +import com.intellij.packaging.impl.artifacts.ArtifactModelBase +import com.intellij.packaging.impl.artifacts.ArtifactPointerManagerImpl +import com.intellij.packaging.impl.artifacts.ArtifactVirtualFileListener +import com.intellij.packaging.impl.artifacts.DefaultPackagingElementResolvingContext +import com.intellij.util.concurrency.annotations.RequiresReadLock +import com.intellij.util.concurrency.annotations.RequiresWriteLock +import com.intellij.util.xmlb.XmlSerializer +import com.intellij.workspaceModel.ide.WorkspaceModel +import com.intellij.workspaceModel.storage.* +import com.intellij.workspaceModel.storage.bridgeEntities.ArtifactEntity +import com.intellij.workspaceModel.storage.bridgeEntities.ArtifactId +import com.intellij.workspaceModel.storage.bridgeEntities.CustomPackagingElementEntity +import com.intellij.workspaceModel.storage.bridgeEntities.ModifiableCustomPackagingElementEntity + +class ArtifactManagerBridge(private val project: Project) : ArtifactManager(), Disposable { + + private val modificationTracker = SimpleModificationTracker() + + private val resolvingContext = DefaultPackagingElementResolvingContext(project) + + internal val artifactWithDiffs: MutableList = mutableListOf() + + init { + (ArtifactPointerManager.getInstance(project) as ArtifactPointerManagerImpl).setArtifactManager(this) + project.messageBus.connect(this).subscribe(VirtualFileManager.VFS_CHANGES, ArtifactVirtualFileListener(project, this)) + DynamicArtifactExtensionsLoaderBridge(this).installListeners(this) + } + + @RequiresReadLock + override fun getArtifacts(): Array { + initBridges() + + val workspaceModel = WorkspaceModel.getInstance(project) + val entityStorage = workspaceModel.entityStorage + + val store = entityStorage.current + + val artifacts = store + .entities(ArtifactEntity::class.java) + .map { store.artifactsMap.getDataByEntity(it) ?: error("All artifact bridges should be already created at this moment") } + .filter { ArtifactModelBase.VALID_ARTIFACT_CONDITION.value(it) } + .toList().toTypedArray() + + return artifacts + } + + @RequiresReadLock + override fun findArtifact(name: String): Artifact? { + initBridges() + + val workspaceModel = WorkspaceModel.getInstance(project) + val entityStorage = workspaceModel.entityStorage + val store = entityStorage.current + + val artifactEntity = store.resolve(ArtifactId(name)) ?: return null + + return store.artifactsMap.getDataByEntity(artifactEntity) + ?: error("All artifact bridges should be already created at this moment") + } + + override fun getArtifactByOriginal(artifact: Artifact): Artifact = artifact + + override fun getOriginalArtifact(artifact: Artifact): Artifact = artifact + + @RequiresReadLock + override fun getArtifactsByType(type: ArtifactType): List { + initBridges() + + val workspaceModel = WorkspaceModel.getInstance(project) + val entityStorage = workspaceModel.entityStorage + val store = entityStorage.current + val typeId = type.id + + return store + .entities(ArtifactEntity::class.java) + .filter { it.artifactType == typeId } + .map { store.artifactsMap.getDataByEntity(it) ?: error("All artifact bridges should be already created at this moment") } + .toList() + } + + @RequiresReadLock + override fun getAllArtifactsIncludingInvalid(): MutableList { + initBridges() + + val entityStorage = WorkspaceModel.getInstance(project).entityStorage + val storage = entityStorage.current + + return storage + .entities(ArtifactEntity::class.java) + .map { + storage.artifactsMap.getDataByEntity(it) ?: error("All artifact bridges should be already created at this moment") + } + .toMutableList() + } + + override fun getSortedArtifacts(): Array { + val artifacts = this.artifacts + + // TODO: 02.02.2021 Do not sort them each time + artifacts.sortWith(ARTIFACT_COMPARATOR) + return artifacts + } + + override fun createModifiableModel(): ModifiableArtifactModel { + val storage = WorkspaceModel.getInstance(project).entityStorage.current + return ArtifactModifiableModelBridge(project, WorkspaceEntityStorageBuilder.from(storage), this) + } + + override fun getResolvingContext(): PackagingElementResolvingContext = resolvingContext + + override fun addArtifact(name: String, type: ArtifactType, root: CompositePackagingElement<*>?): Artifact { + return WriteAction.compute(ThrowableComputable { + val model = createModifiableModel() + val artifact = model.addArtifact(name, type) + if (root != null) { + artifact.rootElement = root + } + model.commit() + artifact + }) + } + + override fun addElementsToDirectory(artifact: Artifact, relativePath: String, elements: Collection>) { + val model = createModifiableModel() + val root = model.getOrCreateModifiableArtifact(artifact).rootElement + PackagingElementFactory.getInstance().getOrCreateDirectory(root, relativePath).addOrFindChildren(elements) + WriteAction.run { model.commit() } + } + + override fun addElementsToDirectory(artifact: Artifact, relativePath: String, element: PackagingElement<*>) { + addElementsToDirectory(artifact, relativePath, listOf(element)) + } + + override fun getModificationTracker(): ModificationTracker { + return modificationTracker + } + + @RequiresWriteLock + fun commit(artifactModel: ArtifactModifiableModelBridge) { + updateCustomElements(artifactModel.diff) + + val current = WorkspaceModel.getInstance(project).entityStorage.current + val changes = artifactModel.diff.collectChanges(current)[ArtifactEntity::class.java] ?: emptyList() + + val removed = mutableSetOf() + val added = mutableListOf() + val changed = mutableListOf>() + val changedArtifacts: MutableList = mutableListOf() + + changes.forEach { + when (it) { + is EntityChange.Removed<*> -> current.artifactsMap.getDataByEntity(it.entity)?.let { it1 -> removed.add(it1) } + is EntityChange.Added -> Unit + is EntityChange.Replaced -> { + // Collect changes and transfer info from the modifiable bridge artifact to the original artifact + val originalArtifact = artifactModel.diff.artifactsMap.getDataByEntity(it.newEntity)!! + val modifiableArtifact = artifactModel.modifiableToOriginal.getKeysByValue(originalArtifact)!!.single() + if (modifiableArtifact !== originalArtifact) { + changedArtifacts.add(modifiableArtifact) + } + artifactModel.modifiableToOriginal.remove(modifiableArtifact, originalArtifact) + originalArtifact.copyFrom(modifiableArtifact) + changed.add(Triple(originalArtifact, (it.oldEntity as ArtifactEntity).name, modifiableArtifact)) + originalArtifact.setActualStorage() + } + } + } + + artifactModel.modifiableToOriginal.entries.forEach { (modifiable, original) -> + if (modifiable !== original) { + changedArtifacts.add(modifiable) + val oldName = original.name + original.copyFrom(modifiable) + changed.add(Triple(original, oldName, modifiable)) + } + } + + (ArtifactPointerManager.getInstance(project) as ArtifactPointerManagerImpl).disposePointers(changedArtifacts) + artifactModel.modifiableToOriginal.clear() + + WorkspaceModel.getInstance(project).updateProjectModel { + it.addDiff(artifactModel.diff) + } + + modificationTracker.incModificationCount() + + // Collect changes + changes.forEach { + when (it) { + is EntityChange.Added<*> -> { + val artifactBridge = artifactModel.diff.artifactsMap.getDataByEntity(it.entity)!! + added.add(artifactBridge) + artifactBridge.setActualStorage() + } + is EntityChange.Removed<*> -> Unit + is EntityChange.Replaced<*> -> Unit + } + } + + // Set actual storages + artifactWithDiffs.forEach { it.setActualStorage() } + artifactWithDiffs.clear() + + val entityStorage = WorkspaceModel.getInstance(project).entityStorage + added.forEach { + it.elementsWithDiff.forEach { it.setStorage(entityStorage, project, HashSet(), PackagingElementInitializer) } + it.elementsWithDiff.clear() + } + changed.forEach { + it.third.elementsWithDiff.forEach { it.setStorage(entityStorage, project, HashSet(), PackagingElementInitializer) } + it.third.elementsWithDiff.clear() + it.first.elementsWithDiff.forEach { it.setStorage(entityStorage, project, HashSet(), PackagingElementInitializer) } + it.first.elementsWithDiff.clear() + } + artifactModel.elementsWithDiff.forEach { it.setStorage(entityStorage, project, HashSet(), PackagingElementInitializer) } + artifactModel.elementsWithDiff.clear() + + val publisher: ArtifactListener = project.messageBus.syncPublisher(TOPIC) + ProjectRootManagerEx.getInstanceEx(project).mergeRootsChangesDuring { + //it's important to send 'removed' events before 'added'. Otherwise when artifacts are reloaded from xml artifact pointers will be damaged + removed.forEach { publisher.artifactRemoved(it) } + added.forEach { publisher.artifactAdded(it) } + changed.forEach { (artifact, oldName) -> publisher.artifactChanged(artifact, oldName) } + } + + // TODO: 17.02.2021 Update watch roots + + if (changes.isNotEmpty()) { + BuildManager.getInstance().clearState(project) + } + } + + private fun updateCustomElements(diff: WorkspaceEntityStorageBuilder) { + val customEntities = diff.entities(CustomPackagingElementEntity::class.java).toList() + for (customEntity in customEntities) { + val packagingElement = diff.elements.getDataByEntity(customEntity) ?: continue + val state = packagingElement.state ?: continue + val newState = JDOMUtil.write(XmlSerializer.serialize(state)) + if (newState != customEntity.propertiesXmlTag) { + diff.modifyEntity(ModifiableCustomPackagingElementEntity::class.java, customEntity) { + this.propertiesXmlTag = newState + } + } + } + } + + // Initialize all artifact bridges + @RequiresReadLock + private fun initBridges() { + val workspaceModel = WorkspaceModel.getInstance(project) + val current = workspaceModel.entityStorage.current + if (current.entitiesAmount(ArtifactEntity::class.java) != current.artifactsMap.size()) { + + synchronized(lock) { + val currentInSync = workspaceModel.entityStorage.current + val artifactsMap = currentInSync.artifactsMap + + // Double check + if (currentInSync.entitiesAmount(ArtifactEntity::class.java) != artifactsMap.size()) { + val newBridges = currentInSync + .entities(ArtifactEntity::class.java) + .mapNotNull { + if (artifactsMap.getDataByEntity(it) == null) { + createArtifactBridge(it, workspaceModel.entityStorage, project) + } + else null + } + .toList() + + workspaceModel.updateProjectModelSilent { + addBridgesToDiff(newBridges, it) + } + } + } + } + } + + companion object { + private val lock = Any() + private const val ARTIFACT_BRIDGE_MAPPING_ID = "intellij.artifacts.bridge" + + val WorkspaceEntityStorage.artifactsMap: ExternalEntityMapping + get() = getExternalMapping(ARTIFACT_BRIDGE_MAPPING_ID) + + internal val WorkspaceEntityStorageBuilder.mutableArtifactsMap: MutableExternalEntityMapping + get() = getMutableExternalMapping(ARTIFACT_BRIDGE_MAPPING_ID) + } + + override fun dispose() { + // Anything here? + } + + @RequiresWriteLock + fun dropMappings(selector: (ArtifactEntity) -> Boolean) { + WorkspaceModel.getInstance(project).updateProjectModelSilent { + val map = it.mutableArtifactsMap + it.entities(ArtifactEntity::class.java).filter(selector).forEach { artifact -> + map.removeMapping(artifact) + } + } + } +} diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/ArtifactModifiableModelBridge.kt b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/ArtifactModifiableModelBridge.kt new file mode 100644 index 000000000000..ea24eacd5098 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/ArtifactModifiableModelBridge.kt @@ -0,0 +1,502 @@ +// Copyright 2000-2021 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.packaging.impl.artifacts.workspacemodel + +import com.intellij.configurationStore.deserializeInto +import com.intellij.openapi.module.ModulePointerManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.isExternalStorageEnabled +import com.intellij.openapi.roots.ProjectModelExternalSource +import com.intellij.openapi.util.JDOMUtil +import com.intellij.packaging.artifacts.* +import com.intellij.packaging.elements.CompositePackagingElement +import com.intellij.packaging.elements.PackagingElement +import com.intellij.packaging.elements.PackagingElementFactory +import com.intellij.packaging.elements.PackagingElementType +import com.intellij.packaging.impl.artifacts.ArtifactModelBase +import com.intellij.packaging.impl.artifacts.ArtifactPointerManagerImpl +import com.intellij.packaging.impl.artifacts.ArtifactUtil +import com.intellij.packaging.impl.artifacts.UnknownPackagingElementTypeException +import com.intellij.packaging.impl.artifacts.workspacemodel.ArtifactManagerBridge.Companion.artifactsMap +import com.intellij.packaging.impl.artifacts.workspacemodel.ArtifactManagerBridge.Companion.mutableArtifactsMap +import com.intellij.packaging.impl.elements.* +import com.intellij.util.EventDispatcher +import com.intellij.util.concurrency.annotations.RequiresWriteLock +import com.intellij.util.containers.BidirectionalMap +import com.intellij.util.containers.mapInPlace +import com.intellij.workspaceModel.ide.* +import com.intellij.workspaceModel.storage.EntityChange +import com.intellij.workspaceModel.storage.WorkspaceEntityStorage +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder +import com.intellij.workspaceModel.storage.bridgeEntities.* +import com.intellij.workspaceModel.storage.impl.VersionedEntityStorageOnBuilder +import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager +import org.jetbrains.jps.util.JpsPathUtil + +class ArtifactModifiableModelBridge( + private val project: Project, + internal val diff: WorkspaceEntityStorageBuilder, + private val manager: ArtifactManagerBridge, +) : ModifiableArtifactModel { + + internal val modifiableToOriginal = BidirectionalMap() + private val eventDispatcher = EventDispatcher.create(ArtifactListener::class.java) + + internal val elementsWithDiff = mutableSetOf>() + + override fun getArtifacts(): Array { + val newBridges = mutableListOf() + val artifacts = diff + .entities(ArtifactEntity::class.java) + .map { + diff.artifactsMap.getDataByEntity(it) ?: createArtifactBridge(it, VersionedEntityStorageOnBuilder(diff), project).also { + newBridges.add(it) + manager.artifactWithDiffs.add(it) + } + } + .filter { ArtifactModelBase.VALID_ARTIFACT_CONDITION.value(it) } + .toList().toTypedArray() + addBridgesToDiff(newBridges, diff) + return artifacts.mapInPlace { modifiableToOriginal.getKeysByValue(it)?.singleOrNull() ?: it } + } + + override fun findArtifact(name: String): Artifact? { + val artifactEntity = diff.resolve(ArtifactId(name)) ?: return null + + val newBridges = mutableListOf() + val bridge = diff.artifactsMap.getDataByEntity(artifactEntity) + ?: createArtifactBridge(artifactEntity, VersionedEntityStorageOnBuilder(diff), project).also { + newBridges.add(it) + manager.artifactWithDiffs.add(it) + } + addBridgesToDiff(newBridges, diff) + + return modifiableToOriginal.getKeysByValue(bridge)?.singleOrNull() ?: bridge + } + + override fun getArtifactByOriginal(artifact: Artifact): Artifact { + return modifiableToOriginal.getKeysByValue(artifact as ArtifactBridge)?.singleOrNull() ?: artifact + } + + override fun getOriginalArtifact(artifact: Artifact): Artifact { + return modifiableToOriginal[artifact as ArtifactBridge] ?: artifact + } + + override fun getArtifactsByType(type: ArtifactType): Collection { + val typeId = type.id + + val newBridges = mutableListOf() + val artifacts = diff + .entities(ArtifactEntity::class.java) + .filter { it.artifactType == typeId } + .map { + diff.artifactsMap.getDataByEntity(it) ?: createArtifactBridge(it, VersionedEntityStorageOnBuilder(diff), project).also { + newBridges.add(it) + manager.artifactWithDiffs.add(it) + } + } + .toList() + addBridgesToDiff(newBridges, diff) + return artifacts + } + + override fun getAllArtifactsIncludingInvalid(): MutableList { + val newBridges = mutableListOf() + val artifacts = diff + .entities(ArtifactEntity::class.java) + .map { + diff.artifactsMap.getDataByEntity(it) ?: createArtifactBridge(it, VersionedEntityStorageOnBuilder(diff), project).also { + newBridges.add(it) + manager.artifactWithDiffs.add(it) + } + } + .toMutableList() + addBridgesToDiff(newBridges, diff) + return artifacts + } + + override fun addArtifact(name: String, artifactType: ArtifactType): ModifiableArtifact { + return addArtifact(name, artifactType, artifactType.createRootElement(name)) + } + + override fun addArtifact(name: String, artifactType: ArtifactType, rootElement: CompositePackagingElement<*>): ModifiableArtifact { + return addArtifact(name, artifactType, rootElement, null) + } + + override fun addArtifact(name: String, + artifactType: ArtifactType, + rootElement: CompositePackagingElement<*>, + externalSource: ProjectModelExternalSource?): ModifiableArtifact { + val uniqueName = generateUniqueName(name) + + val outputPath = ArtifactUtil.getDefaultArtifactOutputPath(uniqueName, project) + + val fileManager = VirtualFileUrlManager.getInstance(project) + + val location = getJpsProjectConfigLocation(project) + val source = if (location != null) { + // TODO: 05.02.2021 Not really clear about entity source + val internalSource = JpsFileEntitySource.FileInDirectory(location.baseDirectoryUrl.append(".idea/artifacts"), location) + if (externalSource != null) { + JpsImportedEntitySource(internalSource, externalSource.id, project.isExternalStorageEnabled) + } + else internalSource + } + else { + NonPersistentEntitySource + } + + val rootElementEntity = rootElement.getOrAddEntity(diff, source, project) as CompositePackagingElementEntity + rootElement.forThisAndFullTree { + if (!it.hasStorage()) { + it.setStorage(VersionedEntityStorageOnBuilder(diff), project, elementsWithDiff, PackagingElementInitializer) + elementsWithDiff += it + } + } + + val outputUrl = outputPath?.let { fileManager.fromPath(it) } + val artifactEntity = diff.addArtifactEntity( + uniqueName, artifactType.id, false, + outputUrl, rootElementEntity, source + ) + + val persistentId = artifactEntity.persistentId() + val modifiableArtifact = ArtifactBridge(persistentId, VersionedEntityStorageOnBuilder(diff), project, eventDispatcher) + modifiableToOriginal[modifiableArtifact] = modifiableArtifact + diff.mutableArtifactsMap.addMapping(artifactEntity, modifiableArtifact) + + eventDispatcher.multicaster.artifactAdded(modifiableArtifact) + + return modifiableArtifact + } + + private fun generateUniqueName(baseName: String): String { + var name = baseName + var i = 2 + while (true) { + if (findArtifact(name) == null) { + return name + } + name = baseName + i++ + } + } + + override fun removeArtifact(artifact: Artifact) { + artifact as ArtifactBridge + val original = modifiableToOriginal[artifact] + if (original != null) { + modifiableToOriginal.remove(artifact) + + diff.removeEntity(diff.resolve(ArtifactId(artifact.name))!!) + eventDispatcher.multicaster.artifactRemoved(original) + } + else { + modifiableToOriginal.removeValue(artifact) + val entities = diff.artifactsMap.getEntities(artifact) + entities.forEach { diff.removeEntity(it) } + eventDispatcher.multicaster.artifactRemoved(artifact) + } + } + + override fun getOrCreateModifiableArtifact(artifact: Artifact): ModifiableArtifact { + if (artifact as ArtifactBridge in modifiableToOriginal) return artifact + + val entity = diff.artifactsMap.getEntities(artifact).singleOrNull() as? ArtifactEntity + if (entity == null) error("Artifact doesn't exist") + val artifactId = entity.persistentId() + val existingModifiableArtifact = modifiableToOriginal.getKeysByValue(artifact)?.singleOrNull() + if (existingModifiableArtifact != null) return existingModifiableArtifact + + val modifiableArtifact = ArtifactBridge(artifactId, VersionedEntityStorageOnBuilder(diff), project, eventDispatcher) + modifiableToOriginal[modifiableArtifact] = artifact + eventDispatcher.multicaster.artifactChanged(modifiableArtifact, artifact.name) + return modifiableArtifact + } + + override fun getModifiableCopy(artifact: Artifact?): Artifact? { + if (artifact == null) return null + return modifiableToOriginal.getKeysByValue(artifact as ArtifactBridge)?.singleOrNull() + } + + override fun addListener(listener: ArtifactListener) { + eventDispatcher.addListener(listener) + } + + override fun removeListener(listener: ArtifactListener) { + eventDispatcher.removeListener(listener) + } + + override fun isModified(): Boolean { + // TODO: 03.02.2021 May give a wrong result + return !diff.isEmpty() + } + + @RequiresWriteLock + override fun commit() { + manager.commit(this) + } + + override fun dispose() { + val artifacts: MutableList = ArrayList() + for (artifact in modifiableToOriginal.keys) { + if (modifiableToOriginal[artifact] == artifact) { + artifacts.add(artifact) + } + } + (ArtifactPointerManager.getInstance(project) as ArtifactPointerManagerImpl).disposePointers(artifacts) + + val current = WorkspaceModel.getInstance(project).entityStorage.current + val changes = diff.collectChanges(current)[ArtifactEntity::class.java] ?: emptyList() + + val added = mutableListOf() + val changed = mutableListOf() + + changes.forEach { + when (it) { + is EntityChange.Removed<*> -> Unit + is EntityChange.Added -> { + val artifactBridge = diff.artifactsMap.getDataByEntity(it.entity)!! + added.add(artifactBridge) + } + is EntityChange.Replaced -> { + // Collect changes and transfer info from the modifiable bridge artifact to the original artifact + val originalArtifact = diff.artifactsMap.getDataByEntity(it.newEntity)!! + val modifiableArtifact = modifiableToOriginal.getKeysByValue(originalArtifact)!!.single() + modifiableToOriginal.remove(modifiableArtifact, originalArtifact) + changed.add(modifiableArtifact) + } + } + } + + modifiableToOriginal.entries.forEach { (modifiable, original) -> + if (modifiable !== original) { + changed.add(modifiable) + } + } + + val entityStorage = WorkspaceModel.getInstance(project).entityStorage + added.forEach { + it.elementsWithDiff.forEach { it.setStorage(entityStorage, project, HashSet(), PackagingElementInitializer) } + it.elementsWithDiff.clear() + } + changed.forEach { + it.elementsWithDiff.forEach { it.setStorage(entityStorage, project, HashSet(), PackagingElementInitializer) } + it.elementsWithDiff.clear() + } + } +} + +internal fun PackagingElementEntity.sameTypeWith(type: PackagingElementType>): Boolean { + return when (this) { + is ModuleOutputPackagingElementEntity -> type == ProductionModuleOutputElementType.ELEMENT_TYPE + is ModuleTestOutputPackagingElementEntity -> type == TestModuleOutputElementType.ELEMENT_TYPE + is ModuleSourcePackagingElementEntity -> type == ProductionModuleSourceElementType.ELEMENT_TYPE + is ArtifactOutputPackagingElementEntity -> type == PackagingElementFactoryImpl.ARCHIVE_ELEMENT_TYPE + is ExtractedDirectoryPackagingElementEntity -> type == PackagingElementFactoryImpl.EXTRACTED_DIRECTORY_ELEMENT_TYPE + is FileCopyPackagingElementEntity -> type == PackagingElementFactoryImpl.FILE_COPY_ELEMENT_TYPE + is DirectoryCopyPackagingElementEntity -> type == PackagingElementFactoryImpl.DIRECTORY_COPY_ELEMENT_TYPE + is DirectoryPackagingElementEntity -> type == PackagingElementFactoryImpl.DIRECTORY_ELEMENT_TYPE + is ArchivePackagingElementEntity -> type == PackagingElementFactoryImpl.ARCHIVE_ELEMENT_TYPE + is ArtifactRootElementEntity -> type == PackagingElementFactoryImpl.ARTIFACT_ROOT_ELEMENT_TYPE + is LibraryFilesPackagingElementEntity -> type == LibraryElementType.LIBRARY_ELEMENT_TYPE + is CustomPackagingElementEntity -> this.typeId == type.id + else -> error("Unexpected branch. $this") + } +} + +internal fun CompositePackagingElementEntity.toCompositeElement( + project: Project, + storage: WorkspaceEntityStorage, + addToMapping: Boolean = true, +): CompositePackagingElement<*> { + val existing = storage.elements.getDataByEntity(this) + if (existing != null) return existing as CompositePackagingElement<*> + + val element = when (this) { + is DirectoryPackagingElementEntity -> { + val element = DirectoryPackagingElement(this.directoryName) + this.children.pushTo(element, project, storage) + element + } + is ArchivePackagingElementEntity -> { + val mapping = storage.getExternalMapping>("intellij.artifacts.packaging.elements") + val data = mapping.getDataByEntity(this) + if (data != null) { + return data + } + + val element = ArchivePackagingElement(this.fileName) + this.children.pushTo(element, project, storage) + element + } + is ArtifactRootElementEntity -> { + val element = ArtifactRootElementImpl() + this.children.pushTo(element, project, storage) + element + } + is CustomPackagingElementEntity -> { + val unpacked = unpackCustomElement(storage, project) + if (unpacked !is CompositePackagingElement<*>) { + error("Expected composite packaging element") + } + unpacked + } + else -> unknownElement() + } + if (addToMapping) { + if (storage is WorkspaceEntityStorageBuilder) { + val mutableMapping = storage.mutableElements + mutableMapping.addMapping(this, element) + } + else { + WorkspaceModel.getInstance(project).updateProjectModelSilent { + val mutableMapping = it.mutableElements + mutableMapping.addMapping(this, element) + } + } + } + return element +} + +fun PackagingElementEntity.toElement(project: Project, storage: WorkspaceEntityStorage): PackagingElement<*> { + val existing = storage.elements.getDataByEntity(this) + if (existing != null) return existing + + val element = when (this) { + is ModuleOutputPackagingElementEntity -> { + val module = this.module + if (module != null) { + val modulePointer = ModulePointerManager.getInstance(project).create(module.name) + ProductionModuleOutputPackagingElement(project, modulePointer) + } + else { + ProductionModuleOutputPackagingElement(project) + } + } + is ModuleTestOutputPackagingElementEntity -> { + val module = this.module + if (module != null) { + val modulePointer = ModulePointerManager.getInstance(project).create(module.name) + TestModuleOutputPackagingElement(project, modulePointer) + } + else { + TestModuleOutputPackagingElement(project) + } + } + is ModuleSourcePackagingElementEntity -> { + val module = this.module + if (module != null) { + val modulePointer = ModulePointerManager.getInstance(project).create(module.name) + ProductionModuleSourcePackagingElement(project, modulePointer) + } + else { + ProductionModuleSourcePackagingElement(project) + } + } + is ArtifactOutputPackagingElementEntity -> { + val artifact = this.artifact + if (artifact != null) { + val artifactPointer = ArtifactPointerManager.getInstance(project).createPointer(artifact.name) + ArtifactPackagingElement(project, artifactPointer) + } + else { + ArtifactPackagingElement(project) + } + } + is ExtractedDirectoryPackagingElementEntity -> { + val pathInArchive = this.pathInArchive + val archive = this.filePath + ExtractedDirectoryPackagingElement(JpsPathUtil.urlToPath(archive.url), pathInArchive) + } + is FileCopyPackagingElementEntity -> { + val file = this.filePath + val renamedOutputFileName = this.renamedOutputFileName + if (file != null) { + if (renamedOutputFileName != null) { + FileCopyPackagingElement(JpsPathUtil.urlToPath(file.url), renamedOutputFileName) + } + else { + FileCopyPackagingElement(JpsPathUtil.urlToPath(file.url)) + } + } + else { + FileCopyPackagingElement() + } + } + is DirectoryCopyPackagingElementEntity -> { + val directory = this.filePath + if (directory != null) { + DirectoryCopyPackagingElement(JpsPathUtil.urlToPath(directory.url)) + } + else { + DirectoryCopyPackagingElement() + } + } + is ArchivePackagingElementEntity -> this.toCompositeElement(project, storage, false) + is DirectoryPackagingElementEntity -> this.toCompositeElement(project, storage, false) + is ArtifactRootElementEntity -> this.toCompositeElement(project, storage, false) + is LibraryFilesPackagingElementEntity -> { + val mapping = storage.getExternalMapping>("intellij.artifacts.packaging.elements") + val data = mapping.getDataByEntity(this) + if (data != null) { + return data + } + + val library = this.library + if (library != null) { + val tableId = library.tableId + val moduleName = if (tableId is LibraryTableId.ModuleLibraryTableId) tableId.moduleId.name else null + LibraryPackagingElement(tableId.level, library.name, moduleName) + } + else { + LibraryPackagingElement() + } + } + is CustomPackagingElementEntity -> unpackCustomElement(storage, project) + else -> unknownElement() + } + + if (storage is WorkspaceEntityStorageBuilder) { + val mutableMapping = storage.mutableElements + mutableMapping.addIfAbsent(this, element) + } + else { + WorkspaceModel.getInstance(project).updateProjectModelSilent { + val mutableMapping = it.mutableElements + mutableMapping.addIfAbsent(this, element) + } + } + return element +} + +private fun CustomPackagingElementEntity.unpackCustomElement(storage: WorkspaceEntityStorage, + project: Project): PackagingElement<*> { + val mapping = storage.getExternalMapping>("intellij.artifacts.packaging.elements") + val data = mapping.getDataByEntity(this) + if (data != null) { + return data + } + + // TODO: 09.04.2021 It should be invalid artifact instead of error + val elementType = PackagingElementFactory.getInstance().findElementType(this.typeId) + ?: throw UnknownPackagingElementTypeException(this.typeId) + val packagingElement = elementType.createEmpty(project) as PackagingElement + val state = packagingElement.state + if (state != null) { + val element = JDOMUtil.load(this.propertiesXmlTag) + element.deserializeInto(state) + packagingElement.loadState(state) + } + if (packagingElement is CompositePackagingElement<*>) { + this.children.pushTo(packagingElement, project, storage) + } + return packagingElement +} + +private fun PackagingElementEntity.unknownElement(): Nothing { + error("Unknown packaging element entity: $this") +} + +fun Sequence.pushTo(element: CompositePackagingElement<*>, project: Project, storage: WorkspaceEntityStorage) { + val children = this.map { it.toElement(project, storage) }.toList() + children.reversed().forEach { element.addFirstChild(it) } +} diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/BridgeUtils.kt b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/BridgeUtils.kt new file mode 100644 index 000000000000..03a81c888b02 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/BridgeUtils.kt @@ -0,0 +1,80 @@ +// Copyright 2000-2021 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.packaging.impl.artifacts.workspacemodel + +import com.intellij.openapi.compiler.JavaCompilerBundle +import com.intellij.openapi.project.Project +import com.intellij.packaging.artifacts.ArtifactPropertiesProvider +import com.intellij.packaging.artifacts.ArtifactType +import com.intellij.packaging.elements.CompositePackagingElement +import com.intellij.packaging.elements.PackagingElement +import com.intellij.packaging.elements.PackagingElementFactory +import com.intellij.packaging.impl.artifacts.workspacemodel.ArtifactManagerBridge.Companion.mutableArtifactsMap +import com.intellij.workspaceModel.storage.VersionedEntityStorage +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder +import com.intellij.workspaceModel.storage.bridgeEntities.ArtifactEntity +import com.intellij.workspaceModel.storage.bridgeEntities.CompositePackagingElementEntity +import com.intellij.workspaceModel.storage.bridgeEntities.CustomPackagingElementEntity +import com.intellij.workspaceModel.storage.bridgeEntities.PackagingElementEntity + +internal fun addBridgesToDiff(newBridges: List, builder: WorkspaceEntityStorageBuilder) { + for (newBridge in newBridges) { + val artifactEntity = builder.resolve(newBridge.artifactId) ?: continue + builder.mutableArtifactsMap.addMapping(artifactEntity, newBridge) + } +} + +internal fun createArtifactBridge(it: ArtifactEntity, entityStorage: VersionedEntityStorage, project: Project): ArtifactBridge { + val type = ArtifactType.findById(it.artifactType) + if (type == null) { + return InvalidArtifactBridge(it.persistentId(), entityStorage, project, null, JavaCompilerBundle.message("unknown.artifact.type.0", it.artifactType)) + } + + fun findMissingArtifactType(element: PackagingElementEntity): String? { + if (element is CustomPackagingElementEntity) { + if (PackagingElementFactory.getInstance().findElementType(element.typeId) == null) { + return element.typeId + } + } + + if (element is CompositePackagingElementEntity) { + element.children.forEach { child -> + val artifactType = findMissingArtifactType(child) + if (artifactType != null) return artifactType + } + } + return null + } + + val missingArtifactType = findMissingArtifactType(it.rootElement) + if (missingArtifactType != null) { + return InvalidArtifactBridge(it.persistentId(), entityStorage, project, null, JavaCompilerBundle.message("unknown.element.0", missingArtifactType)) + } + + val unknownProperty = it.customProperties.firstOrNull { ArtifactPropertiesProvider.findById(it.providerType) == null } + if (unknownProperty != null) { + return InvalidArtifactBridge(it.persistentId(), entityStorage, project, null, JavaCompilerBundle.message("unknown.artifact.properties.0", unknownProperty)) + } + + return ArtifactBridge(it.persistentId(), entityStorage, project, null) +} + +inline fun PackagingElement<*>.forThisAndChildren(action: (PackagingElement<*>) -> Unit) { + action(this) + if (this is CompositePackagingElement<*>) { + this.children.forEach { action(it) } + } +} + +fun PackagingElement<*>.forThisAndFullTree(action: (PackagingElement<*>) -> Unit) { + action(this) + if (this is CompositePackagingElement<*>) { + this.children.forEach { + if (it is CompositePackagingElement<*>) { + it.forThisAndFullTree(action) + } + else { + action(it) + } + } + } +} diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/DynamicArtifactExtensionsLoaderBridge.kt b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/DynamicArtifactExtensionsLoaderBridge.kt new file mode 100644 index 000000000000..25ae46533a6b --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/DynamicArtifactExtensionsLoaderBridge.kt @@ -0,0 +1,69 @@ +// Copyright 2000-2021 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.packaging.impl.artifacts.workspacemodel + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.extensions.ExtensionPointListener +import com.intellij.openapi.extensions.PluginDescriptor +import com.intellij.packaging.artifacts.ArtifactPropertiesProvider +import com.intellij.packaging.artifacts.ArtifactType +import com.intellij.packaging.elements.PackagingElement +import com.intellij.packaging.elements.PackagingElementType +import com.intellij.packaging.impl.artifacts.InvalidArtifactType +import com.intellij.workspaceModel.storage.bridgeEntities.CompositePackagingElementEntity +import com.intellij.workspaceModel.storage.bridgeEntities.PackagingElementEntity + +internal class DynamicArtifactExtensionsLoaderBridge(private val artifactManager: ArtifactManagerBridge) { + fun installListeners(disposable: Disposable) { + ArtifactType.EP_NAME.point.addExtensionPointListener(object : ExtensionPointListener { + override fun extensionAdded(extension: ArtifactType, pluginDescriptor: PluginDescriptor) { + runWriteAction { + artifactManager.dropMappings { it.artifactType == extension.id } + } + } + + override fun extensionRemoved(extension: ArtifactType, pluginDescriptor: PluginDescriptor) { + // As I understand, this should be called already with write action (see prev version of this listener) + artifactManager.dropMappings { it.artifactType == extension.id } + } + }, false, disposable) + + PackagingElementType.EP_NAME.point.addExtensionPointListener( + object : ExtensionPointListener>> { + override fun extensionAdded(extension: PackagingElementType>, pluginDescriptor: PluginDescriptor) { + runWriteAction { + artifactManager.dropMappings { it.artifactType == InvalidArtifactType.getInstance().id } + } + } + + override fun extensionRemoved(extension: PackagingElementType>, pluginDescriptor: PluginDescriptor) { + + // As I understand, this should be called already with write action (see prev version of this listener) + artifactManager.dropMappings { + fun shouldDrop(element: PackagingElementEntity): Boolean { + if (element.sameTypeWith(extension)) return true + if (element is CompositePackagingElementEntity) { + return element.children.any { shouldDrop(it) } + } + return false + } + + return@dropMappings shouldDrop(it.rootElement) + } + } + }, false, disposable) + + ArtifactPropertiesProvider.EP_NAME.point.addExtensionPointListener(object : ExtensionPointListener { + override fun extensionAdded(extension: ArtifactPropertiesProvider, pluginDescriptor: PluginDescriptor) { + runWriteAction { + artifactManager.dropMappings { it.customProperties.any { it.providerType == extension.id } } + } + } + + override fun extensionRemoved(extension: ArtifactPropertiesProvider, pluginDescriptor: PluginDescriptor) { + // As I understand, this should be called already with write action (see prev version of this listener) + artifactManager.dropMappings { it.customProperties.any { it.providerType == extension.id } } + } + }, false, disposable) + } +} \ No newline at end of file diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/InvalidArtifactBridge.kt b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/InvalidArtifactBridge.kt new file mode 100644 index 000000000000..aa94fff8b4b2 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/InvalidArtifactBridge.kt @@ -0,0 +1,22 @@ +// Copyright 2000-2021 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.packaging.impl.artifacts.workspacemodel + +import com.intellij.openapi.project.Project +import com.intellij.packaging.artifacts.ArtifactListener +import com.intellij.packaging.impl.artifacts.InvalidArtifact +import com.intellij.util.EventDispatcher +import com.intellij.workspaceModel.storage.VersionedEntityStorage +import com.intellij.workspaceModel.storage.bridgeEntities.ArtifactId +import org.jetbrains.annotations.Nls + +class InvalidArtifactBridge( + _artifactId: ArtifactId, + entityStorage: VersionedEntityStorage, + project: Project, + eventDispatcher: EventDispatcher?, + + @Nls(capitalization = Nls.Capitalization.Sentence) + private val _errorMessage: String, +) : InvalidArtifact, ArtifactBridge(_artifactId, entityStorage, project, eventDispatcher) { + override fun getErrorMessage(): String = _errorMessage +} diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/PackagingElementInitializer.kt b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/PackagingElementInitializer.kt new file mode 100644 index 000000000000..a3e8dc60acd6 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/PackagingElementInitializer.kt @@ -0,0 +1,17 @@ +// Copyright 2000-2021 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.packaging.impl.artifacts.workspacemodel + +import com.intellij.openapi.project.Project +import com.intellij.packaging.elements.ElementInitializer +import com.intellij.packaging.elements.PackagingElement +import com.intellij.workspaceModel.storage.WorkspaceEntityStorage +import com.intellij.workspaceModel.storage.bridgeEntities.CompositePackagingElementEntity +import com.intellij.workspaceModel.storage.bridgeEntities.PackagingElementEntity + +object PackagingElementInitializer: ElementInitializer { + override fun initialize(entity: PackagingElementEntity, project: Project, storage: WorkspaceEntityStorage): PackagingElement<*> = + entity.toElement(project, storage) + + override fun initialize(entity: CompositePackagingElementEntity, project: Project, storage: WorkspaceEntityStorage): PackagingElement<*> = + entity.toCompositeElement(project, storage) +} diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/packaging/mappings.kt b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/packaging/mappings.kt new file mode 100644 index 000000000000..7d76d2120f12 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/workspacemodel/packaging/mappings.kt @@ -0,0 +1,14 @@ +// Copyright 2000-2021 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.packaging.impl.artifacts.workspacemodel + +import com.intellij.packaging.elements.PackagingElement +import com.intellij.workspaceModel.storage.ExternalEntityMapping +import com.intellij.workspaceModel.storage.MutableExternalEntityMapping +import com.intellij.workspaceModel.storage.WorkspaceEntityStorage +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder + +const val PACKAGING_ELEMENTS = "intellij.artifacts.packaging.elements" +internal val WorkspaceEntityStorageBuilder.mutableElements: MutableExternalEntityMapping> + get() = this.getMutableExternalMapping(PACKAGING_ELEMENTS) +val WorkspaceEntityStorage.elements: ExternalEntityMapping> + get() = this.getExternalMapping(PACKAGING_ELEMENTS) \ No newline at end of file diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArchivePackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArchivePackagingElement.java index 515a475504f1..30f404be7a07 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArchivePackagingElement.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArchivePackagingElement.java @@ -1,16 +1,27 @@ // Copyright 2000-2019 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.packaging.impl.elements; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.NlsSafe; import com.intellij.packaging.elements.PackagingElement; import com.intellij.packaging.impl.ui.ArchiveElementPresentation; import com.intellij.packaging.ui.ArtifactEditorContext; import com.intellij.packaging.ui.PackagingElementPresentation; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xmlb.XmlSerializerUtil; import com.intellij.util.xmlb.annotations.Attribute; +import com.intellij.workspaceModel.storage.EntitySource; +import com.intellij.workspaceModel.storage.WorkspaceEntity; +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder; +import com.intellij.workspaceModel.storage.bridgeEntities.BridgeModelModifiableEntitiesKt; +import com.intellij.workspaceModel.storage.bridgeEntities.ModifiableArchivePackagingElementEntity; +import com.intellij.workspaceModel.storage.bridgeEntities.PackagingElementEntity; +import kotlin.Unit; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import java.util.List; + public class ArchivePackagingElement extends CompositeElementWithManifest { @NonNls public static final String NAME_ATTRIBUTE = "name"; private String myArchiveFileName; @@ -46,7 +57,7 @@ public class ArchivePackagingElement extends CompositeElementWithManifest myArchiveFileName = archiveFileName, + (builder, entity) -> { + builder.modifyEntity(ModifiableArchivePackagingElementEntity.class, entity, ent -> { + ent.setFileName(archiveFileName); + return Unit.INSTANCE; + }); + }, true + ); } @Override @@ -68,4 +91,20 @@ public class ArchivePackagingElement extends CompositeElementWithManifest children = ContainerUtil.map(this.getChildren(), o -> { + return (PackagingElementEntity)o.getOrAddEntity(diff, source, project); + }); + + var entity = BridgeModelModifiableEntitiesKt.addArchivePackagingElementEntity(diff, myArchiveFileName, children, source); + diff.getMutableExternalMapping("intellij.artifacts.packaging.elements").addMapping(entity, this); + return entity; + } } diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactPackagingElement.java index e4b3ec07a00f..270cf4944e1a 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactPackagingElement.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactPackagingElement.java @@ -13,6 +13,12 @@ import com.intellij.packaging.impl.ui.DelegatedPackagingElementPresentation; import com.intellij.packaging.ui.ArtifactEditorContext; import com.intellij.packaging.ui.PackagingElementPresentation; import com.intellij.util.xmlb.annotations.Attribute; +import com.intellij.workspaceModel.storage.EntitySource; +import com.intellij.workspaceModel.storage.WorkspaceEntity; +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder; +import com.intellij.workspaceModel.storage.bridgeEntities.ArtifactId; +import com.intellij.workspaceModel.storage.bridgeEntities.ArtifactOutputPackagingElementEntity; +import com.intellij.workspaceModel.storage.bridgeEntities.BridgeModelModifiableEntitiesKt; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -101,6 +107,21 @@ public class ArtifactPackagingElement extends ComplexPackagingElement { public ArtifactRootElementImpl() { super(PackagingElementFactoryImpl.ARTIFACT_ROOT_ELEMENT_TYPE); @@ -66,4 +76,20 @@ public class ArtifactRootElementImpl extends ArtifactRootElement { public String toString() { return ""; } + + @Override + public WorkspaceEntity getOrAddEntity(@NotNull WorkspaceEntityStorageBuilder diff, + @NotNull EntitySource source, + @NotNull Project project) { + WorkspaceEntity existingEntity = getExistingEntity(diff); + if (existingEntity != null) return existingEntity; + + List children = ContainerUtil.map(this.getChildren(), o -> { + return (PackagingElementEntity)o.getOrAddEntity(diff, source, project); + }); + + ArtifactRootElementEntity entity = BridgeModelModifiableEntitiesKt.addArtifactRootElementEntity(diff, children, source); + diff.getMutableExternalMapping("intellij.artifacts.packaging.elements").addMapping(entity, this); + return entity; + } } diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryCopyPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryCopyPackagingElement.java index ac0b46effd61..e8bf892e32f3 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryCopyPackagingElement.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryCopyPackagingElement.java @@ -1,9 +1,18 @@ // Copyright 2000-2019 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.packaging.impl.elements; +import com.intellij.openapi.project.Project; import com.intellij.packaging.impl.ui.DirectoryCopyPresentation; import com.intellij.packaging.ui.ArtifactEditorContext; import com.intellij.packaging.ui.PackagingElementPresentation; +import com.intellij.workspaceModel.ide.VirtualFileUrlManagerUtil; +import com.intellij.workspaceModel.storage.EntitySource; +import com.intellij.workspaceModel.storage.WorkspaceEntity; +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder; +import com.intellij.workspaceModel.storage.bridgeEntities.BridgeModelModifiableEntitiesKt; +import com.intellij.workspaceModel.storage.bridgeEntities.DirectoryCopyPackagingElementEntity; +import com.intellij.workspaceModel.storage.url.VirtualFileUrl; +import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager; import org.jetbrains.annotations.NotNull; public class DirectoryCopyPackagingElement extends FileOrDirectoryCopyPackagingElement { @@ -22,6 +31,21 @@ public class DirectoryCopyPackagingElement extends FileOrDirectoryCopyPackagingE return new DirectoryCopyPresentation(myFilePath); } + @Override + public WorkspaceEntity getOrAddEntity(@NotNull WorkspaceEntityStorageBuilder diff, + @NotNull EntitySource source, + @NotNull Project project) { + WorkspaceEntity existingEntity = getExistingEntity(diff); + if (existingEntity != null) return existingEntity; + + VirtualFileUrlManager fileUrlManager = VirtualFileUrlManagerUtil.getInstance(VirtualFileUrlManager.Companion, project); + VirtualFileUrl fileUrl = fileUrlManager.fromPath(myFilePath); + DirectoryCopyPackagingElementEntity addedEntity = + BridgeModelModifiableEntitiesKt.addDirectoryCopyPackagingElementEntity(diff, fileUrl, source); + diff.getMutableExternalMapping("intellij.artifacts.packaging.elements").addMapping(addedEntity, this); + return addedEntity; + } + @Override public DirectoryCopyPackagingElement getState() { return this; diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryPackagingElement.java index 0d9d84ebaded..425509754ef6 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryPackagingElement.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryPackagingElement.java @@ -1,16 +1,27 @@ // Copyright 2000-2019 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.packaging.impl.elements; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.NlsSafe; import com.intellij.packaging.elements.PackagingElement; import com.intellij.packaging.impl.ui.DirectoryElementPresentation; import com.intellij.packaging.ui.ArtifactEditorContext; import com.intellij.packaging.ui.PackagingElementPresentation; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xmlb.XmlSerializerUtil; import com.intellij.util.xmlb.annotations.Attribute; +import com.intellij.workspaceModel.storage.EntitySource; +import com.intellij.workspaceModel.storage.WorkspaceEntity; +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder; +import com.intellij.workspaceModel.storage.bridgeEntities.BridgeModelModifiableEntitiesKt; +import com.intellij.workspaceModel.storage.bridgeEntities.ModifiableDirectoryPackagingElementEntity; +import com.intellij.workspaceModel.storage.bridgeEntities.PackagingElementEntity; +import kotlin.Unit; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import java.util.List; + /** * classpath is used for exploded WAR and EJB directories under exploded EAR */ @@ -49,12 +60,24 @@ public class DirectoryPackagingElement extends CompositeElementWithManifest myDirectoryName = newName, + (builder, entity) -> { + builder.modifyEntity(ModifiableDirectoryPackagingElementEntity.class, entity, ent ->{ + ent.setDirectoryName(newName); + return Unit.INSTANCE; + }); + }, true + ); } @Override @@ -67,6 +90,22 @@ public class DirectoryPackagingElement extends CompositeElementWithManifest children = ContainerUtil.map(this.getChildren(), o -> { + return (PackagingElementEntity)o.getOrAddEntity(diff, source, project); + }); + + var entity = BridgeModelModifiableEntitiesKt.addDirectoryPackagingElementEntity(diff, this.myDirectoryName, children, source); + diff.getMutableExternalMapping("intellij.artifacts.packaging.elements").addMapping(entity, this); + return entity; + } + @Override public void loadState(@NotNull DirectoryPackagingElement state) { XmlSerializerUtil.copyBean(state, this); diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ExtractedDirectoryPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ExtractedDirectoryPackagingElement.java index 24d8562f84e2..f43a7a81e7e1 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ExtractedDirectoryPackagingElement.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ExtractedDirectoryPackagingElement.java @@ -1,6 +1,7 @@ // 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.packaging.impl.elements; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.VirtualFile; @@ -9,6 +10,16 @@ import com.intellij.packaging.impl.ui.ExtractedDirectoryPresentation; import com.intellij.packaging.ui.ArtifactEditorContext; import com.intellij.packaging.ui.PackagingElementPresentation; import com.intellij.util.xmlb.annotations.Attribute; +import com.intellij.workspaceModel.ide.VirtualFileUrlManagerUtil; +import com.intellij.workspaceModel.storage.EntitySource; +import com.intellij.workspaceModel.storage.WorkspaceEntity; +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder; +import com.intellij.workspaceModel.storage.bridgeEntities.BridgeModelModifiableEntitiesKt; +import com.intellij.workspaceModel.storage.bridgeEntities.ExtractedDirectoryPackagingElementEntity; +import com.intellij.workspaceModel.storage.bridgeEntities.ModifiableExtractedDirectoryPackagingElementEntity; +import com.intellij.workspaceModel.storage.url.VirtualFileUrl; +import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager; +import kotlin.Unit; import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -75,6 +86,32 @@ public class ExtractedDirectoryPackagingElement extends FileOrDirectoryCopyPacka } public void setPathInJar(String pathInJar) { - myPathInJar = pathInJar; + String myPathInJarBefore = myPathInJar; + this.update( + () -> myPathInJar = pathInJar, + (builder, entity) -> { + if (myPathInJarBefore.equals(pathInJar)) return; + + builder.modifyEntity(ModifiableExtractedDirectoryPackagingElementEntity.class, entity, ent -> { + ent.setPathInArchive(pathInJar); + return Unit.INSTANCE; + }); + }, true); + } + + @Override + public WorkspaceEntity getOrAddEntity(@NotNull WorkspaceEntityStorageBuilder diff, + @NotNull EntitySource source, + @NotNull Project project) { + WorkspaceEntity existingEntity = getExistingEntity(diff); + if (existingEntity != null) return existingEntity; + + VirtualFileUrlManager fileUrlManager = VirtualFileUrlManagerUtil.getInstance(VirtualFileUrlManager.Companion, project); + VirtualFileUrl fileUrl = fileUrlManager.fromPath(this.myFilePath); + + ExtractedDirectoryPackagingElementEntity addedEntity = + BridgeModelModifiableEntitiesKt.addExtractedDirectoryPackagingElementEntity(diff, fileUrl, this.myPathInJar, source); + diff.getMutableExternalMapping("intellij.artifacts.packaging.elements").addMapping(addedEntity, this); + return addedEntity; } } diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileCopyPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileCopyPackagingElement.java index 14a5b0901cb6..9c332a6578c6 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileCopyPackagingElement.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileCopyPackagingElement.java @@ -1,6 +1,7 @@ // 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.packaging.impl.elements; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; @@ -12,6 +13,16 @@ import com.intellij.packaging.ui.ArtifactEditorContext; import com.intellij.packaging.ui.PackagingElementPresentation; import com.intellij.util.PathUtil; import com.intellij.util.xmlb.annotations.Attribute; +import com.intellij.workspaceModel.ide.VirtualFileUrlManagerUtil; +import com.intellij.workspaceModel.storage.EntitySource; +import com.intellij.workspaceModel.storage.WorkspaceEntity; +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder; +import com.intellij.workspaceModel.storage.bridgeEntities.BridgeModelModifiableEntitiesKt; +import com.intellij.workspaceModel.storage.bridgeEntities.FileCopyPackagingElementEntity; +import com.intellij.workspaceModel.storage.bridgeEntities.ModifiableFileCopyPackagingElementEntity; +import com.intellij.workspaceModel.storage.url.VirtualFileUrl; +import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager; +import kotlin.Unit; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -81,7 +92,18 @@ public class FileCopyPackagingElement extends FileOrDirectoryCopyPackagingElemen } public void setRenamedOutputFileName(String renamedOutputFileName) { - myRenamedOutputFileName = renamedOutputFileName; + String renamedBefore = myRenamedOutputFileName; + this.update( + () -> myRenamedOutputFileName = renamedOutputFileName, + (builder, entity) -> { + if (renamedBefore == renamedOutputFileName) return; + + builder.modifyEntity(ModifiableFileCopyPackagingElementEntity.class, entity, ent -> { + ent.setRenamedOutputFileName(renamedOutputFileName); + return Unit.INSTANCE; + }); + }, true + ); } @Override @@ -96,7 +118,16 @@ public class FileCopyPackagingElement extends FileOrDirectoryCopyPackagingElemen @Override public void rename(@NotNull String newName) { - myRenamedOutputFileName = newName.equals(PathUtil.getFileName(myFilePath)) ? null : newName; + String updatedName = newName.equals(PathUtil.getFileName(myFilePath)) ? null : newName; + this.update( + () -> myRenamedOutputFileName = updatedName, + (builder, entity) -> { + builder.modifyEntity(ModifiableFileCopyPackagingElementEntity.class, entity, ent -> { + ent.setRenamedOutputFileName(updatedName); + return Unit.INSTANCE; + }); + }, true + ); } @Nullable @@ -104,4 +135,26 @@ public class FileCopyPackagingElement extends FileOrDirectoryCopyPackagingElemen final String url = VfsUtil.getUrlForLibraryRoot(new File(FileUtil.toSystemDependentName(getFilePath()))); return VirtualFileManager.getInstance().findFileByUrl(url); } + + @Override + public WorkspaceEntity getOrAddEntity(@NotNull WorkspaceEntityStorageBuilder diff, + @NotNull EntitySource source, + @NotNull Project project) { + WorkspaceEntity existingEntity = getExistingEntity(diff); + if (existingEntity != null) return existingEntity; + + String renamedOutputFileName = this.myRenamedOutputFileName; + String filePath = this.myFilePath; + FileCopyPackagingElementEntity addedEntity; + VirtualFileUrlManager fileUrlManager = VirtualFileUrlManagerUtil.getInstance(VirtualFileUrlManager.Companion, project); + VirtualFileUrl fileUrl = fileUrlManager.fromPath(filePath); + if (renamedOutputFileName != null) { + addedEntity = BridgeModelModifiableEntitiesKt.addFileCopyPackagingElementEntity(diff, fileUrl, renamedOutputFileName, source); + } + else { + addedEntity = BridgeModelModifiableEntitiesKt.addFileCopyPackagingElementEntity(diff, fileUrl, null, source); + } + diff.getMutableExternalMapping("intellij.artifacts.packaging.elements").addMapping(addedEntity, this); + return addedEntity; + } } diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileOrDirectoryCopyPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileOrDirectoryCopyPackagingElement.java index 414a60d585f7..54fc2d4ea768 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileOrDirectoryCopyPackagingElement.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileOrDirectoryCopyPackagingElement.java @@ -22,6 +22,11 @@ import com.intellij.packaging.elements.PackagingElementOutputKind; import com.intellij.packaging.elements.PackagingElementResolvingContext; import com.intellij.packaging.elements.PackagingElementType; import com.intellij.util.xmlb.annotations.Attribute; +import com.intellij.workspaceModel.ide.VirtualFileUrlManagerUtil; +import com.intellij.workspaceModel.storage.bridgeEntities.ModifiableFileOrDirectoryPackagingElement; +import com.intellij.workspaceModel.storage.url.VirtualFileUrl; +import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager; +import kotlin.Unit; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -57,7 +62,24 @@ public abstract class FileOrDirectoryCopyPackagingElement myFilePath = filePath, + (builder, entity) -> { + if (filePathBefore.equals(filePath)) return; + + builder.modifyEntity(ModifiableFileOrDirectoryPackagingElement.class, entity, ent -> { + VirtualFileUrlManager manager = VirtualFileUrlManagerUtil.getInstance(VirtualFileUrlManager.Companion, myProject); + if (filePath != null) { + VirtualFileUrl fileUrl = manager.fromPath(filePath); + ent.setFilePath(fileUrl); + } + else { + ent.setFilePath(null); + } + return Unit.INSTANCE; + }); + }, true); } @NotNull diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/LibraryPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/LibraryPackagingElement.java index ecf9cf8ebe10..f95aca8a8adc 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/elements/LibraryPackagingElement.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/LibraryPackagingElement.java @@ -2,6 +2,7 @@ package com.intellij.packaging.impl.elements; import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.LibraryOrderEntry; import com.intellij.openapi.roots.OrderEntry; import com.intellij.openapi.roots.OrderRootType; @@ -19,6 +20,11 @@ import com.intellij.packaging.ui.ArtifactEditorContext; import com.intellij.packaging.ui.PackagingElementPresentation; import com.intellij.util.PathUtil; import com.intellij.util.xmlb.annotations.Attribute; +import com.intellij.workspaceModel.storage.EntitySource; +import com.intellij.workspaceModel.storage.WorkspaceEntity; +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder; +import com.intellij.workspaceModel.storage.bridgeEntities.*; +import kotlin.Unit; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -107,7 +113,31 @@ public class LibraryPackagingElement extends ComplexPackagingElement myLevel = level, + (builder, entity) -> { + if (levelBefore.equals(level)) return; + + builder.modifyEntity(ModifiableLibraryFilesPackagingElementEntity.class, entity, ent -> { + LibraryId libraryId = ent.getLibrary(); + if (libraryId != null) { + LibraryTableId newTableId; + if ("project".equals(level)) { + newTableId = LibraryTableId.ProjectLibraryTableId.INSTANCE; + } + else if ("module".equals(level)) { + throw new RuntimeException("Cannot set module level without module name"); + } + else { + newTableId = new LibraryTableId.GlobalLibraryTableId(level); + } + ent.setLibrary(libraryId.copy(libraryId.getName(), newTableId)); + } + return Unit.INSTANCE; + }); + }, true + ); } @Attribute(LIBRARY_NAME_ATTRIBUTE) @@ -116,7 +146,18 @@ public class LibraryPackagingElement extends ComplexPackagingElement myLibraryName = libraryName, + (builder, entity) -> { + builder.modifyEntity(ModifiableLibraryFilesPackagingElementEntity.class, entity, ent -> { + LibraryId libraryId = ent.getLibrary(); + if (libraryId != null) { + ent.setLibrary(libraryId.copy(libraryName, libraryId.getTableId())); + } + return Unit.INSTANCE; + }); + }, true + ); } @Attribute(MODULE_NAME_ATTRIBUTE) @@ -125,7 +166,22 @@ public class LibraryPackagingElement extends ComplexPackagingElement myModuleName = moduleName, + (builder, entity) -> { + if (moduleNameBefore.equals(moduleName)) return; + + builder.modifyEntity(ModifiableLibraryFilesPackagingElementEntity.class, entity, ent -> { + LibraryId libraryId = ent.getLibrary(); + if (libraryId != null) { + LibraryTableId newTableId = new LibraryTableId.ModuleLibraryTableId(new ModuleId(moduleName)); + ent.setLibrary(libraryId.copy(libraryId.getName(), newTableId)); + } + return Unit.INSTANCE; + }); + }, true + ); } @Override @@ -133,6 +189,34 @@ public class LibraryPackagingElement extends ComplexPackagingElement + diff --git a/java/compiler/openapi/intellij.java.compiler.iml b/java/compiler/openapi/intellij.java.compiler.iml index 1606d36aee4e..dd6b4fc40284 100644 --- a/java/compiler/openapi/intellij.java.compiler.iml +++ b/java/compiler/openapi/intellij.java.compiler.iml @@ -8,6 +8,7 @@ + @@ -15,6 +16,7 @@ + diff --git a/java/compiler/openapi/src/com/intellij/packaging/artifacts/ArtifactModel.java b/java/compiler/openapi/src/com/intellij/packaging/artifacts/ArtifactModel.java index 06981d20ffc6..11d7b70b61a2 100644 --- a/java/compiler/openapi/src/com/intellij/packaging/artifacts/ArtifactModel.java +++ b/java/compiler/openapi/src/com/intellij/packaging/artifacts/ArtifactModel.java @@ -15,6 +15,7 @@ */ package com.intellij.packaging.artifacts; +import com.intellij.util.concurrency.annotations.RequiresReadLock; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -22,9 +23,11 @@ import java.util.Collection; import java.util.List; public interface ArtifactModel { + @RequiresReadLock Artifact @NotNull [] getArtifacts(); @Nullable + @RequiresReadLock Artifact findArtifact(@NotNull String name); @NotNull @@ -34,7 +37,9 @@ public interface ArtifactModel { Artifact getOriginalArtifact(@NotNull Artifact artifact); @NotNull + @RequiresReadLock Collection getArtifactsByType(@NotNull ArtifactType type); + @RequiresReadLock List getAllArtifactsIncludingInvalid(); } diff --git a/java/compiler/openapi/src/com/intellij/packaging/elements/CompositePackagingElement.java b/java/compiler/openapi/src/com/intellij/packaging/elements/CompositePackagingElement.java index 37fa86e93295..e9563be31154 100644 --- a/java/compiler/openapi/src/com/intellij/packaging/elements/CompositePackagingElement.java +++ b/java/compiler/openapi/src/com/intellij/packaging/elements/CompositePackagingElement.java @@ -1,13 +1,21 @@ // Copyright 2000-2019 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.packaging.elements; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.workspaceModel.storage.ExternalEntityMapping; +import com.intellij.workspaceModel.storage.MutableExternalEntityMapping; +import com.intellij.workspaceModel.storage.WorkspaceEntity; +import com.intellij.workspaceModel.storage.bridgeEntities.CompositePackagingElementEntity; +import com.intellij.workspaceModel.storage.bridgeEntities.ModifiableCompositePackagingElementEntity; +import com.intellij.workspaceModel.storage.bridgeEntities.PackagingElementEntity; +import com.intellij.workspaceModel.storage.impl.VersionedEntityStorageOnBuilder; +import kotlin.Pair; +import kotlin.Unit; +import kotlin.sequences.SequencesKt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; public abstract class CompositePackagingElement extends PackagingElement implements RenameablePackagingElement { private final List> myChildren = new ArrayList<>(); @@ -18,6 +26,45 @@ public abstract class CompositePackagingElement extends PackagingElement i } public > T addOrFindChild(@NotNull T child) { + return this.update( + () -> myAddOrFindChild(child), + (builder, packagingElementEntity) -> { + MutableExternalEntityMapping> mapping = builder.getMutableExternalMapping("intellij.artifacts.packaging.elements"); + CompositePackagingElementEntity entity = (CompositePackagingElementEntity)packagingElementEntity; + List> children = ContainerUtil.map(entity.getChildren().iterator(), o -> { + PackagingElement data = mapping.getDataByEntity(o); + return Objects + .requireNonNullElseGet(data, () -> (PackagingElement)myPackagingElementInitializer.initialize(o, myProject, builder)); + }); + for (PackagingElement element : children) { + if (element.isEqualTo(child)) { + if (element instanceof CompositePackagingElement) { + final List> childrenOfChild = ((CompositePackagingElement)child).getChildren(); + ((CompositePackagingElement)element).addOrFindChildren(childrenOfChild); + } + + // Set correct storage if needed + setStorageForPackagingElement(element); + //noinspection unchecked + return (T) element; + } + } + // TODO not sure if the entity source is correct + PackagingElementEntity childEntity = (PackagingElementEntity)child.getOrAddEntity(builder, entity.entitySource, myProject); + builder.modifyEntity(ModifiableCompositePackagingElementEntity.class, entity, o -> { + List mutableList = SequencesKt.toMutableList(o.getChildren()); + mutableList.add(childEntity); + o.setChildren(SequencesKt.asSequence(mutableList.iterator())); + return Unit.INSTANCE; + }); + // Set storage for the new child + setStorageForPackagingElement(child); + return child; + }, + true); + } + + private > T myAddOrFindChild(@NotNull T child) { for (PackagingElement element : myChildren) { if (element.isEqualTo(child)) { if (element instanceof CompositePackagingElement) { @@ -33,6 +80,46 @@ public abstract class CompositePackagingElement extends PackagingElement i } public void addFirstChild(@NotNull PackagingElement child) { + this.update( + () -> myAddFirstChild(child), + (builder, packagingElementEntity) -> { + MutableExternalEntityMapping> mapping = builder.getMutableExternalMapping("intellij.artifacts.packaging.elements"); + CompositePackagingElementEntity entity = (CompositePackagingElementEntity)packagingElementEntity; + List>> pairs = + new ArrayList<>(ContainerUtil.map(entity.getChildren().iterator(), o -> { + PackagingElement data = mapping.getDataByEntity(o); + if (data == null) { + return new Pair<>(o, myPackagingElementInitializer.initialize(o, myProject, builder)); + } + return new Pair<>(o, data); + })); + PackagingElementEntity childEntity = (PackagingElementEntity)child.getOrAddEntity(builder, entity.entitySource, myProject); + pairs.add(0, new Pair<>(childEntity, child)); + for (int i = 1; i < pairs.size(); i++) { + Pair> pair = pairs.get(i); + PackagingElement element = pair.getSecond(); + if (element.isEqualTo(child)) { + if (element instanceof CompositePackagingElement) { + ((CompositePackagingElement)child).addOrFindChildren(((CompositePackagingElement)element).getChildren()); + } + pairs.remove(i); + break; + } + } + List newChildren = ContainerUtil.map(pairs, o -> o.getFirst()); + //noinspection unchecked + builder.modifyEntity(ModifiableCompositePackagingElementEntity.class, entity, o -> { + //noinspection unchecked + o.setChildren(SequencesKt.asSequence(newChildren.iterator())); + return Unit.INSTANCE; + }); + // Set storage for the new child + setStorageForPackagingElement(child); + }, true + ); + } + + private void myAddFirstChild(@NotNull PackagingElement child) { myChildren.add(0, child); for (int i = 1; i < myChildren.size(); i++) { PackagingElement element = myChildren.get(i); @@ -56,27 +143,102 @@ public abstract class CompositePackagingElement extends PackagingElement i @Nullable public PackagingElement moveChild(int index, int direction) { + return this.update( + () -> myMove(index, direction, myChildren), + (builder, packagingElementEntity) -> { + CompositePackagingElementEntity entity = (CompositePackagingElementEntity)packagingElementEntity; + ArrayList children = new ArrayList<>(ContainerUtil.collect(entity.getChildren().iterator())); + PackagingElementEntity entityToReturn = myMove(index, direction, children); + + //noinspection unchecked + builder.modifyEntity(ModifiableCompositePackagingElementEntity.class, entity, o -> { + //noinspection unchecked + o.setChildren(SequencesKt.asSequence(children.iterator())); + return Unit.INSTANCE; + }); + + if (entityToReturn == null) { + return null; + } + MutableExternalEntityMapping> mapping = builder.getMutableExternalMapping("intellij.artifacts.packaging.elements"); + PackagingElement objectToReturn = mapping.getDataByEntity(entityToReturn); + if (objectToReturn == null) { + return myPackagingElementInitializer.initialize(entityToReturn, myProject, builder); + } + return objectToReturn; + }, + true); + } + + @Nullable + private static T myMove(int index, int direction, List elements) { int target = index + direction; - if (0 <= index && index < myChildren.size() && 0 <= target && target < myChildren.size()) { - final PackagingElement element1 = myChildren.get(index); - final PackagingElement element2 = myChildren.get(target); - myChildren.set(index, element2); - myChildren.set(target, element1); + if (0 <= index && index < elements.size() && 0 <= target && target < elements.size()) { + final T element1 = elements.get(index); + final T element2 = elements.get(target); + elements.set(index, element2); + elements.set(target, element1); return element1; } return null; } public void removeChild(@NotNull PackagingElement child) { - myChildren.remove(child); + this.update( + () -> myChildren.remove(child), + (builder, packagingElementEntity) -> { + MutableExternalEntityMapping> mapping = builder.getMutableExternalMapping("intellij.artifacts.packaging.elements"); + WorkspaceEntity entity = ContainerUtil.getFirstItem(mapping.getEntities(child)); + if (entity != null) { + builder.removeEntity(entity); + } + }, false + ); } public void removeChildren(@NotNull Collection> children) { - myChildren.removeAll(children); + this.update( + () -> myChildren.removeAll(children), + (builder, packagingElementEntity) -> { + MutableExternalEntityMapping> mapping = builder.getMutableExternalMapping("intellij.artifacts.packaging.elements"); + children.stream() + .map(o -> ContainerUtil.getFirstItem(mapping.getEntities(o))) + .filter(Objects::nonNull) + .forEach(o -> builder.removeEntity(o)); + }, false + ); } @NotNull public List> getChildren() { + if (myStorage == null) { + return myGetChildren(); + } + else { + ExternalEntityMapping mapping = myStorage.getCurrent().getExternalMapping("intellij.artifacts.packaging.elements"); + List mappedEntities = mapping.getEntities(this); + if (mappedEntities.isEmpty()) { + throw new RuntimeException(this.getClass().getName()); + } + PackagingElementEntity packagingElementEntity = (PackagingElementEntity)mappedEntities.get(0); + if (packagingElementEntity instanceof CompositePackagingElementEntity) { + CompositePackagingElementEntity entity = (CompositePackagingElementEntity)packagingElementEntity; + return ContainerUtil.map(entity.getChildren().iterator(), o -> { + PackagingElement data = (PackagingElement)mapping.getDataByEntity(o); + if (data == null) { + data = (PackagingElement)myPackagingElementInitializer.initialize(o, myProject, myStorage.getBase()); + } + setStorageForPackagingElement(data); + return data; + }); + } + else { + throw new RuntimeException("Expected composite element here"); + } + } + } + + private List> myGetChildren() { if (myUnmodifiableChildren == null) { myUnmodifiableChildren = Collections.unmodifiableList(myChildren); } @@ -89,16 +251,36 @@ public abstract class CompositePackagingElement extends PackagingElement i } public void removeAllChildren() { - myChildren.clear(); + this.update( + () -> myChildren.clear(), + (builder, packagingElementEntity) -> { + CompositePackagingElementEntity entity = (CompositePackagingElementEntity)packagingElementEntity; + // I just don't understand what to do to avoid this warning + //noinspection unchecked + builder.modifyEntity(ModifiableCompositePackagingElementEntity.class, entity, o -> { + //noinspection unchecked + o.setChildren(SequencesKt.emptySequence()); + return Unit.INSTANCE; + }); + }, false + ); } @Nullable public CompositePackagingElement findCompositeChild(@NotNull String name) { - for (PackagingElement child : myChildren) { + for (PackagingElement child : getChildren()) { if (child instanceof CompositePackagingElement && name.equals(((CompositePackagingElement)child).getName())) { return (CompositePackagingElement)child; } } return null; } + + private void setStorageForPackagingElement(PackagingElement packagingElement) { + boolean storageIsDiff = myStorage instanceof VersionedEntityStorageOnBuilder; + if (storageIsDiff && (packagingElement.storageIsStore() || !packagingElement.hasStorage())) { + packagingElement.setStorage(myStorage, myProject, myElementsWithDiff, myPackagingElementInitializer); + myElementsWithDiff.add(packagingElement); + } + } } diff --git a/java/compiler/openapi/src/com/intellij/packaging/elements/ElementInitializer.java b/java/compiler/openapi/src/com/intellij/packaging/elements/ElementInitializer.java new file mode 100644 index 000000000000..f7974c1c1b90 --- /dev/null +++ b/java/compiler/openapi/src/com/intellij/packaging/elements/ElementInitializer.java @@ -0,0 +1,19 @@ +// Copyright 2000-2021 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.packaging.elements; + +import com.intellij.openapi.project.Project; +import com.intellij.workspaceModel.storage.WorkspaceEntityStorage; +import com.intellij.workspaceModel.storage.bridgeEntities.CompositePackagingElementEntity; +import com.intellij.workspaceModel.storage.bridgeEntities.PackagingElementEntity; +import org.jetbrains.annotations.NotNull; + +/** + * This interface was introduced to get an opportunity at {@link CompositePackagingElement} to initialize + * elements from other modules e.g `intellij.java.compiler.impl`. This case is needed for the new project model + * when we have not changed root, but child elements changed and thus we need to update/add external mapping. + * This case was found at {@link com.intellij.java.configurationStore.ReloadProjectTest} + */ +public interface ElementInitializer { + PackagingElement initialize(@NotNull PackagingElementEntity entity, @NotNull Project project, @NotNull WorkspaceEntityStorage storage); + PackagingElement initialize(@NotNull CompositePackagingElementEntity entity, @NotNull Project project, @NotNull WorkspaceEntityStorage storage); +} diff --git a/java/compiler/openapi/src/com/intellij/packaging/elements/PackagingElement.java b/java/compiler/openapi/src/com/intellij/packaging/elements/PackagingElement.java index ac4de2a8381a..a2d03ba6f09c 100644 --- a/java/compiler/openapi/src/com/intellij/packaging/elements/PackagingElement.java +++ b/java/compiler/openapi/src/com/intellij/packaging/elements/PackagingElement.java @@ -2,9 +2,27 @@ package com.intellij.packaging.elements; import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.JDOMUtil; import com.intellij.packaging.ui.ArtifactEditorContext; import com.intellij.packaging.ui.PackagingElementPresentation; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.xmlb.XmlSerializer; +import com.intellij.workspaceModel.storage.*; +import com.intellij.workspaceModel.storage.bridgeEntities.BridgeModelModifiableEntitiesKt; +import com.intellij.workspaceModel.storage.bridgeEntities.CustomPackagingElementEntity; +import com.intellij.workspaceModel.storage.bridgeEntities.PackagingElementEntity; +import com.intellij.workspaceModel.storage.impl.VersionedEntityStorageOnBuilder; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Supplier; /** * Describes an element in artifact's output layout. @@ -15,6 +33,11 @@ import org.jetbrains.annotations.NotNull; public abstract class PackagingElement implements PersistentStateComponent { private final PackagingElementType myType; + protected @Nullable VersionedEntityStorage myStorage; + protected Project myProject; + protected Set> myElementsWithDiff; + protected ElementInitializer myPackagingElementInitializer; + protected PackagingElement(@NotNull PackagingElementType type) { myType = type; } @@ -33,4 +56,102 @@ public abstract class PackagingElement implements PersistentStateComponent public PackagingElementOutputKind getFilesKind(PackagingElementResolvingContext context) { return PackagingElementOutputKind.OTHER; } + + /** + * This method gets an entity from the diff mappings and create a new one for the current element + */ + public WorkspaceEntity getOrAddEntity(@NotNull WorkspaceEntityStorageBuilder diff, + @NotNull EntitySource source, + @NotNull Project project) { + WorkspaceEntity existingEntity = getExistingEntity(diff); + if (existingEntity != null) return existingEntity; + + S state = this.getState(); + String xmlTag = ""; + if (state != null) { + xmlTag = JDOMUtil.write(XmlSerializer.serialize(state)); + } + + List children = new ArrayList<>(); + if (this instanceof CompositePackagingElement) { + children.addAll( + ContainerUtil.map(((CompositePackagingElement)this).getChildren(), + o -> (PackagingElementEntity)o.getOrAddEntity(diff, source, project)) + ); + } + + CustomPackagingElementEntity addedEntity = + BridgeModelModifiableEntitiesKt.addCustomPackagingElementEntity(diff, this.getType().getId(), xmlTag, children, source); + + diff.getMutableExternalMapping("intellij.artifacts.packaging.elements").addMapping(addedEntity, this); + return addedEntity; + } + + protected @Nullable WorkspaceEntity getExistingEntity(WorkspaceEntityStorageBuilder diff) { + ExternalEntityMapping mapping = diff.getExternalMapping("intellij.artifacts.packaging.elements"); + Optional existing = mapping.getEntities(this).stream().findFirst(); + + return existing.orElse(null); + } + + public void setStorage(@NotNull VersionedEntityStorage storage, @NotNull Project project, Set> elementsWithDiff, + @NotNull ElementInitializer initializer) { + // TODO set data to children + myStorage = storage; + myProject = project; + myElementsWithDiff = elementsWithDiff; + myPackagingElementInitializer = initializer; + } + + public boolean hasStorage() { + return myStorage != null; + } + + public boolean storageIsStore() { + return myStorage != null && !(myStorage instanceof VersionedEntityStorageOnBuilder); + } + + protected void update(Runnable noStorageChange, + BiConsumer changeOnBuilder, + boolean filterCustomElements) { + update( + () -> { + noStorageChange.run(); + return null; + }, + (builder, element) -> { + changeOnBuilder.accept(builder, element); + return null; + }, + filterCustomElements + ); + } + + protected T update(Supplier noStorageChange, + BiFunction changeOnBuilder, + boolean filterCustomElements) { + if (myStorage == null) { + return noStorageChange.get(); + } + else { + if (!(myStorage instanceof VersionedEntityStorageOnBuilder)) { + noStorageChange.get(); + throw new RuntimeException(); + } + else { + T nativeValue = noStorageChange.get(); + + WorkspaceEntityStorageBuilder builder = ((VersionedEntityStorageOnBuilder)myStorage).getBuilder(); + MutableExternalEntityMapping> mapping = builder.getMutableExternalMapping("intellij.artifacts.packaging.elements"); + PackagingElementEntity entity = (PackagingElementEntity)ContainerUtil.getFirstItem(mapping.getEntities(this)); + if (filterCustomElements && entity instanceof CustomPackagingElementEntity) { + return nativeValue; + } + if (entity == null) { + throw new RuntimeException("Cannot find an entity"); + } + return changeOnBuilder.apply(builder, entity); + } + } + } } diff --git a/java/compiler/tests/com/intellij/compiler/artifacts/ArtifactsWithCustomElementsTest.kt b/java/compiler/tests/com/intellij/compiler/artifacts/ArtifactsWithCustomElementsTest.kt new file mode 100644 index 000000000000..eb4974f8f6fe --- /dev/null +++ b/java/compiler/tests/com/intellij/compiler/artifacts/ArtifactsWithCustomElementsTest.kt @@ -0,0 +1,147 @@ +// Copyright 2000-2021 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.compiler.artifacts + +import com.intellij.ide.projectView.PresentationData +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.project.Project +import com.intellij.packaging.artifacts.Artifact +import com.intellij.packaging.artifacts.ArtifactManager +import com.intellij.packaging.elements.CompositePackagingElement +import com.intellij.packaging.elements.PackagingElement +import com.intellij.packaging.elements.PackagingElementFactory +import com.intellij.packaging.elements.PackagingElementType +import com.intellij.packaging.impl.artifacts.PlainArtifactType +import com.intellij.packaging.ui.ArtifactEditorContext +import com.intellij.packaging.ui.PackagingElementPresentation +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.PlatformTestUtil +import com.intellij.testFramework.rules.ProjectModelRule +import com.intellij.ui.SimpleTextAttributes +import com.intellij.util.xmlb.annotations.Attribute +import org.junit.Assert +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import java.io.File +import java.util.function.Supplier + +class ArtifactsWithCustomElementsTest { + @Rule + @JvmField + val projectModel = ProjectModelRule(true) + + @Test + fun `create custom artifact`() { + val project = projectModel.project + + val artifactManager = ArtifactManager.getInstance(project) + WriteAction.runAndWait { + val artifact = artifactManager.addArtifact("Artifact", PlainArtifactType.getInstance(), + PackagingElementFactory.getInstance().createArtifactRootElement()) + val modifiableModel = artifactManager.createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + modifiableArtifact.rootElement.addOrFindChild(MyPackagingElement("MyData")) + modifiableModel.commit() + } + + PlatformTestUtil.saveProject(project) + + Assert.assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + } + + @Test + fun `modify custom artifact`() { + val project = projectModel.project + + val artifactManager = ArtifactManager.getInstance(project) + WriteAction.runAndWait { + val artifact = artifactManager.addArtifact("Artifact", PlainArtifactType.getInstance(), + PackagingElementFactory.getInstance().createArtifactRootElement()) + val modifiableModel = artifactManager.createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + modifiableArtifact.rootElement.addOrFindChild(MyPackagingElement("MyData")) + modifiableModel.commit() + + val modifiableModel2 = artifactManager.createModifiableModel() + val modifiableArtifact2 = modifiableModel2.getOrCreateModifiableArtifact(artifact) + val packagingElement = modifiableArtifact2.rootElement.children[0] as MyPackagingElement + packagingElement.data = "AnotherData" + modifiableModel2.commit() + } + + PlatformTestUtil.saveProject(project) + + Assert.assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + } + + companion object { + @JvmField + @ClassRule + val appRule = ApplicationRule() + } +} + +object MyPackagingElementType : PackagingElementType("MyElement", Supplier { "My element" }) { + override fun canCreate(context: ArtifactEditorContext, artifact: Artifact): Boolean { + return true + } + + override fun chooseAndCreate(context: ArtifactEditorContext, + artifact: Artifact, + parent: CompositePackagingElement<*>): List> { + return listOf(MyPackagingElement()) + } + + override fun createEmpty(project: Project): MyPackagingElement { + return MyPackagingElement() + } +} + +class MyPackagingElement(_data: String?) : PackagingElement(MyPackagingElementType) { + + constructor() : this(null) + + @Attribute("data") + var data: String? = _data + + override fun createPresentation(context: ArtifactEditorContext): PackagingElementPresentation { + return object : PackagingElementPresentation() { + override fun getPresentableName(): String = "My element" + + override fun render(presentationData: PresentationData, + mainAttributes: SimpleTextAttributes?, + commentAttributes: SimpleTextAttributes?) { + } + + override fun getWeight(): Int = 0 + } + } + + override fun isEqualTo(element: PackagingElement<*>): Boolean { + return element is MyPackagingElement && element.data == this.data + } + + override fun getState(): MyPackagingElement { + return this + } + + override fun loadState(state: MyPackagingElement) { + this.data = state.data + } +} diff --git a/java/compiler/tests/com/intellij/compiler/artifacts/ElementsModificationTest.kt b/java/compiler/tests/com/intellij/compiler/artifacts/ElementsModificationTest.kt new file mode 100644 index 000000000000..3448c60882e3 --- /dev/null +++ b/java/compiler/tests/com/intellij/compiler/artifacts/ElementsModificationTest.kt @@ -0,0 +1,409 @@ +// Copyright 2000-2021 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.compiler.artifacts + +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.vfs.JarFileSystem +import com.intellij.packaging.artifacts.Artifact +import com.intellij.packaging.artifacts.ArtifactManager +import com.intellij.packaging.elements.PackagingElementFactory +import com.intellij.packaging.impl.artifacts.PlainArtifactType +import com.intellij.packaging.impl.elements.ExtractedDirectoryPackagingElement +import com.intellij.packaging.impl.elements.FileCopyPackagingElement +import com.intellij.packaging.impl.elements.LibraryPackagingElement +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.PlatformTestUtil +import com.intellij.testFramework.VfsTestUtil +import com.intellij.testFramework.rules.ProjectModelRule +import com.intellij.workspaceModel.ide.WorkspaceModel +import org.junit.Assert.assertEquals +import org.junit.Assume.assumeTrue +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import java.io.File + +class ElementsModificationTest { + @Rule + @JvmField + val projectModel = ProjectModelRule(true) + + @Test + fun `create library artifact`() { + val project = projectModel.project + + val artifactManager = ArtifactManager.getInstance(project) + WriteAction.runAndWait { + val artifact = artifactManager.addArtifact("Artifact", PlainArtifactType.getInstance(), + PackagingElementFactory.getInstance().createArtifactRootElement()) + val modifiableModel = artifactManager.createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + modifiableArtifact.rootElement.addOrFindChild(PackagingElementFactory.getInstance().createLibraryFiles("One", "project", null)) + modifiableModel.commit() + } + + PlatformTestUtil.saveProject(project) + + assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + } + + @Test + fun `rename library artifact`() { + val project = projectModel.project + + val artifactManager = ArtifactManager.getInstance(project) + val artifact = WriteAction.computeAndWait { + val rootElement = PackagingElementFactory.getInstance().createArtifactRootElement() + rootElement.addOrFindChild(PackagingElementFactory.getInstance().createLibraryFiles("One", "project", null)) + artifactManager.addArtifact("Artifact", PlainArtifactType.getInstance(), rootElement) + } + + PlatformTestUtil.saveProject(project) + WriteAction.runAndWait { + val modifiableModel = artifactManager.createModifiableModel() + val mutableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + val libraryElement = mutableArtifact.rootElement.children[0] as LibraryPackagingElement + libraryElement.libraryName = "AnotherName" + modifiableModel.commit() + } + + PlatformTestUtil.saveProject(project) + + assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + } + + @Test + fun `change level of library artifact`() { + val project = projectModel.project + + val artifactManager = ArtifactManager.getInstance(project) + val artifact = WriteAction.computeAndWait { + val rootElement = PackagingElementFactory.getInstance().createArtifactRootElement() + rootElement.addOrFindChild(PackagingElementFactory.getInstance().createLibraryFiles("One", "project", null)) + artifactManager.addArtifact("Artifact", PlainArtifactType.getInstance(), rootElement) + } + + PlatformTestUtil.saveProject(project) + WriteAction.runAndWait { + val modifiableModel = artifactManager.createModifiableModel() + val mutableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + val libraryElement = mutableArtifact.rootElement.children[0] as LibraryPackagingElement + libraryElement.level = "Custom" + modifiableModel.commit() + } + + PlatformTestUtil.saveProject(project) + + assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + } + + @Test + fun `change module name of library artifact`() { + val project = projectModel.project + + val artifactManager = ArtifactManager.getInstance(project) + val artifact = WriteAction.computeAndWait { + val rootElement = PackagingElementFactory.getInstance().createArtifactRootElement() + rootElement.addOrFindChild(PackagingElementFactory.getInstance().createLibraryFiles("One", "module", "myModule")) + artifactManager.addArtifact("Artifact", PlainArtifactType.getInstance(), rootElement) + } + + PlatformTestUtil.saveProject(project) + WriteAction.runAndWait { + val modifiableModel = artifactManager.createModifiableModel() + val mutableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + val libraryElement = mutableArtifact.rootElement.children[0] as LibraryPackagingElement + libraryElement.moduleName = "AnotherModuleName" + modifiableModel.commit() + } + + PlatformTestUtil.saveProject(project) + + assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + } + + @Test + fun `change change path in jar for extracted directory`() { + val project = projectModel.project + + val artifactManager = ArtifactManager.getInstance(project) + val artifact = WriteAction.computeAndWait { + val rootElement = PackagingElementFactory.getInstance().createArtifactRootElement() + + val file = VfsTestUtil.createFile(projectModel.baseProjectDir.virtualFileRoot, "MyPath.jar", "") + val jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(file)!! + rootElement.addOrFindChild(PackagingElementFactory.getInstance().createExtractedDirectory(jarRoot)) + artifactManager.addArtifact("Artifact", PlainArtifactType.getInstance(), rootElement) + } + + PlatformTestUtil.saveProject(project) + WriteAction.runAndWait { + val modifiableModel = artifactManager.createModifiableModel() + val mutableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + val element = mutableArtifact.rootElement.children[0] as ExtractedDirectoryPackagingElement + element.pathInJar = "AnotherPath" + modifiableModel.commit() + } + + PlatformTestUtil.saveProject(project) + + assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + } + + @Test + fun `update file copy packaging element`() { + val project = projectModel.project + + val artifactManager = ArtifactManager.getInstance(project) + val artifact = WriteAction.computeAndWait { + val rootElement = PackagingElementFactory.getInstance().createArtifactRootElement() + + rootElement.addOrFindChild(PackagingElementFactory.getInstance().createFileCopy("myPath", null)) + artifactManager.addArtifact("Artifact", PlainArtifactType.getInstance(), rootElement) + } + + PlatformTestUtil.saveProject(project) + WriteAction.runAndWait { + val modifiableModel = artifactManager.createModifiableModel() + val mutableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + val element = mutableArtifact.rootElement.children[0] as FileCopyPackagingElement + element.renamedOutputFileName = "Rename" + modifiableModel.commit() + } + + PlatformTestUtil.saveProject(project) + + assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + } + + @Test + fun `update file copy packaging element rename`() { + val project = projectModel.project + + val artifactManager = ArtifactManager.getInstance(project) + val artifact = WriteAction.computeAndWait { + val rootElement = PackagingElementFactory.getInstance().createArtifactRootElement() + + rootElement.addOrFindChild(PackagingElementFactory.getInstance().createFileCopy("myPath", null)) + artifactManager.addArtifact("Artifact", PlainArtifactType.getInstance(), rootElement) + } + + PlatformTestUtil.saveProject(project) + WriteAction.runAndWait { + val modifiableModel = artifactManager.createModifiableModel() + val mutableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + val element = mutableArtifact.rootElement.children[0] as FileCopyPackagingElement + element.rename("Rename") + modifiableModel.commit() + } + + PlatformTestUtil.saveProject(project) + + assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + } + + @Test + fun `update file copy packaging element set file path`() { + val project = projectModel.project + + val artifactManager = ArtifactManager.getInstance(project) + val artifact = WriteAction.computeAndWait { + val rootElement = PackagingElementFactory.getInstance().createArtifactRootElement() + + rootElement.addOrFindChild(PackagingElementFactory.getInstance().createFileCopy("myPath", null)) + artifactManager.addArtifact("Artifact", PlainArtifactType.getInstance(), rootElement) + } + + PlatformTestUtil.saveProject(project) + WriteAction.runAndWait { + val modifiableModel = artifactManager.createModifiableModel() + val mutableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + val element = mutableArtifact.rootElement.children[0] as FileCopyPackagingElement + element.filePath = "AnotherFilePath" + modifiableModel.commit() + } + + PlatformTestUtil.saveProject(project) + + assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + } + + @Test + fun `modification double modification`() { + val project = projectModel.project + + val artifactManager = ArtifactManager.getInstance(project) + val artifact = WriteAction.computeAndWait { + val rootElement = PackagingElementFactory.getInstance().createArtifactRootElement() + rootElement.addOrFindChild(PackagingElementFactory.getInstance().createLibraryFiles("One", "project", null)) + artifactManager.addArtifact("Artifact", PlainArtifactType.getInstance(), rootElement) + } + + PlatformTestUtil.saveProject(project) + WriteAction.runAndWait { + val modifiableModel = artifactManager.createModifiableModel() + val mutableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + val libraryElement = mutableArtifact.rootElement.children[0] as LibraryPackagingElement + libraryElement.libraryName = "Two" + modifiableModel.commit() + } + + PlatformTestUtil.saveProject(project) + assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + + PlatformTestUtil.saveProject(project) + WriteAction.runAndWait { + val modifiableModel = artifactManager.createModifiableModel() + val mutableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + val libraryElement = mutableArtifact.rootElement.children[0] as LibraryPackagingElement + libraryElement.libraryName = "Three" + modifiableModel.commit() + } + + PlatformTestUtil.saveProject(project) + + assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + } + + @Test + fun `modification with dispose`() { + + // Old artifacts implementation works incorrect in such case + assumeTrue(WorkspaceModel.enabledForArtifacts) + + val project = projectModel.project + + val artifactManager = ArtifactManager.getInstance(project) + val artifact = WriteAction.computeAndWait { + val rootElement = PackagingElementFactory.getInstance().createArtifactRootElement() + rootElement.addOrFindChild(PackagingElementFactory.getInstance().createLibraryFiles("One", "project", null)) + artifactManager.addArtifact("Artifact", PlainArtifactType.getInstance(), rootElement) + } + + PlatformTestUtil.saveProject(project) + WriteAction.runAndWait { + val modifiableModel = artifactManager.createModifiableModel() + val mutableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + val libraryElement = mutableArtifact.rootElement.children[0] as LibraryPackagingElement + libraryElement.libraryName = "Two" + modifiableModel.dispose() + } + + PlatformTestUtil.saveProject(project) + assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + + PlatformTestUtil.saveProject(project) + WriteAction.runAndWait { + val modifiableModel = artifactManager.createModifiableModel() + val mutableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + val libraryElement = mutableArtifact.rootElement.children[0] as LibraryPackagingElement + libraryElement.libraryName = "Three" + modifiableModel.commit() + } + + PlatformTestUtil.saveProject(project) + + assertEquals( + """ + + ${'$'}PROJECT_DIR${'$'}/out/artifacts/Artifact + + + + +""", File(projectModel.baseProjectDir.root, ".idea/artifacts/Artifact.xml").readText()) + } + + companion object { + @JvmField + @ClassRule + val appRule = ApplicationRule() + } +} \ No newline at end of file diff --git a/java/compiler/tests/com/intellij/compiler/artifacts/IncrementalArtifactsCompilerTest.java b/java/compiler/tests/com/intellij/compiler/artifacts/IncrementalArtifactsCompilerTest.java index 560fab62ce5c..e697fdc14eea 100644 --- a/java/compiler/tests/com/intellij/compiler/artifacts/IncrementalArtifactsCompilerTest.java +++ b/java/compiler/tests/com/intellij/compiler/artifacts/IncrementalArtifactsCompilerTest.java @@ -7,7 +7,10 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.packaging.artifacts.Artifact; import com.intellij.packaging.artifacts.ModifiableArtifactModel; +import com.intellij.packaging.elements.CompositePackagingElement; import com.intellij.packaging.elements.PackagingElementFactory; +import com.intellij.packaging.impl.elements.ArchivePackagingElement; +import com.intellij.packaging.impl.elements.DirectoryPackagingElement; import com.intellij.testFramework.PsiTestUtil; import com.intellij.testFramework.VfsTestUtil; @@ -36,6 +39,175 @@ public class IncrementalArtifactsCompilerTest extends ArtifactCompilerTestCase { assertOutput(a, fs().dir("dir").file("file.txt", "b")); } + public void testFileNearDir() { + VirtualFile file2 = createFile("1.txt", "a"); + final Artifact a = addArtifact(root().dir("dir").file(file2)); + make(a); + assertOutput(a, fs().dir("dir").file("1.txt")); + + VirtualFile file = createFile("2.txt", "a"); + WriteAction.runAndWait(() -> { + ModifiableArtifactModel model = getArtifactManager().createModifiableModel(); + model.getOrCreateModifiableArtifact(a).getRootElement() + .addFirstChild(PackagingElementFactory.getInstance().createFileCopy(file.getPath(), null)); + model.commit(); + }); + + make(a); + assertOutput(a, fs().file("2.txt").dir("dir").file("1.txt")); + } + + public void testTwoFilesInOneDir() { + VirtualFile file2 = createFile("1.txt", "a"); + final Artifact a = addArtifact(root().dir("dir").file(file2)); + make(a); + assertOutput(a, fs().dir("dir").file("1.txt")); + + VirtualFile file = createFile("2.txt", "a"); + WriteAction.runAndWait(() -> { + ModifiableArtifactModel model = getArtifactManager().createModifiableModel(); + CompositePackagingElement rootElement = model.getOrCreateModifiableArtifact(a).getRootElement(); + DirectoryPackagingElement element = (DirectoryPackagingElement) rootElement.getChildren().get(0); + element.addFirstChild(PackagingElementFactory.getInstance().createFileCopy(file.getPath(), null)); + model.commit(); + }); + + make(a); + assertOutput(a, fs().dir("dir").file("1.txt").file("2.txt")); + } + + public void testTwoFilesInOneDirUsingFindCompositeChild() { + VirtualFile file2 = createFile("1.txt", "a"); + final Artifact a = addArtifact(root().dir("dir").file(file2)); + make(a); + assertOutput(a, fs().dir("dir").file("1.txt")); + + VirtualFile file = createFile("2.txt", "a"); + WriteAction.runAndWait(() -> { + ModifiableArtifactModel model = getArtifactManager().createModifiableModel(); + CompositePackagingElement rootElement = model.getOrCreateModifiableArtifact(a).getRootElement(); + DirectoryPackagingElement element = (DirectoryPackagingElement)rootElement.findCompositeChild("dir"); + element.addFirstChild(PackagingElementFactory.getInstance().createFileCopy(file.getPath(), null)); + model.commit(); + }); + + make(a); + assertOutput(a, fs().dir("dir").file("1.txt").file("2.txt")); + } + + public void testTwoFilesInOneDirUsingAddOrCreate() { + VirtualFile file2 = createFile("1.txt", "a"); + final Artifact a = addArtifact(root().dir("dir").file(file2)); + make(a); + assertOutput(a, fs().dir("dir").file("1.txt")); + + VirtualFile file = createFile("2.txt", "a"); + WriteAction.runAndWait(() -> { + ModifiableArtifactModel model = getArtifactManager().createModifiableModel(); + CompositePackagingElement rootElement = model.getOrCreateModifiableArtifact(a).getRootElement(); + DirectoryPackagingElement element = rootElement.addOrFindChild(new DirectoryPackagingElement("dir")); + element.addFirstChild(PackagingElementFactory.getInstance().createFileCopy(file.getPath(), null)); + model.commit(); + }); + + make(a); + assertOutput(a, fs().dir("dir").file("1.txt").file("2.txt")); + } + + public void testRenameArchive() { + VirtualFile file = createFile("file.txt", "a"); + final Artifact a = addArtifact(root().archive("myArchive").file(file)); + make(a); + assertOutput(a, fs().archive("myArchive").file("file.txt")); + + WriteAction.runAndWait(() -> { + ModifiableArtifactModel model = getArtifactManager().createModifiableModel(); + CompositePackagingElement rootElement = model.getOrCreateModifiableArtifact(a).getRootElement(); + ArchivePackagingElement element = (ArchivePackagingElement)rootElement.getChildren().get(0); + element.rename("AnotherName"); + model.commit(); + }); + + make(a); + assertOutput(a, fs().archive("AnotherName").file("file.txt")); + } + + public void testSetNewNameToArchive() { + VirtualFile file = createFile("file.txt", "a"); + final Artifact a = addArtifact(root().archive("myArchive").file(file)); + make(a); + assertOutput(a, fs().archive("myArchive").file("file.txt")); + + WriteAction.runAndWait(() -> { + ModifiableArtifactModel model = getArtifactManager().createModifiableModel(); + CompositePackagingElement rootElement = model.getOrCreateModifiableArtifact(a).getRootElement(); + ArchivePackagingElement element = (ArchivePackagingElement)rootElement.getChildren().get(0); + element.setArchiveFileName("AnotherName"); + model.commit(); + }); + + make(a); + assertOutput(a, fs().archive("AnotherName").file("file.txt")); + } + + public void testSetDirectoryName() { + VirtualFile file = createFile("file.txt", "a"); + final Artifact a = addArtifact(root().dir("myArchive").file(file)); + /// Because of some reason the old directory is not cleared, but I actually test the rename itself here + //make(a); + //assertOutput(a, fs().dir("myArchive").file("file.txt")); + + WriteAction.runAndWait(() -> { + ModifiableArtifactModel model = getArtifactManager().createModifiableModel(); + CompositePackagingElement rootElement = model.getOrCreateModifiableArtifact(a).getRootElement(); + DirectoryPackagingElement element = (DirectoryPackagingElement)rootElement.getChildren().get(0); + element.setDirectoryName("AnotherName"); + model.commit(); + }); + + make(a); + assertOutput(a, fs().dir("AnotherName").file("file.txt")); + } + + public void testRenameDirectory() { + VirtualFile file = createFile("file.txt", "a"); + final Artifact a = addArtifact(root().dir("myArchive").file(file)); + /// Because of some reason the old directory is not cleared, but I actually test the rename itself here + //make(a); + //assertOutput(a, fs().dir("myArchive").file("file.txt")); + + WriteAction.runAndWait(() -> { + ModifiableArtifactModel model = getArtifactManager().createModifiableModel(); + CompositePackagingElement rootElement = model.getOrCreateModifiableArtifact(a).getRootElement(); + DirectoryPackagingElement element = (DirectoryPackagingElement)rootElement.getChildren().get(0); + element.rename("AnotherName"); + model.commit(); + }); + + make(a); + assertOutput(a, fs().dir("AnotherName").file("file.txt")); + } + + public void testFilesInTwoDirs() { + VirtualFile file2 = createFile("1.txt", "a"); + final Artifact a = addArtifact(root().dir("dir").dir("anotherDir").file(file2)); + make(a); + assertOutput(a, fs().dir("dir").dir("anotherDir").file("1.txt")); + + VirtualFile file = createFile("2.txt", "a"); + WriteAction.runAndWait(() -> { + ModifiableArtifactModel model = getArtifactManager().createModifiableModel(); + CompositePackagingElement rootElement = model.getOrCreateModifiableArtifact(a).getRootElement(); + DirectoryPackagingElement element = rootElement.addOrFindChild(new DirectoryPackagingElement("dir")); + DirectoryPackagingElement anotherElement = (DirectoryPackagingElement)element.getChildren().get(0); + anotherElement.addFirstChild(PackagingElementFactory.getInstance().createFileCopy(file.getPath(), null)); + model.commit(); + }); + + make(a); + assertOutput(a, fs().dir("dir").dir("anotherDir").file("1.txt").file("2.txt")); + } + public void testAddRemoveJavaClass() { final VirtualFile file = createFile("src/A.java", "public class A {}"); final Module module = addModule("a", file.getParent()); diff --git a/java/compiler/tests/com/intellij/compiler/artifacts/propertybased/ArtifactTestUtils.kt b/java/compiler/tests/com/intellij/compiler/artifacts/propertybased/ArtifactTestUtils.kt new file mode 100644 index 000000000000..268a74a10c85 --- /dev/null +++ b/java/compiler/tests/com/intellij/compiler/artifacts/propertybased/ArtifactTestUtils.kt @@ -0,0 +1,55 @@ +// Copyright 2000-2021 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.compiler.artifacts.propertybased + +import com.intellij.openapi.project.Project +import com.intellij.packaging.artifacts.Artifact +import com.intellij.packaging.artifacts.ArtifactManager +import com.intellij.packaging.elements.CompositePackagingElement +import com.intellij.packaging.elements.PackagingElement +import com.intellij.packaging.impl.artifacts.workspacemodel.toElement +import com.intellij.packaging.impl.elements.ArtifactRootElementImpl +import com.intellij.workspaceModel.ide.WorkspaceModel +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder +import com.intellij.workspaceModel.storage.bridgeEntities.ArtifactEntity +import com.intellij.workspaceModel.storage.bridgeEntities.PackagingElementEntity +import org.junit.Assert + +internal fun artifact(project: Project, name: String): Artifact { + val bridgeArtifact = ArtifactManager.getInstance(project).findArtifact(name) + Assert.assertNotNull(bridgeArtifact) + return bridgeArtifact!! +} + +internal fun artifactEntity(project: Project, name: String): ArtifactEntity { + val artifactEntities = WorkspaceModel.getInstance(project).entityStorage.current.entities(ArtifactEntity::class.java) + val artifactEntity = artifactEntities.find { it.name == name } + Assert.assertNotNull(artifactEntity) + return artifactEntity!! +} + +internal fun assertTreesEquals(project: Project, left: PackagingElement<*>, right: PackagingElementEntity) { + val rightElement = right.toElement(project, WorkspaceEntityStorageBuilder.create()) + + assertElementsEquals(left, rightElement) +} + +internal fun assertElementsEquals(left: PackagingElement<*>, right: PackagingElement<*>) { + if (left !is ArtifactRootElementImpl || right !is ArtifactRootElementImpl) { + if (!left.isEqualTo(right)) { + Assert.fail("Elements are not equals. $left <-> $right") + } + } + + if (left is CompositePackagingElement<*> && right is CompositePackagingElement<*>) { + val leftChildren = left.children + val rightChildren = right.children + if (leftChildren.size != rightChildren.size) { + Assert.fail("Elements have different amount of children. Left: ${leftChildren} != right: ${rightChildren}") + } + + for (i in leftChildren.indices) { + assertElementsEquals(leftChildren[i], rightChildren[i]) + } + } +} + diff --git a/java/compiler/tests/com/intellij/compiler/artifacts/propertybased/ArtifactsPropertyTest.kt b/java/compiler/tests/com/intellij/compiler/artifacts/propertybased/ArtifactsPropertyTest.kt new file mode 100644 index 000000000000..1634b8c0323f --- /dev/null +++ b/java/compiler/tests/com/intellij/compiler/artifacts/propertybased/ArtifactsPropertyTest.kt @@ -0,0 +1,1042 @@ +// Copyright 2000-2021 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.compiler.artifacts.propertybased + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.invokeAndWaitIfNeeded +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.packaging.artifacts.* +import com.intellij.packaging.elements.* +import com.intellij.packaging.impl.artifacts.PlainArtifactType +import com.intellij.packaging.impl.artifacts.workspacemodel.ArtifactBridge +import com.intellij.packaging.impl.artifacts.workspacemodel.ArtifactManagerBridge.Companion.artifactsMap +import com.intellij.packaging.impl.artifacts.workspacemodel.forThisAndFullTree +import com.intellij.packaging.impl.elements.ArtifactRootElementImpl +import com.intellij.packaging.impl.elements.DirectoryPackagingElement +import com.intellij.packaging.impl.elements.FileCopyPackagingElement +import com.intellij.packaging.ui.ArtifactEditorContext +import com.intellij.packaging.ui.PackagingElementPresentation +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.UsefulTestCase.assertNotEmpty +import com.intellij.testFramework.rules.ProjectModelRule +import com.intellij.util.ui.EmptyIcon +import com.intellij.workspaceModel.ide.WorkspaceModel +import com.intellij.workspaceModel.storage.EntitySource +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder +import com.intellij.workspaceModel.storage.bridgeEntities.* +import com.intellij.workspaceModel.storage.impl.VersionedEntityStorageImpl +import org.jetbrains.annotations.NotNull +import org.jetbrains.jetCheck.Generator +import org.jetbrains.jetCheck.ImperativeCommand +import org.jetbrains.jetCheck.PropertyChecker +import org.junit.Assert.* +import org.junit.Assume.assumeTrue +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import java.util.function.Supplier +import javax.swing.Icon + +class ArtifactsPropertyTest { + companion object { + @ClassRule + @JvmField + val application = ApplicationRule() + + private const val MAX_ARTIFACT_NUMBER = 50 + } + + @Rule + @JvmField + val projectModel = ProjectModelRule() + + @Rule + @JvmField + val disposableRule = DisposableRule() + + // This is a code generator for failed tests. + // At the moment it's incomplete and should be updated if some execution paths are missing + lateinit var codeMaker: CodeMaker + + @Test + fun `property test`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + val writeDisposable = writeActionDisposable(disposableRule.disposable) + invokeAndWaitIfNeeded { + PackagingElementType.EP_NAME.point.registerExtension(MyWorkspacePackagingElementType, writeDisposable) + PackagingElementType.EP_NAME.point.registerExtension(MyCompositeWorkspacePackagingElementType, writeDisposable) + customArtifactTypes.forEach { + ArtifactType.EP_NAME.point.registerExtension(it, writeDisposable) + } + } + + PropertyChecker.checkScenarios { + codeMaker = CodeMaker() + ImperativeCommand { + try { + it.executeCommands(Generator.sampledFrom( + RenameArtifact(), + AddArtifact(), + RemoveArtifact(), + ChangeBuildOnMake(), + ChangeArtifactType(), + + AddPackagingElementTree(), + GetPackagingElement(), + FindCompositeChild(), + RemoveAllChildren(), + + GetAllArtifacts(), + GetSortedArtifacts(), + GetAllArtifactsIncludingInvalid(), + + FindByNameExisting(), + FindByNameNonExisting(), + FindByType(), + + CreateViaWorkspaceModel(), + RenameViaWorkspaceModel(), + ChangeOnBuildViaWorkspaceModel(), + ChangeArtifactTypeViaWorkspaceModel(), + RemoveViaWorkspaceModel(), + )) + } + finally { + codeMaker.finish() + makeChecksHappy { + val artifacts = ArtifactManager.getInstance(projectModel.project).artifacts + val modifiableModel = ArtifactManager.getInstance(projectModel.project).createModifiableModel() + artifacts.forEach { + modifiableModel.removeArtifact(it) + } + modifiableModel.commit() + + WorkspaceModel.getInstance(projectModel.project).updateProjectModel { + it.replaceBySource({ true }, WorkspaceEntityStorageBuilder.create()) + } + } + + it.logMessage("------- Code -------") + it.logMessage(codeMaker.get()) + } + } + } + } + + inner class GetPackagingElement : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val artifactBridge = selectArtifactBridge(env, "get packaging element") ?: return + makeChecksHappy { + val modifiableModel = ArtifactManager.getInstance(projectModel.project).createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifactBridge) + val (parent, child) = chooseSomeElementFromTree(env, modifiableArtifact) + if (parent == null) { + modifiableModel.dispose() + return@makeChecksHappy + } + + val newChild = parent.addOrFindChild(child) + checkResult(env) { + assertSame(child, newChild) + } + modifiableModel.commit() + } + } + } + + inner class FindCompositeChild : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val artifactBridge = selectArtifactBridge(env, "get packaging element") ?: return + makeChecksHappy { + val modifiableModel = ArtifactManager.getInstance(projectModel.project).createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifactBridge) + val (parent, child) = chooseSomeElementFromTree(env, modifiableArtifact) + if (parent == null || child !is CompositePackagingElement<*>) { + modifiableModel.dispose() + return@makeChecksHappy + } + + val names = parent.children.filterIsInstance>().map { it.name } + if (names.size != names.toSet().size) { + modifiableModel.dispose() + return@makeChecksHappy + } + val newChild = parent.findCompositeChild(child.name) + checkResult(env) { + assertSame(child, newChild) + } + modifiableModel.commit() + } + } + } + + inner class RemoveAllChildren : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val artifactBridge = selectArtifactBridge(env, "get packaging element") ?: return + val (rootElement, removedChild, parent) = makeChecksHappy { + val modifiableModel = ArtifactManager.getInstance(projectModel.project).createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifactBridge) + + val modifiableModelVal = codeMaker.makeVal("modifiableModel", "ArtifactManager.getInstance(project).createModifiableModel()") + val modifiableArtifactVal = codeMaker.makeVal("modifiableArtifact", "${modifiableModelVal}.getOrCreateModifiableArtifact(${ + codeMaker.v("chosenArtifact") + })") + + val (parent, child) = chooseSomeElementFromTree(env, modifiableArtifact) + if (parent == null) { + modifiableModel.dispose() + return@makeChecksHappy null + } + + parent.removeAllChildren() + + env.logMessage("Removing some package element for ${artifactBridge.name}") + codeMaker.addLine("${codeMaker.v("chosenParent")}.removeAllChildren()") + + // It's important to get root element + // Otherwise diff will be injected into the root element + val rootElement = modifiableArtifact.rootElement + modifiableModel.commit() + val rootElementVal = codeMaker.makeVal("rootElement", "$modifiableArtifactVal.rootElement") + codeMaker.addLine("$modifiableModelVal.commit()") + codeMaker.addLine("return@runWriteAction Triple($rootElementVal, ${codeMaker.v("chosenChild")}, ${codeMaker.v("chosenParent")})") + Triple(rootElement, child, parent) + } ?: return + + checkResult(env) { + val manager = ArtifactManager.getInstance(projectModel.project) + val foundArtifact = manager.findArtifact(artifactBridge.name)!! + val managerVal = codeMaker.makeVal("manager", "ArtifactManager.getInstance(project)") + val foundArtifactVal = codeMaker.makeVal("foundArtifact", + "$managerVal.findArtifact(${codeMaker.v("chosenArtifact")}.name)!!") + + val artifactEntity = WorkspaceModel.getInstance(projectModel.project).entityStorage.current + .entities(ArtifactEntity::class.java).find { it.name == artifactBridge.name }!! + + assertElementsEquals(rootElement, foundArtifact.rootElement) + + assertTreesEquals(projectModel.project, foundArtifact.rootElement, artifactEntity.rootElement) + + codeMaker.scope("$foundArtifactVal.rootElement.forThisAndFullTree") { + codeMaker.scope("if (it === ${codeMaker.v("happyResult")}.third)") { + codeMaker.addLine("assertTrue(it.children.none { it.isEqualTo(${codeMaker.v("happyResult")}.second) })") + } + } + foundArtifact.rootElement.forThisAndFullTree { + if (it === parent) { + assertTrue(it.children.none { it.isEqualTo(removedChild) }) + } + } + } + } + } + + inner class AddPackagingElementTree : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val (selectedArtifact, manager) = selectArtifactViaBridge(env, "adding package element") ?: return + + env.logMessage("Add new packaging elements tree to: ${selectedArtifact.name}") + + val rootElement = makeChecksHappy { + val modifiableModel = manager.createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(selectedArtifact) + + val (parent, _) = chooseSomeElementFromTree(env, modifiableArtifact) + val newTree = makeElementsTree(env).first + + parent?.let { addChildSomehow(env, it, newTree) } + + // It's important to get root element + // Otherwise diff will be injected into the root element + val rootElement = modifiableArtifact.rootElement + modifiableModel.commit() + + rootElement + } + + checkResult(env) { + val foundArtifact = manager.findArtifact(selectedArtifact.name)!! + + val artifactEntity = WorkspaceModel.getInstance(projectModel.project).entityStorage.current + .entities(ArtifactEntity::class.java).find { it.name == selectedArtifact.name }!! + + assertElementsEquals(rootElement, foundArtifact.rootElement) + + assertTreesEquals(projectModel.project, foundArtifact.rootElement, artifactEntity.rootElement) + } + } + + private fun addChildSomehow(env: ImperativeCommand.Environment, + parent: CompositePackagingElement<*>, + newTree: PackagingElement<*>) { + when (env.generateValue(Generator.integers(0, 1), null)) { + 0 -> parent.addOrFindChild(newTree) + 1 -> parent.addFirstChild(newTree) + else -> error("Unexpected") + } + } + } + + inner class CreateViaWorkspaceModel : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val workspaceModel = WorkspaceModel.getInstance(projectModel.project) + val artifactName = selectArtifactName( + env, + workspaceModel.entityStorage + .current + .entities(ArtifactEntity::class.java) + .map { it.name } + .toList() + ) ?: run { + env.logMessage("Cannot select name for new artifact via workspace model") + return + } + + makeChecksHappy { + workspaceModel.updateProjectModel { + val rootElement = createCompositeElementEntity(env, it) + val (_, id, _) = selectArtifactType(env) + it.addArtifactEntity(artifactName, id, true, null, rootElement, TestEntitySource) + } + } + env.logMessage("Add artifact via model: $artifactName") + + checkResult(env) { + val foundArtifact = ArtifactManager.getInstance(projectModel.project).findArtifact(artifactName) + assertNotNull(foundArtifact) + } + } + } + + inner class RenameViaWorkspaceModel : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val workspaceModel = WorkspaceModel.getInstance(projectModel.project) + val artifactName = selectArtifactName( + env, + workspaceModel.entityStorage + .current + .entities(ArtifactEntity::class.java) + .map { it.name } + .toList() + ) ?: run { + env.logMessage("Cannot select name for new artifact via workspace model") + return + } + + val selectedArtifact = selectArtifactViaModel(env, workspaceModel, "renaming") ?: return + env.logMessage("Rename artifact via workspace model: ${selectedArtifact.name} -> $artifactName") + makeChecksHappy { + workspaceModel.updateProjectModel { + it.modifyEntity(ModifiableArtifactEntity::class.java, selectedArtifact) { + this.name = artifactName + } + } + } + + checkResult(env) { + val entities = workspaceModel.entityStorage.current.entities(ArtifactEntity::class.java) + assertTrue(entities.none { it.name == selectedArtifact.name }) + assertTrue(entities.any { it.name == artifactName }) + + onManager(env) { manager -> + val allArtifacts = manager.artifacts + assertTrue(allArtifacts.none { it.name == selectedArtifact.name }) + assertTrue(allArtifacts.any { it.name == artifactName }) + } + } + } + } + + inner class ChangeOnBuildViaWorkspaceModel : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val workspaceModel = WorkspaceModel.getInstance(projectModel.project) + + val selectedArtifact = selectArtifactViaModel(env, workspaceModel, "changing option") ?: return + env.logMessage("Change build on make option for ${selectedArtifact.name}: Prev value: ${selectedArtifact.includeInProjectBuild}") + makeChecksHappy { + workspaceModel.updateProjectModel { + it.modifyEntity(ModifiableArtifactEntity::class.java, selectedArtifact) { + this.includeInProjectBuild = !this.includeInProjectBuild + } + } + } + + checkResult(env) { + val artifactEntity = workspaceModel.entityStorage.current.resolve(selectedArtifact.persistentId())!! + assertEquals(!selectedArtifact.includeInProjectBuild, artifactEntity.includeInProjectBuild) + + onManager(env) { manager -> + val artifact = manager.findArtifact(selectedArtifact.name)!! + assertEquals(!selectedArtifact.includeInProjectBuild, artifact.isBuildOnMake) + } + } + } + } + + inner class ChangeArtifactTypeViaWorkspaceModel : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val workspaceModel = WorkspaceModel.getInstance(projectModel.project) + + val selectedArtifact = selectArtifactViaModel(env, workspaceModel, "changing artifact type") ?: return + val (_, id, _) = selectArtifactType(env) + env.logMessage("Change artifact type for ${selectedArtifact.name}: Prev value: ${selectedArtifact.artifactType}") + makeChecksHappy { + workspaceModel.updateProjectModel { + it.modifyEntity(ModifiableArtifactEntity::class.java, selectedArtifact) { + this.artifactType = id + } + } + } + + checkResult(env) { + val artifactEntity = artifactEntity(projectModel.project, selectedArtifact.name) + assertEquals(id, artifactEntity.artifactType) + + onManager(env) { manager -> + val artifact = manager.findArtifact(selectedArtifact.name)!! + assertEquals(id, artifact.artifactType.id) + } + } + } + } + + inner class RemoveViaWorkspaceModel : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val workspaceModel = WorkspaceModel.getInstance(projectModel.project) + + val selectedArtifact = selectArtifactViaModel(env, workspaceModel, "removing") ?: return + env.logMessage("Remove artifact: ${selectedArtifact.name}") + makeChecksHappy { + workspaceModel.updateProjectModel { + it.removeEntity(selectedArtifact) + } + } + + checkResult(env) { + val entities = workspaceModel.entityStorage.current.entities(ArtifactEntity::class.java) + assertTrue(entities.none { it.name == selectedArtifact.name }) + + onManager(env) { manager -> + val allArtifacts = manager.artifacts + assertTrue(allArtifacts.none { it.name == selectedArtifact.name }) + } + } + } + } + + inner class FindByType : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val (selectedArtifact, _) = selectArtifactViaBridge(env, "finding by type") ?: return + val searchType = selectedArtifact.artifactType.id + env.logMessage("Search for artifact by type: $searchType") + + onManager(env) { manager -> + assertNotEmpty(manager.getArtifactsByType(ArtifactType.findById(searchType)!!)) + } + } + } + + inner class FindByNameNonExisting : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val artifactEntities = WorkspaceModel.getInstance(projectModel.project).entityStorage.current.entities( + ArtifactEntity::class.java).toList() + + val artifactName = selectArtifactName(env, artifactEntities.map { it.name }) ?: run { + env.logMessage("Cannot select non-existing name for search") + return + } + env.logMessage("Search for artifact by name: $artifactName") + + onManager(env) { manager -> + assertNull(manager.findArtifact(artifactName)) + } + } + } + + inner class FindByNameExisting : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val artifactEntities = WorkspaceModel.getInstance(projectModel.project).entityStorage.current.entities( + ArtifactEntity::class.java).toList() + if (artifactEntities.isEmpty()) { + env.logMessage("Cannot select artifact for finding") + return + } + + val artifactName = env.generateValue(Generator.sampledFrom(artifactEntities), null).name + env.logMessage("Search for artifact by name: $artifactName") + + onManager(env) { manager -> + assertNotNull(manager.findArtifact(artifactName)) + } + } + } + + inner class GetAllArtifactsIncludingInvalid : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val manager = ArtifactManager.getInstance(projectModel.project) + env.logMessage("Get all artifacts including invalid") + val artifacts = manager.allArtifactsIncludingInvalid + artifacts.forEach { _ -> + // Nothing + } + } + } + + inner class GetAllArtifacts : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val manager = ArtifactManager.getInstance(projectModel.project) + env.logMessage("Get all artifacts") + val artifacts = manager.artifacts + artifacts.forEach { _ -> + // Nothing + } + } + } + + inner class GetSortedArtifacts : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val manager = ArtifactManager.getInstance(projectModel.project) + env.logMessage("Get all artifacts sorted") + val artifacts = manager.sortedArtifacts + artifacts.forEach { _ -> + // Nothing + } + } + } + + inner class RemoveArtifact : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val (artifactForRemoval, _) = selectArtifactViaBridge(env, "removing") ?: return + + val manager = ArtifactManager.getInstance(projectModel.project) + val initialArtifactsSize = manager.artifacts.size + + val removalName = artifactForRemoval.name + invokeAndWaitIfNeeded { + runWriteAction { + val modifiableModel = manager.createModifiableModel() + modifiableModel.removeArtifact(artifactForRemoval) + modifiableModel.commit() + } + } + + checkResult(env) { + val newArtifactsList = manager.artifacts + assertEquals(initialArtifactsSize - 1, newArtifactsList.size) + assertTrue(newArtifactsList.none { it.name == removalName }) + + val artifactEntities = WorkspaceModel.getInstance(projectModel.project).entityStorage.current.entities(ArtifactEntity::class.java) + assertTrue(artifactEntities.none { it.name == removalName }) + } + } + } + + inner class AddArtifact : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val artifactName = selectArtifactName(env) + val createRootElement = env.generateValue(Generator.sampledFrom(false, true, true, true, true, true), null) + val (rootElement, rootVal) = if (createRootElement) createCompositeElement(env) else null to null + val newArtifact = makeChecksHappy { + val (instance, _, typeVal) = selectArtifactType(env) + codeMaker.addLine("ArtifactManager.getInstance(project).addArtifact(\"$artifactName\", $typeVal, $rootVal)") + ArtifactManager.getInstance(projectModel.project).addArtifact(artifactName, instance, rootElement) + } + val newArtifactName = newArtifact.name + env.logMessage("Add new artifact via bridge: $newArtifactName. Final name: $newArtifactName") + + checkResult(env) { + val bridgeVal = codeMaker.makeVal("bridgeArtifact", "artifact(project, \"$newArtifactName\")") + val bridgeArtifact = artifact(projectModel.project, newArtifactName) + + val artifactEntityVal = codeMaker.makeVal("artifactEntity", "artifactEntity(project, \"$newArtifactName\")") + val artifactEntity = artifactEntity(projectModel.project, newArtifactName) + + codeMaker.addLine("assertTreesEquals(project, $bridgeVal.rootElement, $artifactEntityVal.rootElement)") + assertTreesEquals(projectModel.project, bridgeArtifact.rootElement, artifactEntity.rootElement) + } + } + } + + inner class RenameArtifact : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val manager = ArtifactManager.getInstance(projectModel.project) + val artifacts = manager.artifacts + if (artifacts.isEmpty()) return + + val index = env.generateValue(Generator.integers(0, artifacts.lastIndex), null) + val artifact = artifacts[index] + val newName = selectArtifactName(env, artifacts.map { it.name }) + val oldName = artifact.name + env.logMessage("Rename artifact: $oldName -> $newName") + makeChecksHappy { + val modifiableModel = manager.createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + modifiableArtifact.name = newName + modifiableModel.commit() + } + + checkResult(env) { + assertEquals(newName, manager.artifacts[index].name) + + val artifactEntities = WorkspaceModel.getInstance(projectModel.project).entityStorage.current.entities(ArtifactEntity::class.java) + assertTrue(artifactEntities.any { it.name == newName }) + assertTrue(artifactEntities.none { it.name == oldName }) + } + } + } + + inner class ChangeBuildOnMake : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val manager = ArtifactManager.getInstance(projectModel.project) + val artifacts = manager.artifacts + if (artifacts.isEmpty()) return + + val index = env.generateValue(Generator.integers(0, artifacts.lastIndex), null) + val artifact = artifacts[index] + val oldBuildOnMake = artifact.isBuildOnMake + env.logMessage("Change isBuildOnMake for ${artifact.name}. New value: ${oldBuildOnMake}") + makeChecksHappy { + val modifiableModel = manager.createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + modifiableArtifact.isBuildOnMake = !modifiableArtifact.isBuildOnMake + modifiableModel.commit() + } + + checkResult(env) { + assertEquals(!oldBuildOnMake, manager.artifacts[index].isBuildOnMake) + + val artifactEntities = WorkspaceModel.getInstance(projectModel.project).entityStorage.current.entities(ArtifactEntity::class.java) + assertTrue(artifactEntities.single { it.name == artifact.name }.includeInProjectBuild == !oldBuildOnMake) + } + } + } + + inner class ChangeArtifactType : ImperativeCommand { + override fun performCommand(env: ImperativeCommand.Environment) { + val artifact = selectArtifactBridge(env, "change artifact type") ?: return + val artifactVal = codeMaker.v("chosenArtifact") + val (newArtifactType, id, typeVal) = selectArtifactType(env) + env.logMessage("Change artifact type for ${artifact.name}. New value: ${newArtifactType}") + makeChecksHappy { + modifyArtifact(artifact, artifactVal) { + codeMaker.addLine("$it.artifactType = $typeVal") + artifactType = newArtifactType + } + } + + checkResult(env) { + assertEquals(newArtifactType, artifact(projectModel.project, artifact.name).artifactType) + + val artifactEntityVal = codeMaker.makeVal("artifactEntity", "artifactEntity(project, \"${artifact.name}\")") + codeMaker.addLine("assertTrue($artifactEntityVal.artifactType == \"$id\")") + val artifactEntity = artifactEntity(projectModel.project, artifact.name) + assertTrue(artifactEntity.artifactType == id) + } + } + } + + private fun modifyArtifact(artifact: Artifact, artifactVal: String, modification: ModifiableArtifact.(String) -> Unit) { + val modifiableModel = ArtifactManager.getInstance(projectModel.project).createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + + val modifiableModelVal = codeMaker.makeVal("modifiableModel", "ArtifactManager.getInstance(project).createModifiableModel()") + val modifiableArtifactVal = codeMaker.makeVal("modifiableArtifact", "$modifiableModelVal.getOrCreateModifiableArtifact($artifactVal)") + + modifiableArtifact.modification(modifiableArtifactVal) + + codeMaker.addLine("$modifiableModelVal.commit()") + + modifiableModel.commit() + } + + private fun selectArtifactName(env: ImperativeCommand.Environment): String { + return "Artifact-${env.generateValue(Generator.integers(0, MAX_ARTIFACT_NUMBER), null)}" + } + + private fun selectArtifactName(env: ImperativeCommand.Environment, notLike: List): String? { + var counter = 50 + while (counter > 0) { + val name = "Artifact-${env.generateValue(Generator.integers(0, MAX_ARTIFACT_NUMBER), null)}" + if (name !in notLike) return name + counter-- + } + return null + } + + private fun createCompositeElement(env: ImperativeCommand.Environment): Pair, String> { + val root = ArtifactRootElementImpl() + val rootVal = codeMaker.makeVal("artifactRoot", "ArtifactRootElementImpl()") + val value = env.generateValue(Generator.booleans(), null) + if (value) { + root.addFirstChild(makeElementsTree(env).first) + codeMaker.addLine("$rootVal.addFirstChild(${codeMaker.v("element_0")})") + } + return root to rootVal + } + + private fun makeElementsTree(env: ImperativeCommand.Environment, depth: Int = 0): Pair, String> { + val value = env.generateValue(Generator.integers(0, 3), null) + val indent = " ".repeat(depth) + val (element, elementName) = when (value) { + 0 -> { + val directoryName = env.generateValue(Generator.sampledFrom(names), null) + val element = DirectoryPackagingElement(directoryName) + val currentElementName = codeMaker.makeVal("element_$depth", "DirectoryPackagingElement(\"$directoryName\")") + env.logMessage("${indent}Generate DirectoryPackagingElement: $directoryName") + element to currentElementName + } + 1 -> { + val outputName = env.generateValue(Generator.sampledFrom(names), null) + val pathName = "/" + env.generateValue(Generator.sampledFrom(names), null) + env.logMessage("${indent}Generate FileCopyPackagingElement ($pathName -> $outputName)") + val currentElementName = codeMaker.makeVal("element_$depth", "FileCopyPackagingElement(\"$pathName\", \"$outputName\")") + FileCopyPackagingElement(pathName, outputName) to currentElementName + } + 2 -> { + val data = env.generateValue(Generator.sampledFrom(names), null) + env.logMessage("${indent}Generate MyWorkspacePackagingElement ($data)") + val currentElementName = codeMaker.makeVal("element_$depth", "MyWorkspacePackagingElement(\"$data\")") + MyWorkspacePackagingElement(data) to currentElementName + } + 3 -> { + val data = env.generateValue(Generator.sampledFrom(names), null) + val name = env.generateValue(Generator.sampledFrom(names), null) + env.logMessage("${indent}Generate MyCompositeWorkspacePackagingElement ($data, $name)") + val currentElementName = codeMaker.makeVal("element_$depth", "MyCompositeWorkspacePackagingElement(\"$data\", \"$name\")") + MyCompositeWorkspacePackagingElement(data, name) to currentElementName + } + else -> error("Unexpected branch") + } + + if (element is CompositePackagingElement<*>) { + if (depth < 5) { + // This is all magic numbers. Just trying to make less children for deeper layers of package elements tree + val maxChildren = when { + depth == 0 -> 5 + depth in 1..2 -> 3 + depth in 3..4 -> 2 + depth > 4 -> 1 + else -> 0 + } + val amountOfChildren = env.generateValue(Generator.integers(0, maxChildren), null) + env.logMessage("${indent}- Generate $amountOfChildren children:") + for (i in 0 until amountOfChildren) { + val (child, name) = makeElementsTree(env, depth + 1) + element.addFirstChild(child) + codeMaker.addLine("$elementName.addFirstChild($name)") + } + } + } + + return element to elementName + } + + private fun createCompositeElementEntity(env: ImperativeCommand.Environment, + builder: WorkspaceEntityStorageBuilder): CompositePackagingElementEntity { + return builder.addArtifactRootElementEntity(emptyList(), TestEntitySource) + } + + private fun chooseSomeElementFromTree(env: ImperativeCommand.Environment, + artifact: Artifact): Pair?, PackagingElement<*>> { + val root = artifact.rootElement + val allElements: MutableList?, PackagingElement<*>>> = mutableListOf(null to root) + flatElements(root, allElements) + val (parent, child) = env.generateValue(Generator.sampledFrom(allElements), null) + + val artifactVal = codeMaker.vOrNull("modifiableArtifact") ?: codeMaker.v("chosenArtifact") + var rootElementVal = codeMaker.makeVal("rootElement", "$artifactVal.rootElement") + if (root != child) { + val address = generateAddress(root, child)!! + var childElementVal = "($rootElementVal as CompositePackagingElement<*>).children[${address[0]}]" + address.drop(1).forEach { + rootElementVal = childElementVal + childElementVal = "($rootElementVal as CompositePackagingElement<*>).children[$it]" + } + codeMaker.makeVal("chosenParent", "$rootElementVal as CompositePackagingElement<*>") + codeMaker.makeVal("chosenChild", childElementVal) + } + else { + codeMaker.makeVal("chosenParent", null) + codeMaker.makeVal("chosenChild", rootElementVal) + } + + return parent to child + } + + private fun generateAddress(root: CompositePackagingElement<*>, child: PackagingElement<*>): List? { + root.children.forEachIndexed { index, packagingElement -> + val indexList = listOf(index) + if (packagingElement === child) return indexList + if (packagingElement is CompositePackagingElement<*>) { + val result = generateAddress(packagingElement, child) + if (result != null) { + return indexList + result + } + } + } + return null + } + + private fun generateAddress(root: CompositePackagingElementEntity, child: PackagingElementEntity): List? { + root.children.forEachIndexed { index, packagingElement -> + if (packagingElement == child) return listOf(index) + if (packagingElement is CompositePackagingElementEntity) { + val result = generateAddress(packagingElement, child) + if (result != null) { + return result + } + } + } + return null + } + + private fun selectArtifactType(env: ImperativeCommand.Environment): Triple { + val selector = env.generateValue(Generator.integers(0, allArtifactTypes.lastIndex), null) + val artifactVal = codeMaker.makeVal("artifactType", "allArtifactTypes[$selector]") + val instance = allArtifactTypes[selector] + return Triple(instance, instance.id, artifactVal) + } + + private fun flatElements(currentElement: CompositePackagingElement<*>, + result: MutableList?, PackagingElement<*>>>) { + currentElement.children.forEach { + result.add(currentElement to it) + if (it is CompositePackagingElement<*>) { + flatElements(it, result) + } + } + } + + private fun selectArtifactViaBridge(env: ImperativeCommand.Environment, reason: String): Pair? { + val manager = ArtifactManager.getInstance(projectModel.project) + val artifacts = manager.artifacts + if (artifacts.isEmpty()) { + env.logMessage("Cannot select artifact for $reason") + return null + } + + val selectedArtifact = env.generateValue(Generator.sampledFrom(*artifacts), null) + val artifactIndex = artifacts.indexOf(selectedArtifact) + val managerVal = codeMaker.makeVal("manager", "ArtifactManager.getInstance(project)") + val artifactsVal = codeMaker.makeVal("artifacts", "$managerVal.artifacts") + codeMaker.makeVal("chosenArtifact", "$artifactsVal[$artifactIndex]") + + return selectedArtifact to manager + } + + fun selectArtifactViaModel(env: ImperativeCommand.Environment, workspaceModel: WorkspaceModel, reason: String): ArtifactEntity? { + val existingArtifacts = workspaceModel.entityStorage.current.entities(ArtifactEntity::class.java).toList() + if (existingArtifacts.isEmpty()) { + env.logMessage("Cannot select artifact for $reason") + return null + } + val selectedArtifact = env.generateValue(Generator.sampledFrom(existingArtifacts), null) + + val artifactEntityId = existingArtifacts.indexOf(selectedArtifact) + + codeMaker.makeVal("chosenArtifactEntity", "WorkspaceModel.getInstance(project).entityStorage.current.entities(ArtifactEntity::class.java).toList()[$artifactEntityId]") + + return selectedArtifact + } + + private fun selectArtifactBridge(env: ImperativeCommand.Environment, reason: String): Artifact? { + val viaBridge = env.generateValue(Generator.booleans(), null) + return if (viaBridge) { + selectArtifactViaBridge(env, reason)?.first + } + else { + val workspaceModel = WorkspaceModel.getInstance(projectModel.project) + val entity = selectArtifactViaModel(env, workspaceModel, reason) ?: return null + + codeMaker.makeVal("chosenArtifact", "WorkspaceModel.getInstance(project).entityStorage.current.artifactsMap.getDataByEntity(${codeMaker.v("chosenArtifactEntity")})") + + workspaceModel.entityStorage.current.artifactsMap.getDataByEntity(entity) + } + } + + private inline fun checkResult(env: ImperativeCommand.Environment, action: () -> Unit) { + val checkResult = env.generateValue(Generator.booleans(), null) + env.logMessage("Check result: $checkResult") + if (checkResult) { + action() + } + + assertArtifactsHaveStableStore() + } + + private inline fun onManager(env: ImperativeCommand.Environment, action: (ArtifactModel) -> Unit) { + val onModifiableModel = env.generateValue(Generator.booleans(), null) + + val manager = if (onModifiableModel) { + ArtifactManager.getInstance(projectModel.project).createModifiableModel() + } + else { + ArtifactManager.getInstance(projectModel.project) + } + + action(manager) + + if (onModifiableModel) (manager as ModifiableArtifactModel).dispose() + } + + private fun assertArtifactsHaveStableStore() { + val manager = ArtifactManager.getInstance(projectModel.project) + manager.artifacts.forEach { + assertTrue((it as ArtifactBridge).entityStorage is VersionedEntityStorageImpl) + } + } + + private val names = listOf( + "Name-1", + "Name-2", + "Name-3", + "Name-4", + "Name-5", + "Name-6", + "Name-7", + "Name-8", + "Name-9", + "Name-10", + "Name-11", + "Name-12", + "Name-13", + "Name-14", + "Name-15", + "Name-16", + "Name-17", + "Name-18", + "Name-19", + "Name-20", + ) + + private fun makeChecksHappy(action: () -> T): T { + return invokeAndWaitIfNeeded { + runWriteAction { + codeMaker.startScope("happyResult", "invokeAndWaitIfNeeded") + codeMaker.startScope("runWriteAction") + try { + return@runWriteAction action() + } + finally { + if (codeMaker.last()?.trimIndent()?.startsWith("return") != true) { + codeMaker.addLine("return@runWriteAction null") + } + codeMaker.finishScope() + codeMaker.finishScope() + } + } + } + } + + private fun writeActionDisposable(parent: Disposable): Disposable { + val writeDisposable = Disposer.newDisposable() + Disposer.register(parent) { + invokeAndWaitIfNeeded { + runWriteAction { + Disposer.dispose(writeDisposable) + } + } + } + return writeDisposable + } +} + +object TestEntitySource : EntitySource + +class MyWorkspacePackagingElement(data: String) : PackagingElement(PackagingElementType.EP_NAME.findExtensionOrFail(MyWorkspacePackagingElementType::class.java)) { + + constructor(): this("") + + private val state: MyWorkspacePackagingElementState = MyWorkspacePackagingElementState(data) + + override fun getState(): MyWorkspacePackagingElementState = state + + override fun loadState(state: MyWorkspacePackagingElementState) { + this.state.data = state.data + } + + override fun isEqualTo(element: PackagingElement<*>): Boolean = (element as? MyWorkspacePackagingElement)?.state?.data == state.data + + override fun createPresentation(context: ArtifactEditorContext): PackagingElementPresentation { + throw UnsupportedOperationException() + } +} + +class MyWorkspacePackagingElementState(var data: String = "") + +object MyWorkspacePackagingElementType : PackagingElementType("Custom-element", Supplier { "Custom Element" }) { + override fun canCreate(context: ArtifactEditorContext, artifact: Artifact): Boolean = true + + override fun chooseAndCreate(context: ArtifactEditorContext, + artifact: Artifact, + parent: CompositePackagingElement<*>): MutableList> { + throw UnsupportedOperationException() + } + + override fun createEmpty(project: Project): MyWorkspacePackagingElement { + return MyWorkspacePackagingElement() + } +} + +class MyCompositeWorkspacePackagingElement(data: String, name: String) : CompositePackagingElement(PackagingElementType.EP_NAME.findExtensionOrFail(MyCompositeWorkspacePackagingElementType::class.java)) { + + constructor(): this("", "") + + private val state: MyCompositeWorkspacePackagingElementState = MyCompositeWorkspacePackagingElementState(data, name) + + override fun getState(): MyCompositeWorkspacePackagingElementState = state + + override fun loadState(state: MyCompositeWorkspacePackagingElementState) { + this.state.data = state.data + } + + override fun isEqualTo(element: PackagingElement<*>): Boolean = (element as? MyCompositeWorkspacePackagingElement)?.state?.data == state.data + + override fun getName(): String = state.name + + override fun rename(newName: String) { + state.name = newName + } + + override fun createPresentation(context: ArtifactEditorContext): PackagingElementPresentation { + throw UnsupportedOperationException() + } + + override fun toString(): String { + return "MyCompositeWorkspacePackagingElement(state=$state)" + } +} + +data class MyCompositeWorkspacePackagingElementState(var data: String = "", var name: String = "") + +object MyCompositeWorkspacePackagingElementType : PackagingElementType("Composite-custom-element", Supplier { "Composite Custom Element" }) { + override fun canCreate(context: ArtifactEditorContext, artifact: Artifact): Boolean = true + + override fun chooseAndCreate(context: ArtifactEditorContext, + artifact: Artifact, + parent: CompositePackagingElement<*>): MutableList> { + throw UnsupportedOperationException() + } + + override fun createEmpty(project: Project): MyCompositeWorkspacePackagingElement { + return MyCompositeWorkspacePackagingElement() + } +} + +internal val customArtifactTypes: List = List(10) { + object : ArtifactType("myArtifactType-$it", Supplier{ "myArtifactType-$it" }) { + + override fun getIcon(): Icon = EmptyIcon.ICON_16 + + override fun getDefaultPathFor(kind: PackagingElementOutputKind): String = "" + + override fun createRootElement(artifactName: String): CompositePackagingElement<*> { + return PackagingElementFactory.getInstance().createArtifactRootElement() + } + } +} + +internal val allArtifactTypes = customArtifactTypes + PlainArtifactType.getInstance() diff --git a/java/compiler/tests/com/intellij/compiler/artifacts/propertybased/CodeMaker.kt b/java/compiler/tests/com/intellij/compiler/artifacts/propertybased/CodeMaker.kt new file mode 100644 index 000000000000..b526409538dd --- /dev/null +++ b/java/compiler/tests/com/intellij/compiler/artifacts/propertybased/CodeMaker.kt @@ -0,0 +1,84 @@ +// Copyright 2000-2021 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.compiler.artifacts.propertybased + +/** + * This is an experimental thing to generate source code for failed property test. + */ +class CodeMaker { + private val code = ArrayList() + private val variables: MutableMap = HashMap() + private var indent = 0 + private var collectCode = true + + fun finish() { + while (indent > 0) { + finishScope() + } + collectCode = false + } + + fun scope(scopeName: String, action: () -> Unit) { + startScope(scopeName) + try { + action() + } + finally { + finishScope() + } + } + + fun startScope(scopeName: String) { + addLine("$scopeName {") + indent += 1 + } + + fun startScope(withVar: String, scopeName: String): String { + val varName = uniqueVarName(withVar) + addLine("val $varName = $scopeName {") + indent += 1 + return varName + } + + fun last(): String? = code.lastOrNull() + + fun finishScope() { + indent -= 1 + addLine("}") + } + + // last created variable by base name + fun v(base: String): String { + val counter = variables.getValue(base) + return makeVarName(base, counter) + } + + // last created variable by base name + fun vOrNull(base: String): String? { + val counter = variables[base] ?: return null + return makeVarName(base, counter) + } + + fun addLine(line: String) { + if (!collectCode) return + code.add(" ".repeat(indent) + line) + } + + fun makeVal(baseName: String, value: String?): String { + val varName = uniqueVarName(baseName) + addLine("val $varName = $value") + return varName + } + + private fun uniqueVarName(baseName: String): String { + val counter = variables.getOrPut(baseName) { 0 } + 1 + variables[baseName] = counter + val varName = makeVarName(baseName, counter) + return varName + } + + private fun makeVarName(baseName: String, counter: Int) = if (counter == 1) baseName else baseName + "_" + counter + + fun get(): String { + return code.joinToString("\n") + } +} \ No newline at end of file diff --git a/java/compiler/tests/com/intellij/compiler/artifacts/workspaceModel/ArtifactTest.kt b/java/compiler/tests/com/intellij/compiler/artifacts/workspaceModel/ArtifactTest.kt new file mode 100644 index 000000000000..16fe1544f3ee --- /dev/null +++ b/java/compiler/tests/com/intellij/compiler/artifacts/workspaceModel/ArtifactTest.kt @@ -0,0 +1,397 @@ +// Copyright 2000-2021 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.compiler.artifacts.workspaceModel + +import com.intellij.compiler.artifacts.ArtifactsTestCase +import com.intellij.compiler.artifacts.propertybased.* +import com.intellij.concurrency.JobSchedulerImpl +import com.intellij.openapi.application.invokeAndWaitIfNeeded +import com.intellij.openapi.application.runWriteAction +import com.intellij.packaging.artifacts.ArtifactManager +import com.intellij.packaging.elements.CompositePackagingElement +import com.intellij.packaging.elements.PackagingElementType +import com.intellij.packaging.impl.artifacts.InvalidArtifact +import com.intellij.packaging.impl.artifacts.PlainArtifactType +import com.intellij.packaging.impl.artifacts.workspacemodel.ArtifactManagerBridge.Companion.artifactsMap +import com.intellij.packaging.impl.artifacts.workspacemodel.forThisAndFullTree +import com.intellij.packaging.impl.elements.ArtifactRootElementImpl +import com.intellij.packaging.impl.elements.DirectoryPackagingElement +import com.intellij.packaging.impl.elements.FileCopyPackagingElement +import com.intellij.util.ConcurrencyUtil +import com.intellij.util.concurrency.AppExecutorUtil +import com.intellij.workspaceModel.ide.WorkspaceModel +import com.intellij.workspaceModel.storage.EntitySource +import com.intellij.workspaceModel.storage.bridgeEntities.* +import junit.framework.TestCase +import org.junit.Assume.assumeTrue +import java.util.concurrent.Callable + +class ArtifactTest : ArtifactsTestCase() { + + override fun isRunInWriteAction(): Boolean = true + + fun `test rename artifact via model`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + addArtifact("art") + + val anotherName = "anotherName" + + WorkspaceModel.getInstance(project).updateProjectModel { + val artifactEntity = it.entities(ArtifactEntity::class.java).single() + it.modifyEntity(ModifiableArtifactEntity::class.java, artifactEntity) { + name = anotherName + } + } + + val artifactObject = artifactManager.artifacts.single() + TestCase.assertEquals(anotherName, artifactObject.name) + } + + fun `test add artifact mix bridge and model`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + val workspaceModel = WorkspaceModel.getInstance(project) + + // Add via model + workspaceModel.updateProjectModel { + val root = it.addArtifactRootElementEntity(emptyList(), MySource) + it.addArtifactEntity("MyName", PlainArtifactType.ID, true, null, root, MySource) + } + + // Add via bridge + addArtifact("AnotherName") + + rename(artifactManager.findArtifact("MyName"), "NameThree") + + val artifacts = artifactManager.artifacts + TestCase.assertEquals(2, artifacts.size) + TestCase.assertTrue(artifacts.any { it.name == "AnotherName" }) + TestCase.assertTrue(artifacts.any { it.name == "NameThree" }) + } + + fun `test add artifact mix bridge and model rename via model`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + val workspaceModel = WorkspaceModel.getInstance(project) + + // Add via model + workspaceModel.updateProjectModel { + val root = it.addArtifactRootElementEntity(emptyList(), MySource) + it.addArtifactEntity("MyName", PlainArtifactType.ID, true, null, root, MySource) + } + + // Add via bridge + addArtifact("AnotherName") + + workspaceModel.updateProjectModel { + val artifactEntity = it.resolve(ArtifactId("MyName"))!! + it.modifyEntity(ModifiableArtifactEntity::class.java, artifactEntity) { + name = "NameThree" + } + } + + val artifacts = artifactManager.artifacts + TestCase.assertEquals(2, artifacts.size) + TestCase.assertTrue(artifacts.any { it.name == "AnotherName" }) + TestCase.assertTrue(artifacts.any { it.name == "NameThree" }) + } + + fun `test add artifact mix bridge and model rename via model same name`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + val workspaceModel = WorkspaceModel.getInstance(project) + + // Add via model + workspaceModel.updateProjectModel { + val root = it.addArtifactRootElementEntity(emptyList(), MySource) + it.addArtifactEntity("MyName", PlainArtifactType.ID, true, null, root, MySource) + } + + // Add via bridge + addArtifact("MyName") + + workspaceModel.updateProjectModel { + val artifactEntity = it.resolve(ArtifactId("MyName"))!! + it.modifyEntity(ModifiableArtifactEntity::class.java, artifactEntity) { + name = "NameThree" + } + } + + val artifacts = artifactManager.artifacts + TestCase.assertEquals(2, artifacts.size) + TestCase.assertTrue(artifacts.any { it.name == "MyName2" }) + TestCase.assertTrue(artifacts.any { it.name == "NameThree" }) + } + + fun `test dispose modifiable model`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + val workspaceModel = WorkspaceModel.getInstance(project) + + // Add via model + workspaceModel.updateProjectModel { + val root = it.addArtifactRootElementEntity(emptyList(), MySource) + it.addArtifactEntity("MyName", PlainArtifactType.ID, true, null, root, MySource) + } + + val manager = ArtifactManager.getInstance(project) + val artifact = manager.artifacts.single() + val modifiableModel = manager.createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(artifact) + + // Just call this method. It initializes some internal structures + modifiableArtifact.rootElement + + modifiableModel.dispose() + } + + fun `test dir with same name`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + val element_0 = DirectoryPackagingElement("Name-15") + val element_1 = DirectoryPackagingElement("Name-15") + element_0.addFirstChild(element_1) + invokeAndWaitIfNeeded { + runWriteAction { + ArtifactManager.getInstance(project).addArtifact("Artifact-0", PlainArtifactType.getInstance(), element_0) + } + } + val chosenArtifactEntity = WorkspaceModel.getInstance(project).entityStorage.current.entities(ArtifactEntity::class.java).toList()[0] + val chosenArtifact = WorkspaceModel.getInstance(project).entityStorage.current.artifactsMap.getDataByEntity(chosenArtifactEntity)!! + val happyResult_2 = invokeAndWaitIfNeeded { + runWriteAction { + val modifiableModel = ArtifactManager.getInstance(project).createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(chosenArtifact) + val rootElement = modifiableArtifact.rootElement + val chosenChild = rootElement.children[0] + rootElement.removeAllChildren() + val rootElement_2 = modifiableArtifact.rootElement + modifiableModel.commit() + return@runWriteAction Triple(rootElement_2, chosenChild, rootElement) + } + } + val manager = ArtifactManager.getInstance(project) + val foundArtifact = manager.findArtifact(chosenArtifact.name)!! + foundArtifact.rootElement.forThisAndFullTree { + if (it.isEqualTo(happyResult_2.third)) { + assertTrue((it as CompositePackagingElement<*>).children.none { it.isEqualTo(happyResult_2.second) }) + } + } + } + + fun `test another remove`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + val element_0 = DirectoryPackagingElement("Name-19") + val element_1 = DirectoryPackagingElement("Name-11") + val element_2 = FileCopyPackagingElement("/Name-20", "Name-7") + element_1.addFirstChild(element_2) + element_0.addFirstChild(element_1) + val element_1_2 = DirectoryPackagingElement("Name-1") + val element_2_2 = DirectoryPackagingElement("Name-18") + element_1_2.addFirstChild(element_2_2) + element_0.addFirstChild(element_1_2) + val element_1_3 = DirectoryPackagingElement("Name-7") + val element_2_3 = DirectoryPackagingElement("Name-12") + val element_3 = DirectoryPackagingElement("Name-18") + val element_4 = DirectoryPackagingElement("Name-9") + element_3.addFirstChild(element_4) + element_2_3.addFirstChild(element_3) + element_1_3.addFirstChild(element_2_3) + element_0.addFirstChild(element_1_3) + val element_1_4 = DirectoryPackagingElement("Name-19") + val element_2_4 = FileCopyPackagingElement("/Name-8", "Name-13") + element_1_4.addFirstChild(element_2_4) + val element_2_5 = DirectoryPackagingElement("Name-9") + val element_3_2 = DirectoryPackagingElement("Name-20") + val element_4_2 = DirectoryPackagingElement("Name-19") + element_3_2.addFirstChild(element_4_2) + element_2_5.addFirstChild(element_3_2) + val element_3_3 = FileCopyPackagingElement("/Name-18", "Name-18") + element_2_5.addFirstChild(element_3_3) + element_1_4.addFirstChild(element_2_5) + val element_2_6 = DirectoryPackagingElement("Name-18") + val element_3_4 = DirectoryPackagingElement("Name-9") + element_2_6.addFirstChild(element_3_4) + element_1_4.addFirstChild(element_2_6) + element_0.addFirstChild(element_1_4) + val happyResult = invokeAndWaitIfNeeded { + runWriteAction { + ArtifactManager.getInstance(project).addArtifact("Artifact-0", PlainArtifactType.getInstance(), element_0) + return@runWriteAction null + } + } + val chosenArtifactEntity = WorkspaceModel.getInstance(project).entityStorage.current.entities(ArtifactEntity::class.java).toList()[0] + val chosenArtifact = WorkspaceModel.getInstance(project).entityStorage.current.artifactsMap.getDataByEntity(chosenArtifactEntity)!! + val happyResult_2 = invokeAndWaitIfNeeded { + runWriteAction { + val modifiableModel = ArtifactManager.getInstance(project).createModifiableModel() + val modifiableArtifact = modifiableModel.getOrCreateModifiableArtifact(chosenArtifact) + val rootElement = modifiableArtifact.rootElement + val chosenParent = (rootElement.children[0] as CompositePackagingElement<*>).children[0] as CompositePackagingElement<*> + val chosenChild = ((rootElement.children[0] as CompositePackagingElement<*>).children[0] as CompositePackagingElement<*>).children[0] + chosenParent.removeAllChildren() + val rootElement_2 = modifiableArtifact.rootElement + modifiableModel.commit() + return@runWriteAction Triple(rootElement_2, chosenChild, chosenParent) + } + } + val manager = ArtifactManager.getInstance(project) + val foundArtifact = manager.findArtifact(chosenArtifact.name)!! + foundArtifact.rootElement.forThisAndFullTree { + if (it === happyResult_2.third) { + assertTrue(it.children.none { it.isEqualTo(happyResult_2.second) }) + } + } + } + + fun `test custom element`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + PackagingElementType.EP_NAME.point.registerExtension(MyWorkspacePackagingElementType, this.testRootDisposable) + + val workspaceModel = WorkspaceModel.getInstance(project) + workspaceModel.updateProjectModel { + val customElement = it.addCustomPackagingElementEntity("Custom-element", "\n" + + " ", emptyList(), MySource) + val rootElement = it.addArtifactRootElementEntity(listOf(customElement), MySource) + it.addArtifactEntity("MyArtifact", PlainArtifactType.ID, false, null, rootElement, MySource) + } + + val newArtifact = ArtifactManager.getInstance(project).artifacts.single() + val packagingElement = newArtifact.rootElement.children.single() as MyWorkspacePackagingElement + TestCase.assertEquals("Name-2", packagingElement.state.data) + } + + fun `test unknown custom element`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + val workspaceModel = WorkspaceModel.getInstance(project) + workspaceModel.updateProjectModel { + val customElement = it.addCustomPackagingElementEntity("Custom-element", "\n" + + " ", emptyList(), MySource) + val rootElement = it.addArtifactRootElementEntity(listOf(customElement), MySource) + it.addArtifactEntity("MyArtifact", PlainArtifactType.ID, false, null, rootElement, MySource) + } + + val newArtifact = ArtifactManager.getInstance(project).allArtifactsIncludingInvalid.single() + assertTrue(newArtifact is InvalidArtifact) + } + + fun `test add root via model and get via bridge`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + val workspaceModel = WorkspaceModel.getInstance(project) + workspaceModel.updateProjectModel { + val rootElement = it.addArtifactRootElementEntity(listOf(), MySource) + it.addArtifactEntity("MyArtifact", PlainArtifactType.ID, false, null, rootElement, MySource) + } + + val modifiableModel = ArtifactManager.getInstance(project).createModifiableModel() + val modifiableArtifact = modifiableModel.artifacts.single() + val element = modifiableArtifact.rootElement + TestCase.assertEquals(0, element.children.size) + modifiableModel.commit() + } + + fun `test custom composite package element`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + PackagingElementType.EP_NAME.point.registerExtension(MyCompositeWorkspacePackagingElementType, this.testRootDisposable) + + val artifactRoot = ArtifactRootElementImpl() + val element_0 = MyCompositeWorkspacePackagingElement("Name-14", "Name-13") + val element_1 = DirectoryPackagingElement("Name-17") + val element_2 = DirectoryPackagingElement("Name-10") + element_1.addFirstChild(element_2) + val element_2_2 = DirectoryPackagingElement("Name-1") + element_1.addFirstChild(element_2_2) + element_0.addFirstChild(element_1) + artifactRoot.addFirstChild(element_0) + invokeAndWaitIfNeeded { + runWriteAction { + ArtifactManager.getInstance(project).addArtifact("Artifact-0", PlainArtifactType.getInstance(), artifactRoot) + } + } + + val artifact = ArtifactManager.getInstance(project).artifacts.single() + val rootChildren = artifact.rootElement.children + TestCase.assertEquals(1, rootChildren.size) + val customElement = rootChildren.single() as MyCompositeWorkspacePackagingElement + TestCase.assertEquals("Name-14", customElement.state.data) + TestCase.assertEquals("Name-13", customElement.state.name) + + val directoryElement = customElement.children.single() as DirectoryPackagingElement + TestCase.assertEquals("Name-17", directoryElement.directoryName) + } + + fun `test complicated packaging elements structure`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + PackagingElementType.EP_NAME.point.registerExtension(MyCompositeWorkspacePackagingElementType, this.testRootDisposable) + + val artifactRoot = ArtifactRootElementImpl() + val element_0 = MyCompositeWorkspacePackagingElement("Name-3", "Name-11") + val element_1 = DirectoryPackagingElement("Name-13") + element_0.addFirstChild(element_1) + val element_1_2 = MyCompositeWorkspacePackagingElement("Name-10", "Name-16") + element_0.addFirstChild(element_1_2) + val element_1_3 = DirectoryPackagingElement("Name-7") + element_0.addFirstChild(element_1_3) + val element_1_4 = DirectoryPackagingElement("Name-12") + element_0.addFirstChild(element_1_4) + artifactRoot.addFirstChild(element_0) + invokeAndWaitIfNeeded { + runWriteAction { + ArtifactManager.getInstance(project).addArtifact("Artifact-0", PlainArtifactType.getInstance(), artifactRoot) + } + } + val bridgeArtifact = artifact(project, "Artifact-0") + (bridgeArtifact.rootElement.children.single() as MyCompositeWorkspacePackagingElement).children + val artifactEntity = artifactEntity(project, "Artifact-0") + assertTreesEquals(project, bridgeArtifact.rootElement, artifactEntity.rootElement) + } + + fun `test async artifacts requesting`() { + assumeTrue(WorkspaceModel.enabledForArtifacts) + + // This test checks that simultaneous requesting of artifacts from different threads won't lead to a multiple instances + // of the same artifact. + + repeat(1000) { + val workspaceModel = WorkspaceModel.getInstance(project) + val artifacts = workspaceModel.entityStorage.current.entities(ArtifactEntity::class.java).toList() + workspaceModel.updateProjectModel { + artifacts.forEach { artifact -> + it.removeEntity(artifact) + } + } + + repeat(10) { counter -> + workspaceModel.updateProjectModel { + val rootElementEntity = it.addArtifactRootElementEntity(emptyList(), MySource) + it.addArtifactEntity("Artifact-$counter", PlainArtifactType.ID, false, null, rootElementEntity, MySource) + } + } + + val threads = List(10) { + Callable { + ArtifactManager.getInstance(project).artifacts + } + } + + val service = AppExecutorUtil.createBoundedApplicationPoolExecutor("Test executor", JobSchedulerImpl.getCPUCoresCount()) + val res = ConcurrencyUtil.invokeAll(threads, service).map { it.get().sortedBy { it.name } } + for (i in res[0].indices) { + val mainArtifact = res[0][i] + for (j in 1..res.lastIndex) { + TestCase.assertSame(mainArtifact, res[j][i]) + } + } + } + } + + object MySource : EntitySource +} diff --git a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/LegacyBridgeProjectLifecycleListener.kt b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/LegacyBridgeProjectLifecycleListener.kt index 48f538d02c52..256f10fab55f 100644 --- a/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/LegacyBridgeProjectLifecycleListener.kt +++ b/platform/lang-impl/src/com/intellij/workspaceModel/ide/impl/legacyBridge/LegacyBridgeProjectLifecycleListener.kt @@ -1,6 +1,7 @@ // Copyright 2000-2021 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.workspaceModel.ide.impl.legacyBridge +import com.intellij.ide.plugins.IdeaPluginDescriptor import com.intellij.ide.plugins.PluginManagerCore import com.intellij.openapi.components.ServiceDescriptor import com.intellij.openapi.diagnostic.logger @@ -14,7 +15,6 @@ import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable import com.intellij.serviceContainer.ComponentManagerImpl import com.intellij.workspaceModel.ide.WorkspaceModel import com.intellij.workspaceModel.ide.WorkspaceModelTopics -import com.intellij.workspaceModel.ide.impl.WorkspaceModelImpl import com.intellij.workspaceModel.ide.impl.WorkspaceModelInitialTestContent import com.intellij.workspaceModel.ide.impl.jps.serialization.JpsProjectModelSynchronizer import com.intellij.workspaceModel.ide.impl.legacyBridge.facet.FacetEntityChangeListener @@ -61,5 +61,20 @@ class LegacyBridgeProjectLifecycleListener : ProjectServiceContainerCustomizer { container.registerService(ModifiableModelCommitterService::class.java, ModifiableModelCommitterServiceBridge::class.java, pluginDescriptor, true) container.registerService(WorkspaceModelTopics::class.java, WorkspaceModelTopics::class.java, pluginDescriptor, false) container.registerService(FacetEntityChangeListener::class.java, FacetEntityChangeListener::class.java, pluginDescriptor, false) + + if (WorkspaceModel.enabledForArtifacts) { + registerArtifactManager(container, pluginDescriptor) + } } -} \ No newline at end of file + + private fun registerArtifactManager(container: ComponentManagerImpl, pluginDescriptor: IdeaPluginDescriptor) { + try { //todo improve + val apiClass = Class.forName("com.intellij.packaging.artifacts.ArtifactManager", true, javaClass.classLoader) + val implClass = Class.forName("com.intellij.packaging.impl.artifacts.workspacemodel.ArtifactManagerBridge", true, + javaClass.classLoader) + container.registerService(apiClass, implClass, pluginDescriptor, true) + } + catch (ignored: Throwable) { + } + } +} diff --git a/platform/platform-impl/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectModelSynchronizer.kt b/platform/platform-impl/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectModelSynchronizer.kt index 750c88bad4a8..5ba6b711f772 100644 --- a/platform/platform-impl/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectModelSynchronizer.kt +++ b/platform/platform-impl/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectModelSynchronizer.kt @@ -252,8 +252,9 @@ class JpsProjectModelSynchronizer(private val project: Project) : Disposable { //TODO:: Get rid of dependency on ExternalStorageConfigurationManager in order to use in build process val externalStorageConfigurationManager = ExternalStorageConfigurationManager.getInstance(project) val fileInDirectorySourceNames = FileInDirectorySourceNames.from(WorkspaceModel.getInstance(project).entityStorage.current) - val serializers = JpsProjectEntitiesLoader.createProjectSerializers(configLocation, fileContentReader, externalStoragePath, false, - virtualFileManager, externalStorageConfigurationManager, fileInDirectorySourceNames) + val serializers = JpsProjectEntitiesLoader.createProjectSerializers(configLocation, fileContentReader, externalStoragePath, + virtualFileManager, + externalStorageConfigurationManager, fileInDirectorySourceNames) this.serializers.set(serializers) return serializers diff --git a/platform/testFramework/testSrc/tests/testGroups.properties b/platform/testFramework/testSrc/tests/testGroups.properties index 00956ac7754c..120221019b25 100644 --- a/platform/testFramework/testSrc/tests/testGroups.properties +++ b/platform/testFramework/testSrc/tests/testGroups.properties @@ -183,6 +183,7 @@ com.intellij.java.propertyBased.* com.intellij.lang.properties.propertyBased.PropertiesCodeInsightSanityTest org.jetbrains.plugins.groovy.GroovySanityTest com.intellij.workspaceModel.storage.propertyBased.* +com.intellij.compiler.artifacts.propertybased.* [GROOVY_TESTS] org.jetbrains.plugins.groovy.* diff --git a/platform/workspaceModel/ide/src/META-INF/WorkspaceModelExtensions.xml b/platform/workspaceModel/ide/src/META-INF/WorkspaceModelExtensions.xml index 0e7e5340e152..c89b51a35c15 100644 --- a/platform/workspaceModel/ide/src/META-INF/WorkspaceModelExtensions.xml +++ b/platform/workspaceModel/ide/src/META-INF/WorkspaceModelExtensions.xml @@ -23,6 +23,8 @@ description="Enable additional checks after replaceBySource and addDiff"/> + diff --git a/platform/workspaceModel/ide/src/com/intellij/workspaceModel/ide/WorkspaceModel.kt b/platform/workspaceModel/ide/src/com/intellij/workspaceModel/ide/WorkspaceModel.kt index 20b584745320..bdea158abf3e 100644 --- a/platform/workspaceModel/ide/src/com/intellij/workspaceModel/ide/WorkspaceModel.kt +++ b/platform/workspaceModel/ide/src/com/intellij/workspaceModel/ide/WorkspaceModel.kt @@ -71,6 +71,9 @@ interface WorkspaceModel { val isEnabled: Boolean get() = !Registry.`is`("ide.old.project.model") + val enabledForArtifacts: Boolean + get() = Registry.`is`("ide.new.project.model.artifacts") + @JvmStatic fun getInstance(project: Project): WorkspaceModel = project.service() } diff --git a/platform/workspaceModel/ide/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsArtifactEntitiesSerializer.kt b/platform/workspaceModel/ide/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsArtifactEntitiesSerializer.kt index 6a82992ad543..1e165090bdef 100644 --- a/platform/workspaceModel/ide/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsArtifactEntitiesSerializer.kt +++ b/platform/workspaceModel/ide/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsArtifactEntitiesSerializer.kt @@ -4,17 +4,21 @@ package com.intellij.workspaceModel.ide.impl.jps.serialization import com.intellij.openapi.util.JDOMUtil import com.intellij.util.xmlb.SkipDefaultsSerializationFilter import com.intellij.util.xmlb.XmlSerializer -import com.intellij.workspaceModel.storage.impl.EntityDataDelegation -import com.intellij.workspaceModel.storage.impl.WorkspaceEntityData -import com.intellij.workspaceModel.storage.impl.ModifiableWorkspaceEntityBase -import com.intellij.workspaceModel.storage.impl.WorkspaceEntityBase import com.intellij.workspaceModel.ide.JpsFileEntitySource +import com.intellij.workspaceModel.ide.JpsImportedEntitySource import com.intellij.workspaceModel.storage.* import com.intellij.workspaceModel.storage.bridgeEntities.* +import com.intellij.workspaceModel.storage.impl.EntityDataDelegation +import com.intellij.workspaceModel.storage.impl.ModifiableWorkspaceEntityBase +import com.intellij.workspaceModel.storage.impl.WorkspaceEntityBase +import com.intellij.workspaceModel.storage.impl.WorkspaceEntityData +import com.intellij.workspaceModel.storage.impl.references.MutableOneToOneChild +import com.intellij.workspaceModel.storage.impl.references.OneToOneChild import com.intellij.workspaceModel.storage.url.VirtualFileUrl import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager import org.jdom.Element import org.jetbrains.jps.model.serialization.JDomSerializationUtil +import org.jetbrains.jps.model.serialization.SerializationConstants import org.jetbrains.jps.model.serialization.artifact.ArtifactPropertiesState import org.jetbrains.jps.model.serialization.artifact.ArtifactState import org.jetbrains.jps.util.JpsPathUtil @@ -38,10 +42,42 @@ internal class JpsArtifactsDirectorySerializerFactory(override val directoryUrl: private const val ARTIFACT_MANAGER_COMPONENT_NAME = "ArtifactManager" +internal class JpsArtifactsExternalFileSerializer(private val externalFile: JpsFileEntitySource.ExactFile, + private val internalArtifactsDirUrl: VirtualFileUrl, virtualFileManager: VirtualFileUrlManager) + : JpsArtifactEntitiesSerializer(externalFile.file, externalFile, false, virtualFileManager), JpsFileEntityTypeSerializer { + override val isExternalStorage: Boolean + get() = true + + override val entityFilter: (ArtifactEntity) -> Boolean + get() = { (it.entitySource as? JpsImportedEntitySource)?.storedExternally == true } + + override val additionalEntityTypes: List> + get() = listOf(ArtifactsOrderEntity::class.java) + + override fun createEntitySource(artifactTag: Element): EntitySource? { + val externalSystemId = artifactTag.getAttributeValue(SerializationConstants.EXTERNAL_SYSTEM_ID_ATTRIBUTE) ?: return null + val internalEntitySource = JpsFileEntitySource.FileInDirectory(internalArtifactsDirUrl, externalFile.projectLocation) + return JpsImportedEntitySource(internalEntitySource, externalSystemId, true) + } + + override fun getExternalSystemId(artifactEntity: ArtifactEntity): String? { + val source = artifactEntity.entitySource + return (source as? JpsImportedEntitySource)?.externalSystemId + } + + override fun deleteObsoleteFile(fileUrl: String, writer: JpsFileContentWriter) { + writer.saveComponent(fileUrl, ARTIFACT_MANAGER_COMPONENT_NAME, null) + } +} + internal class JpsArtifactsFileSerializer(fileUrl: VirtualFileUrl, entitySource: JpsFileEntitySource, virtualFileManager: VirtualFileUrlManager) : JpsArtifactEntitiesSerializer(fileUrl, entitySource, true, virtualFileManager), JpsFileEntityTypeSerializer { override val isExternalStorage: Boolean get() = false + + override val entityFilter: (ArtifactEntity) -> Boolean + get() = { (it.entitySource as? JpsImportedEntitySource)?.storedExternally != true } + override val additionalEntityTypes: List> get() = listOf(ArtifactsOrderEntity::class.java) @@ -74,6 +110,9 @@ internal open class JpsArtifactEntitiesSerializer(override val fileUrl: VirtualF override val internalEntitySource: JpsFileEntitySource, private val preserveOrder: Boolean, private val virtualFileManager: VirtualFileUrlManager) : JpsFileEntitiesSerializer { + open val isExternalStorage: Boolean + get() = false + override val mainEntityClass: Class get() = ArtifactEntity::class.java @@ -84,16 +123,23 @@ internal open class JpsArtifactEntitiesSerializer(override val fileUrl: VirtualF val artifactListElement = reader.loadComponent(fileUrl.url, ARTIFACT_MANAGER_COMPONENT_NAME) if (artifactListElement == null) return - val source = internalEntitySource val orderOfItems = ArrayList() - artifactListElement.getChildren("artifact").forEach { - val state = XmlSerializer.deserialize(it, ArtifactState::class.java) - val outputUrl = virtualFileManager.fromPath(state.outputPath) - val rootElement = loadPackagingElement(state.rootElement, source, builder) + artifactListElement.getChildren("artifact").forEach { actifactElement -> + val entitySource = createEntitySource(actifactElement) ?: return@forEach + val state = XmlSerializer.deserialize(actifactElement, ArtifactState::class.java) + val outputUrl = state.outputPath?.let { path -> if (path.isNotEmpty()) virtualFileManager.fromPath(path) else null } + val rootElement = loadPackagingElement(state.rootElement, entitySource, builder) val artifactEntity = builder.addArtifactEntity(state.name, state.artifactType, state.isBuildOnMake, outputUrl, - rootElement as CompositePackagingElementEntity, source) + rootElement as CompositePackagingElementEntity, entitySource) for (propertiesState in state.propertiesList) { - builder.addArtifactPropertiesEntity(artifactEntity, propertiesState.id, JDOMUtil.write(propertiesState.options), source) + builder.addArtifactPropertiesEntity(artifactEntity, propertiesState.id, JDOMUtil.write(propertiesState.options), entitySource) + } + val externalSystemId = actifactElement.getAttributeValue(SerializationConstants.EXTERNAL_SYSTEM_ID_IN_INTERNAL_STORAGE_ATTRIBUTE) + if (externalSystemId != null && !isExternalStorage) { + builder.addEntity(ModifiableArtifactExternalSystemIdEntity::class.java, entitySource) { + this.externalSystemId = externalSystemId + artifact = artifactEntity + } } orderOfItems += state.name } @@ -105,11 +151,18 @@ internal open class JpsArtifactEntitiesSerializer(override val fileUrl: VirtualF } } else { - builder.addEntity(ModifiableArtifactsOrderEntity::class.java, source) { + builder.addEntity(ModifiableArtifactsOrderEntity::class.java, internalEntitySource) { orderOfArtifacts = orderOfItems } } } + + } + + protected open fun createEntitySource(artifactTag: Element): EntitySource? = internalEntitySource + + protected open fun getExternalSystemId(artifactEntity: ArtifactEntity): String? { + return artifactEntity.externalSystemId?.externalSystemId } private fun loadPackagingElement(element: Element, @@ -118,7 +171,7 @@ internal open class JpsArtifactEntitiesSerializer(override val fileUrl: VirtualF fun loadElementChildren() = element.children.mapTo(ArrayList()) { loadPackagingElement(it, source, builder) } fun getAttribute(name: String) = element.getAttributeValue(name)!! fun getOptionalAttribute(name: String) = element.getAttributeValue(name) - fun getPathAttribute(name: String) = virtualFileManager.fromPath(element.getAttributeValue(name)!!) + fun getPathAttribute(name: String) = element.getAttributeValue(name)!!.let { virtualFileManager.fromPath(it) } return when (val typeId = getAttribute("id")) { "root" -> builder.addArtifactRootElementEntity(loadElementChildren(), source) "directory" -> builder.addDirectoryPackagingElementEntity(getAttribute("name"), loadElementChildren(), source) @@ -126,21 +179,30 @@ internal open class JpsArtifactEntitiesSerializer(override val fileUrl: VirtualF "dir-copy" -> builder.addDirectoryCopyPackagingElementEntity(getPathAttribute("path"), source) "file-copy" -> builder.addFileCopyPackagingElementEntity(getPathAttribute("path"), getOptionalAttribute("output-file-name"), source) "extracted-dir" -> builder.addExtractedDirectoryPackagingElementEntity(getPathAttribute("path"), getAttribute("path-in-jar"), source) - "artifact" -> builder.addArtifactOutputPackagingElementEntity(ArtifactId(getAttribute("artifact-name")), source) - "module-output" -> builder.addModuleOutputPackagingElementEntity(ModuleId(getAttribute("name")), source) - "module-test-output" -> builder.addModuleTestOutputPackagingElementEntity(ModuleId(getAttribute("name")), source) - "module-source" -> builder.addModuleSourcePackagingElementEntity(ModuleId(getAttribute("name")), source) + "artifact" -> builder.addArtifactOutputPackagingElementEntity(getOptionalAttribute("artifact-name")?.let { ArtifactId(it) }, source) + "module-output" -> builder.addModuleOutputPackagingElementEntity(getOptionalAttribute("name")?.let { ModuleId(it) }, source) + "module-test-output" -> builder.addModuleTestOutputPackagingElementEntity(getOptionalAttribute("name")?.let { ModuleId(it) }, source) + "module-source" -> builder.addModuleSourcePackagingElementEntity(getOptionalAttribute("name")?.let { ModuleId(it) }, source) "library" -> { - val moduleName = getOptionalAttribute("module-name") - val level = getAttribute("level") + val level = getOptionalAttribute("level") val name = getOptionalAttribute("name") - val parentId = when { - moduleName != null -> LibraryTableId.ModuleLibraryTableId(ModuleId(moduleName)) - else -> levelToLibraryTableId(level) + if (level != null && name != null) { + val moduleName = getOptionalAttribute("module-name") + val parentId = when { + moduleName != null -> LibraryTableId.ModuleLibraryTableId(ModuleId(moduleName)) + else -> levelToLibraryTableId(level) + } + builder.addLibraryFilesPackagingElementEntity(LibraryId(name, parentId), source) + } + else { + builder.addLibraryFilesPackagingElementEntity(null, source) } - builder.addLibraryFilesPackagingElementEntity(LibraryId(name!!, parentId), source) } - else -> builder.addCustomPackagingElementEntity(typeId, JDOMUtil.write(element), source) + else -> { + val cloned = element.clone() + cloned.removeContent() + builder.addCustomPackagingElementEntity(typeId, JDOMUtil.write(cloned), loadElementChildren(), source) + } } } @@ -172,7 +234,7 @@ internal open class JpsArtifactEntitiesSerializer(override val fileUrl: VirtualF artifactState.name = artifact.name artifactState.artifactType = artifact.artifactType artifactState.isBuildOnMake = artifact.includeInProjectBuild - artifactState.outputPath = JpsPathUtil.urlToPath(artifact.outputUrl.url) + artifactState.outputPath = JpsPathUtil.urlToPath(artifact.outputUrl?.url) val customProperties = artifact.customProperties.filter { it.entitySource == artifact.entitySource } artifactState.propertiesList = customProperties.mapTo(ArrayList()) { ArtifactPropertiesState().apply { @@ -181,6 +243,13 @@ internal open class JpsArtifactEntitiesSerializer(override val fileUrl: VirtualF } } artifactState.rootElement = savePackagingElement(artifact.rootElement) + val externalSystemId = getExternalSystemId(artifact) + if (externalSystemId != null) { + if (isExternalStorage) + artifactState.externalSystemId = externalSystemId + else + artifactState.externalSystemIdInInternalStorage = externalSystemId + } return XmlSerializer.serialize(artifactState, SkipDefaultsSerializationFilter()) } @@ -214,46 +283,80 @@ internal open class JpsArtifactEntitiesSerializer(override val fileUrl: VirtualF } is DirectoryCopyPackagingElementEntity -> { setId("dir-copy") - setPathAttribute("path", element.directory) + setPathAttribute("path", element.filePath) } is FileCopyPackagingElementEntity -> { setId("file-copy") - setPathAttribute("path", element.file) + setPathAttribute("path", element.filePath) element.renamedOutputFileName?.let { setAttribute("output-file-name", it) } } is ExtractedDirectoryPackagingElementEntity -> { setId("extracted-dir") - setPathAttribute("path", element.archive) + setPathAttribute("path", element.filePath) setAttribute("path-in-jar", element.pathInArchive) } is ArtifactOutputPackagingElementEntity -> { setId("artifact") - setAttribute("artifact-name", element.artifact.name) + element.artifact?.let { setAttribute("artifact-name", it.name) } } is ModuleOutputPackagingElementEntity -> { setId("module-output") - setAttribute("name", element.module.name) + element.module?.let { setAttribute("name", it.name) } } is ModuleTestOutputPackagingElementEntity -> { setId("module-test-output") - setAttribute("name", element.module.name) + element.module?.let { setAttribute("name", it.name) } } is ModuleSourcePackagingElementEntity -> { setId("module-source") - setAttribute("name", element.module.name) + element.module?.let { setAttribute("name", it.name) } } is LibraryFilesPackagingElementEntity -> { setId("library") - val tableId = element.library.tableId - setAttribute("level", tableId.level) - setAttribute("name", element.library.name) - if (tableId is LibraryTableId.ModuleLibraryTableId) { - setAttribute("module-name", tableId.moduleId.name) + val library = element.library + if (library != null) { + val tableId = library.tableId + setAttribute("level", tableId.level) + setAttribute("name", library.name) + if (tableId is LibraryTableId.ModuleLibraryTableId) { + setAttribute("module-name", tableId.moduleId.name) + } } } + is CustomPackagingElementEntity -> { + setId(element.typeId) + val customElement = JDOMUtil.load(element.propertiesXmlTag) + customElement.attributes.forEach { attribute -> setAttribute(attribute.name, attribute.value) } + saveElementChildren(element) + } } return tag } override fun toString(): String = "${javaClass.simpleName.substringAfterLast('.')}($fileUrl)" -} \ No newline at end of file +} + +/** + * This property indicates that external-system-id attribute should be stored in artifact configuration file to avoid unnecessary modifications + */ +@Suppress("unused") +internal class ArtifactExternalSystemIdEntityData : WorkspaceEntityData() { + lateinit var externalSystemId: String + + override fun createEntity(snapshot: WorkspaceEntityStorage): ArtifactExternalSystemIdEntity { + return ArtifactExternalSystemIdEntity(externalSystemId).also { addMetaData(it, snapshot) } + } +} + +internal class ArtifactExternalSystemIdEntity( + val externalSystemId: String +) : WorkspaceEntityBase() { + val artifact: ArtifactEntity by OneToOneChild.NotNull(ArtifactEntity::class.java, true) +} + +internal class ModifiableArtifactExternalSystemIdEntity : ModifiableWorkspaceEntityBase() { + var externalSystemId: String by EntityDataDelegation() + var artifact: ArtifactEntity by MutableOneToOneChild.NotNull(ArtifactExternalSystemIdEntity::class.java, ArtifactEntity::class.java, true) +} + +private val ArtifactEntity.externalSystemId get() = referrers(ArtifactExternalSystemIdEntity::artifact).firstOrNull() diff --git a/platform/workspaceModel/ide/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectEntitiesLoader.kt b/platform/workspaceModel/ide/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectEntitiesLoader.kt index 463495294690..29eef95a3fe0 100644 --- a/platform/workspaceModel/ide/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectEntitiesLoader.kt +++ b/platform/workspaceModel/ide/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectEntitiesLoader.kt @@ -22,26 +22,22 @@ import org.jetbrains.jps.model.serialization.library.JpsLibraryTableSerializer import java.nio.file.Path object JpsProjectEntitiesLoader { - /** - * [serializeArtifacts] specifies whether artifacts should be serialized or not. We need this until a legacy bridge implementation for - * ArtifactManager is provided. - */ fun createProjectSerializers(configLocation: JpsProjectConfigLocation, reader: JpsFileContentReader, externalStoragePath: Path, - serializeArtifacts: Boolean, virtualFileManager: VirtualFileUrlManager, externalStorageConfigurationManager: ExternalStorageConfigurationManager? = null, fileInDirectorySourceNames: FileInDirectorySourceNames = FileInDirectorySourceNames.empty()): JpsProjectSerializers { - return createProjectEntitiesSerializers(configLocation, reader, externalStoragePath, serializeArtifacts, virtualFileManager, - externalStorageConfigurationManager, fileInDirectorySourceNames) + return createProjectEntitiesSerializers(configLocation, reader, externalStoragePath, virtualFileManager, + externalStorageConfigurationManager, + fileInDirectorySourceNames) } @TestOnly fun loadProject(configLocation: JpsProjectConfigLocation, builder: WorkspaceEntityStorageBuilder, externalStoragePath: Path, errorReporter: ErrorReporter, virtualFileManager: VirtualFileUrlManager): JpsProjectSerializers { val reader = CachingJpsFileContentReader(configLocation.baseDirectoryUrlString) - val data = createProjectEntitiesSerializers(configLocation, reader, externalStoragePath, true, virtualFileManager) + val data = createProjectEntitiesSerializers(configLocation, reader, externalStoragePath, virtualFileManager) data.loadAll(reader, builder, errorReporter, null) return data } @@ -67,16 +63,16 @@ object JpsProjectEntitiesLoader { private fun createProjectEntitiesSerializers(configLocation: JpsProjectConfigLocation, reader: JpsFileContentReader, externalStoragePath: Path, - serializeArtifacts: Boolean, virtualFileManager: VirtualFileUrlManager, externalStorageConfigurationManager: ExternalStorageConfigurationManager? = null, fileInDirectorySourceNames: FileInDirectorySourceNames = FileInDirectorySourceNames.empty()): JpsProjectSerializers { val externalStorageRoot = externalStoragePath.toVirtualFileUrl(virtualFileManager) val externalStorageMapping = JpsExternalStorageMappingImpl(externalStorageRoot, configLocation) return when (configLocation) { - is JpsProjectConfigLocation.FileBased -> createIprProjectSerializers(configLocation, reader, externalStorageMapping, serializeArtifacts, virtualFileManager, fileInDirectorySourceNames) + is JpsProjectConfigLocation.FileBased -> createIprProjectSerializers(configLocation, reader, externalStorageMapping, + virtualFileManager, fileInDirectorySourceNames) is JpsProjectConfigLocation.DirectoryBased -> createDirectoryProjectSerializers(configLocation, reader, externalStorageMapping, - serializeArtifacts, virtualFileManager, + virtualFileManager, externalStorageConfigurationManager, fileInDirectorySourceNames) } @@ -85,25 +81,24 @@ object JpsProjectEntitiesLoader { private fun createDirectoryProjectSerializers(configLocation: JpsProjectConfigLocation.DirectoryBased, reader: JpsFileContentReader, externalStorageMapping: JpsExternalStorageMapping, - serializeArtifacts: Boolean, virtualFileManager: VirtualFileUrlManager, externalStorageConfigurationManager: ExternalStorageConfigurationManager?, fileInDirectorySourceNames: FileInDirectorySourceNames): JpsProjectSerializers { val projectDirUrl = configLocation.projectDir.url val directorySerializersFactories = ArrayList>() val librariesDirectoryUrl = "$projectDirUrl/.idea/libraries" + val artifactsDirectoryUrl = "$projectDirUrl/.idea/artifacts" directorySerializersFactories += JpsLibrariesDirectorySerializerFactory(librariesDirectoryUrl) - if (serializeArtifacts) { - directorySerializersFactories += JpsArtifactsDirectorySerializerFactory("$projectDirUrl/.idea/artifacts") - } + directorySerializersFactories += JpsArtifactsDirectorySerializerFactory(artifactsDirectoryUrl) val externalStorageRoot = externalStorageMapping.externalStorageRoot - val internalLibrariesDirUrl = virtualFileManager.fromUrl(librariesDirectoryUrl) val externalStorageEnabled = isExternalStorageEnabled(reader, projectDirUrl) - val librariesExternalStorageFile = JpsFileEntitySource.ExactFile(externalStorageRoot.append("project/libraries.xml"), - configLocation) + val librariesExternalStorageFile = JpsFileEntitySource.ExactFile(externalStorageRoot.append("project/libraries.xml"), configLocation) + val artifactsExternalStorageFile = JpsFileEntitySource.ExactFile(externalStorageRoot.append("project/artifacts.xml"), configLocation) val externalModuleListSerializer = ExternalModuleListSerializer(externalStorageRoot, virtualFileManager) return JpsProjectSerializers.createSerializers( - entityTypeSerializers = listOf(JpsLibrariesExternalFileSerializer(librariesExternalStorageFile, internalLibrariesDirUrl)), + entityTypeSerializers = listOf(JpsLibrariesExternalFileSerializer(librariesExternalStorageFile, virtualFileManager.fromUrl(librariesDirectoryUrl)), + JpsArtifactsExternalFileSerializer(artifactsExternalStorageFile, virtualFileManager.fromUrl(artifactsDirectoryUrl), + virtualFileManager)), directorySerializersFactories = directorySerializersFactories, moduleListSerializers = listOf( ModuleListSerializerImpl("$projectDirUrl/.idea/modules.xml", virtualFileManager, externalModuleListSerializer, @@ -127,16 +122,13 @@ object JpsProjectEntitiesLoader { private fun createIprProjectSerializers(configLocation: JpsProjectConfigLocation.FileBased, reader: JpsFileContentReader, externalStorageMapping: JpsExternalStorageMappingImpl, - serializeArtifacts: Boolean, virtualFileManager: VirtualFileUrlManager, fileInDirectorySourceNames: FileInDirectorySourceNames): JpsProjectSerializers { val projectFileSource = JpsFileEntitySource.ExactFile(configLocation.iprFile, configLocation) val projectFileUrl = projectFileSource.file val entityTypeSerializers = ArrayList>() entityTypeSerializers += JpsLibrariesFileSerializer(projectFileSource, LibraryTableId.ProjectLibraryTableId) - if (serializeArtifacts) { - entityTypeSerializers += JpsArtifactsFileSerializer(projectFileUrl, projectFileSource, virtualFileManager) - } + entityTypeSerializers += JpsArtifactsFileSerializer(projectFileUrl, projectFileSource, virtualFileManager) return JpsProjectSerializers.createSerializers( entityTypeSerializers = entityTypeSerializers, directorySerializersFactories = emptyList(), diff --git a/platform/workspaceModel/ide/tests/testSrc/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectEntitiesLoaderTest.kt b/platform/workspaceModel/ide/tests/testSrc/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectEntitiesLoaderTest.kt index 58ecede578b4..864fc5c17276 100644 --- a/platform/workspaceModel/ide/tests/testSrc/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectEntitiesLoaderTest.kt +++ b/platform/workspaceModel/ide/tests/testSrc/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectEntitiesLoaderTest.kt @@ -12,10 +12,12 @@ import com.intellij.project.stateStore import com.intellij.testFramework.HeavyPlatformTestCase import com.intellij.util.io.write import com.intellij.workspaceModel.ide.getInstance -import com.intellij.workspaceModel.storage.* +import com.intellij.workspaceModel.storage.WorkspaceEntityStorage +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder import com.intellij.workspaceModel.storage.bridgeEntities.* -import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager +import com.intellij.workspaceModel.storage.checkConsistency import com.intellij.workspaceModel.storage.impl.url.toVirtualFileUrl +import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager import org.jetbrains.jps.util.JpsPathUtil import org.junit.Test import java.io.File @@ -148,32 +150,32 @@ class JpsProjectEntitiesLoaderTest : HeavyPlatformTestCase() { assertEquals("dir", artifacts[0].name) assertTrue(artifacts[0].includeInProjectBuild) - assertEquals(File(projectDir, "out/artifacts/dir").absolutePath, JpsPathUtil.urlToOsPath(artifacts[0].outputUrl.url)) + assertEquals(File(projectDir, "out/artifacts/dir").absolutePath, JpsPathUtil.urlToOsPath(artifacts[0].outputUrl!!.url)) val children = artifacts[0].rootElement.children.sortedBy { it::class.qualifiedName }.toList() assertEquals(2, children.size) val innerJar = children[0] as ArchivePackagingElementEntity assertEquals("x.jar", innerJar.fileName) - assertEquals(utilModule, (innerJar.children.single() as ModuleOutputPackagingElementEntity).module.resolve(storage)) + assertEquals(utilModule, (innerJar.children.single() as ModuleOutputPackagingElementEntity).module!!.resolve(storage)) val innerDir = children[1] as DirectoryPackagingElementEntity assertEquals("lib", innerDir.directoryName) val innerChildren = innerDir.children.toList() assertEquals(5, innerChildren.size) - assertNotNull(innerChildren.find { it is LibraryFilesPackagingElementEntity && it.library.resolve(storage) == log4jModuleLibrary }) - assertNotNull(innerChildren.find { it is LibraryFilesPackagingElementEntity && it.library.resolve(storage) == junitProjectLibrary }) - assertEquals(File(projectDir, "main.iml").absolutePath, JpsPathUtil.urlToOsPath(innerChildren.filterIsInstance().single().file.url)) - assertEquals(File(projectDir, "lib/junit-anno").absolutePath, JpsPathUtil.urlToOsPath(innerChildren.filterIsInstance().single().directory.url)) + assertNotNull(innerChildren.find { it is LibraryFilesPackagingElementEntity && it.library?.resolve(storage) == log4jModuleLibrary }) + assertNotNull(innerChildren.find { it is LibraryFilesPackagingElementEntity && it.library?.resolve(storage) == junitProjectLibrary }) + assertEquals(File(projectDir, "main.iml").absolutePath, JpsPathUtil.urlToOsPath(innerChildren.filterIsInstance().single().filePath!!.url)) + assertEquals(File(projectDir, "lib/junit-anno").absolutePath, JpsPathUtil.urlToOsPath(innerChildren.filterIsInstance().single().filePath!!.url)) innerChildren.filterIsInstance().single().let { - assertEquals(File(projectDir, "lib/junit.jar").absolutePath, JpsPathUtil.urlToOsPath(it.archive.url)) + assertEquals(File(projectDir, "lib/junit.jar").absolutePath, JpsPathUtil.urlToOsPath(it.filePath!!.url)) assertEquals("/junit/", it.pathInArchive) } assertEquals("jar", artifacts[1].name) - assertEquals(File(projectDir, "out/artifacts/jar").absolutePath, JpsPathUtil.urlToOsPath(artifacts[1].outputUrl.url)) + assertEquals(File(projectDir, "out/artifacts/jar").absolutePath, JpsPathUtil.urlToOsPath(artifacts[1].outputUrl!!.url)) val archiveRoot = artifacts[1].rootElement as ArchivePackagingElementEntity assertEquals("jar.jar", archiveRoot.fileName) val archiveChildren = archiveRoot.children.toList() assertEquals(3, archiveChildren.size) - assertEquals(artifacts[0], archiveChildren.filterIsInstance().single().artifact.resolve(storage)) + assertEquals(artifacts[0], archiveChildren.filterIsInstance().single().artifact!!.resolve(storage)) } @Test diff --git a/platform/workspaceModel/ide/tests/testSrc/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectSaveAllEntitiesTest.kt b/platform/workspaceModel/ide/tests/testSrc/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectSaveAllEntitiesTest.kt index 73f176742ab7..73ab9633a7f0 100644 --- a/platform/workspaceModel/ide/tests/testSrc/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectSaveAllEntitiesTest.kt +++ b/platform/workspaceModel/ide/tests/testSrc/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectSaveAllEntitiesTest.kt @@ -7,11 +7,11 @@ import com.intellij.testFramework.ApplicationRule import com.intellij.testFramework.rules.ProjectModelRule import com.intellij.workspaceModel.ide.getInstance import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder -import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager import com.intellij.workspaceModel.storage.bridgeEntities.LibraryRoot import com.intellij.workspaceModel.storage.bridgeEntities.LibraryRootTypeId import com.intellij.workspaceModel.storage.bridgeEntities.LibraryTableId import com.intellij.workspaceModel.storage.bridgeEntities.addLibraryEntity +import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager import org.junit.Before import org.junit.ClassRule import org.junit.Rule @@ -87,6 +87,24 @@ class JpsProjectSaveAllEntitiesTest { assertDirectoryMatches(projectDir, expectedDir, emptySet(), emptyList()) } + @Test + fun `escape special symbols in library name2`() { + val projectDir = FileUtil.createTempDirectory("jpsSaveTest", null) + val serializers = createProjectSerializers(projectDir, virtualFileManager) + + + val builder = WorkspaceEntityStorageBuilder.create() + for (libName in listOf("a lib", "my-lib", "group-id:artifact-id")) { + val source = JpsProjectEntitiesLoader.createJpsEntitySourceForProjectLibrary(projectDir.asConfigLocation(virtualFileManager)) + builder.addLibraryEntity(libName, LibraryTableId.ProjectLibraryTableId, emptyList(), emptyList(), source) + } + val storage = builder.toStorage() + serializers.saveAllEntities(storage, projectDir) + val expectedDir = File(PathManagerEx.getCommunityHomePath(), + "platform/workspaceModel/ide/tests/testData/serialization/specialSymbolsInLibraryName") + assertDirectoryMatches(projectDir, expectedDir, emptySet(), emptyList()) + } + private fun checkLoadSave(originalProjectFile: File) { val projectData = copyAndLoadProject(originalProjectFile, virtualFileManager) FileUtil.delete(projectData.projectDir) diff --git a/platform/workspaceModel/ide/tests/testSrc/com/intellij/workspaceModel/ide/impl/jps/serialization/jpsTestUtils.kt b/platform/workspaceModel/ide/tests/testSrc/com/intellij/workspaceModel/ide/impl/jps/serialization/jpsTestUtils.kt index 526da36d3489..99a57256e304 100644 --- a/platform/workspaceModel/ide/tests/testSrc/com/intellij/workspaceModel/ide/impl/jps/serialization/jpsTestUtils.kt +++ b/platform/workspaceModel/ide/tests/testSrc/com/intellij/workspaceModel/ide/impl/jps/serialization/jpsTestUtils.kt @@ -106,7 +106,8 @@ internal fun assertDirectoryMatches(actualDir: File, expectedDir: File, filesToI internal fun createProjectSerializers(projectDir: File, virtualFileManager: VirtualFileUrlManager): JpsProjectSerializersImpl { val reader = CachingJpsFileContentReader(VfsUtilCore.pathToUrl(projectDir.systemIndependentPath)) val externalStoragePath = projectDir.toPath().resolve("cache") - return JpsProjectEntitiesLoader.createProjectSerializers(toConfigLocation(projectDir.toPath(), virtualFileManager), reader, externalStoragePath, true, virtualFileManager) as JpsProjectSerializersImpl + return JpsProjectEntitiesLoader.createProjectSerializers(toConfigLocation(projectDir.toPath(), virtualFileManager), reader, + externalStoragePath, virtualFileManager) as JpsProjectSerializersImpl } fun JpsProjectSerializersImpl.checkConsistency(projectBaseDirUrl: String, storage: WorkspaceEntityStorage, virtualFileManager: VirtualFileUrlManager) { diff --git a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/ExternalEntityMapping.kt b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/ExternalEntityMapping.kt index dcdabb6090ea..3d75d0dec1df 100644 --- a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/ExternalEntityMapping.kt +++ b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/ExternalEntityMapping.kt @@ -9,6 +9,7 @@ interface ExternalEntityMapping { fun getEntities(data: T): List fun getDataByEntity(entity: WorkspaceEntity): T? fun forEach(action: (key: WorkspaceEntity, value: T) -> Unit) + fun size(): Int } interface MutableExternalEntityMapping : ExternalEntityMapping { diff --git a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/VersionedEntityStorage.kt b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/VersionedEntityStorage.kt index c828f4ce3bfa..301d8d71063e 100644 --- a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/VersionedEntityStorage.kt +++ b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/VersionedEntityStorage.kt @@ -10,6 +10,9 @@ interface VersionedEntityStorage { val version: Long val current: WorkspaceEntityStorage + // Return builder or storage that is base for this entity storage + val base: WorkspaceEntityStorage + /** * Return cached result or evaluate it by calling [value::source] if it isn't evaluated for the current version of storage yet. */ diff --git a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/WorkspaceEntityStorage.kt b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/WorkspaceEntityStorage.kt index b7cea7e5c32b..51b8fa3bb7be 100644 --- a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/WorkspaceEntityStorage.kt +++ b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/WorkspaceEntityStorage.kt @@ -138,6 +138,7 @@ interface WorkspaceEntityWithPersistentId : WorkspaceEntity { */ interface WorkspaceEntityStorage { fun entities(entityClass: Class): Sequence + fun entitiesAmount(entityClass: Class): Int fun referrers(e: E, entityClass: KClass, property: KProperty1>): Sequence fun referrers(id: PersistentEntityId, entityClass: Class): Sequence fun resolve(id: PersistentEntityId): E? diff --git a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/bridgeEntities/bridgeModelEntities.kt b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/bridgeEntities/bridgeModelEntities.kt index 92ed710ec1c5..f0f4560ec0c7 100644 --- a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/bridgeEntities/bridgeModelEntities.kt +++ b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/bridgeEntities/bridgeModelEntities.kt @@ -749,7 +749,7 @@ class ArtifactEntityData : WorkspaceEntityData.WithCalculablePersistentId(), SoftLinkable { - lateinit var artifact: ArtifactId + var artifact: ArtifactId? = null - override fun getLinks(): Set> = setOf(artifact) + override fun getLinks(): Set> = artifact?.let { setOf(it) } ?: emptySet() - override fun updateLink(oldLink: PersistentEntityId<*>, - newLink: PersistentEntityId<*>): Boolean { + override fun updateLink(oldLink: PersistentEntityId<*>, newLink: PersistentEntityId<*>): Boolean { if (oldLink != artifact) return false this.artifact = newLink as ArtifactId return true @@ -855,18 +854,15 @@ class ArtifactOutputPackagingElementEntityData : WorkspaceEntityData(), SoftLinkable { - lateinit var module: ModuleId + var module: ModuleId? = null - override fun getLinks(): Set> = setOf(module) + override fun getLinks(): Set> = module?.let { setOf(it) } ?: emptySet() - override fun updateLink(oldLink: PersistentEntityId<*>, - newLink: PersistentEntityId<*>): Boolean { + override fun updateLink(oldLink: PersistentEntityId<*>, newLink: PersistentEntityId<*>): Boolean { if (module != oldLink) return false this.module = newLink as ModuleId return true @@ -877,18 +873,15 @@ class ModuleOutputPackagingElementEntityData : WorkspaceEntityData(), SoftLinkable { - lateinit var library: LibraryId + var library: LibraryId? = null - override fun getLinks(): Set> = setOf(library) + override fun getLinks(): Set> = library?.let { setOf(it) } ?: emptySet() - override fun updateLink(oldLink: PersistentEntityId<*>, - newLink: PersistentEntityId<*>): Boolean { + override fun updateLink(oldLink: PersistentEntityId<*>, newLink: PersistentEntityId<*>): Boolean { if (oldLink == library) { this.library = newLink as LibraryId return true @@ -902,20 +895,15 @@ class LibraryFilesPackagingElementEntityData : WorkspaceEntityData(), SoftLinkable { - lateinit var module: ModuleId + var module: ModuleId? = null - override fun getLinks(): Set> { - return setOf(module) - } + override fun getLinks(): Set> = module?.let { setOf(it) } ?: emptySet() - override fun updateLink(oldLink: PersistentEntityId<*>, - newLink: PersistentEntityId<*>): Boolean { + override fun updateLink(oldLink: PersistentEntityId<*>, newLink: PersistentEntityId<*>): Boolean { if (module != oldLink) return false this.module = newLink as ModuleId return true @@ -926,18 +914,15 @@ class ModuleSourcePackagingElementEntityData : WorkspaceEntityData(), SoftLinkable { - lateinit var module: ModuleId + var module: ModuleId? = null - override fun getLinks(): Set> = setOf(module) + override fun getLinks(): Set> = module?.let { setOf(it) } ?: emptySet() - override fun updateLink(oldLink: PersistentEntityId<*>, - newLink: PersistentEntityId<*>): Boolean { + override fun updateLink(oldLink: PersistentEntityId<*>, newLink: PersistentEntityId<*>): Boolean { if (module != oldLink) return false this.module = newLink as ModuleId return true @@ -948,50 +933,50 @@ class ModuleTestOutputPackagingElementEntityData : WorkspaceEntityData() { - lateinit var directory: VirtualFileUrl + lateinit var filePath: VirtualFileUrl + override fun createEntity(snapshot: WorkspaceEntityStorage): DirectoryCopyPackagingElementEntity { - return DirectoryCopyPackagingElementEntity(directory).also { addMetaData(it, snapshot) } + return DirectoryCopyPackagingElementEntity(filePath).also { addMetaData(it, snapshot) } } } -class DirectoryCopyPackagingElementEntity( - val directory: VirtualFileUrl -) : PackagingElementEntity() +class DirectoryCopyPackagingElementEntity(filePath: VirtualFileUrl) : FileOrDirectoryPackagingElementEntity(filePath) @Suppress("unused") class ExtractedDirectoryPackagingElementEntityData : WorkspaceEntityData() { - lateinit var archive: VirtualFileUrl + lateinit var filePath: VirtualFileUrl lateinit var pathInArchive: String override fun createEntity(snapshot: WorkspaceEntityStorage): ExtractedDirectoryPackagingElementEntity { - return ExtractedDirectoryPackagingElementEntity(archive, pathInArchive).also { addMetaData(it, snapshot) } + return ExtractedDirectoryPackagingElementEntity(filePath, pathInArchive).also { addMetaData(it, snapshot) } } } class ExtractedDirectoryPackagingElementEntity( - val archive: VirtualFileUrl, + filePath: VirtualFileUrl, val pathInArchive: String -) : PackagingElementEntity() +) : FileOrDirectoryPackagingElementEntity(filePath) @Suppress("unused") class FileCopyPackagingElementEntityData : WorkspaceEntityData() { - lateinit var file: VirtualFileUrl + lateinit var filePath: VirtualFileUrl var renamedOutputFileName: String? = null + override fun createEntity(snapshot: WorkspaceEntityStorage): FileCopyPackagingElementEntity { - return FileCopyPackagingElementEntity(file, renamedOutputFileName).also { addMetaData(it, snapshot) } + return FileCopyPackagingElementEntity(filePath, renamedOutputFileName).also { addMetaData(it, snapshot) } } } class FileCopyPackagingElementEntity( - val file: VirtualFileUrl, + filePath: VirtualFileUrl, val renamedOutputFileName: String? -) : PackagingElementEntity() +) : FileOrDirectoryPackagingElementEntity(filePath) @Suppress("unused") class CustomPackagingElementEntityData : WorkspaceEntityData() { @@ -1006,4 +991,4 @@ class CustomPackagingElementEntityData : WorkspaceEntityData() @@ -272,23 +269,29 @@ class ModifiableArtifactEntity : ModifiableWorkspaceEntityBase() var name: String by EntityDataDelegation() var artifactType: String by EntityDataDelegation() var includeInProjectBuild: Boolean by EntityDataDelegation() - var outputUrl: VirtualFileUrl by VirtualFileUrlProperty() + var outputUrl: VirtualFileUrl? by VirtualFileUrlNullableProperty() var rootElement: CompositePackagingElementEntity by MutableOneToAbstractOneChild(ArtifactEntity::class.java, CompositePackagingElementEntity::class.java) + var customProperties: Sequence by customPropertiesDelegate + + companion object { + val customPropertiesDelegate = MutableOneToMany(ArtifactEntity::class.java, ArtifactPropertiesEntity::class.java, false) + } } fun WorkspaceEntityStorageDiffBuilder.addArtifactEntity(name: String, artifactType: String, includeInProjectBuild: Boolean, - outputUrl: VirtualFileUrl, + outputUrl: VirtualFileUrl?, rootElement: CompositePackagingElementEntity, - source: EntitySource) = addEntity( - ModifiableArtifactEntity::class.java, source) { - this.name = name - this.artifactType = artifactType - this.includeInProjectBuild = includeInProjectBuild - this.outputUrl = outputUrl - this.rootElement = rootElement + source: EntitySource): ArtifactEntity { + return addEntity(ModifiableArtifactEntity::class.java, source) { + this.name = name + this.artifactType = artifactType + this.includeInProjectBuild = includeInProjectBuild + this.outputUrl = outputUrl + this.rootElement = rootElement + } } class ModifiableArtifactPropertiesEntity : ModifiableWorkspaceEntityBase() { @@ -306,19 +309,24 @@ fun WorkspaceEntityStorageDiffBuilder.addArtifactPropertiesEntity(artifact: Arti this.propertiesXmlTag = propertiesXmlTag } -class ModifiableArtifactRootElementEntity : ModifiableWorkspaceEntityBase() { - var children: Sequence by MutableOneToAbstractMany(ArtifactRootElementEntity::class.java, - PackagingElementEntity::class.java) +abstract class ModifiableCompositePackagingElementEntity(clazz: Class) : ModifiableWorkspaceEntityBase() { + var children: Sequence by MutableOneToAbstractMany(clazz, PackagingElementEntity::class.java) } -fun WorkspaceEntityStorageDiffBuilder.addArtifactRootElementEntity(children: MutableList, - source: EntitySource) = addEntity( - ModifiableArtifactRootElementEntity::class.java, source) { this.children = children.asSequence() } +class ModifiableArtifactRootElementEntity : ModifiableCompositePackagingElementEntity( + ArtifactRootElementEntity::class.java +) -class ModifiableDirectoryPackagingElementEntity : ModifiableWorkspaceEntityBase() { +fun WorkspaceEntityStorageDiffBuilder.addArtifactRootElementEntity(children: List, + source: EntitySource): ArtifactRootElementEntity { + return addEntity(ModifiableArtifactRootElementEntity::class.java, source) { + this.children = children.asSequence() + } +} + +class ModifiableDirectoryPackagingElementEntity : ModifiableCompositePackagingElementEntity( + DirectoryPackagingElementEntity::class.java) { var directoryName: String by EntityDataDelegation() - var children: Sequence by MutableOneToAbstractMany(DirectoryPackagingElementEntity::class.java, - PackagingElementEntity::class.java) } fun WorkspaceEntityStorageDiffBuilder.addDirectoryPackagingElementEntity(directoryName: String, @@ -330,10 +338,9 @@ fun WorkspaceEntityStorageDiffBuilder.addDirectoryPackagingElementEntity(directo } } -class ModifiableArchivePackagingElementEntity : ModifiableWorkspaceEntityBase() { +class ModifiableArchivePackagingElementEntity : ModifiableCompositePackagingElementEntity( + ArchivePackagingElementEntity::class.java) { var fileName: String by EntityDataDelegation() - var children: Sequence by MutableOneToAbstractMany(ArchivePackagingElementEntity::class.java, - PackagingElementEntity::class.java) } fun WorkspaceEntityStorageDiffBuilder.addArchivePackagingElementEntity(fileName: String, @@ -346,79 +353,105 @@ fun WorkspaceEntityStorageDiffBuilder.addArchivePackagingElementEntity(fileName: } class ModifiableArtifactOutputPackagingElementEntity : ModifiableWorkspaceEntityBase() { - var artifact: ArtifactId by EntityDataDelegation() + var artifact: ArtifactId? by EntityDataDelegation() } -fun WorkspaceEntityStorageDiffBuilder.addArtifactOutputPackagingElementEntity(artifact: ArtifactId, source: EntitySource) = addEntity( - ModifiableArtifactOutputPackagingElementEntity::class.java, source) { this.artifact = artifact } +fun WorkspaceEntityStorageDiffBuilder.addArtifactOutputPackagingElementEntity(artifact: ArtifactId?, source: EntitySource): ArtifactOutputPackagingElementEntity { + return addEntity(ModifiableArtifactOutputPackagingElementEntity::class.java, source) { + this.artifact = artifact + } +} class ModifiableModuleOutputPackagingElementEntity : ModifiableWorkspaceEntityBase() { - var module: ModuleId by EntityDataDelegation() + var module: ModuleId? by EntityDataDelegation() } -fun WorkspaceEntityStorageDiffBuilder.addModuleOutputPackagingElementEntity(module: ModuleId, source: EntitySource) = addEntity( - ModifiableModuleOutputPackagingElementEntity::class.java, source) { this.module = module } +fun WorkspaceEntityStorageDiffBuilder.addModuleOutputPackagingElementEntity(module: ModuleId?, source: EntitySource): ModuleOutputPackagingElementEntity { + return addEntity(ModifiableModuleOutputPackagingElementEntity::class.java, source) { + this.module = module + } +} class ModifiableLibraryFilesPackagingElementEntity : ModifiableWorkspaceEntityBase() { - var library: LibraryId by EntityDataDelegation() + var library: LibraryId? by EntityDataDelegation() } -fun WorkspaceEntityStorageDiffBuilder.addLibraryFilesPackagingElementEntity(library: LibraryId, source: EntitySource) = addEntity( - ModifiableLibraryFilesPackagingElementEntity::class.java, source) { this.library = library } +fun WorkspaceEntityStorageDiffBuilder.addLibraryFilesPackagingElementEntity(library: LibraryId?, source: EntitySource): LibraryFilesPackagingElementEntity { + return addEntity(ModifiableLibraryFilesPackagingElementEntity::class.java, source) { + this.library = library + } +} class ModifiableModuleSourcePackagingElementEntity : ModifiableWorkspaceEntityBase() { - var module: ModuleId by EntityDataDelegation() + var module: ModuleId? by EntityDataDelegation() } -fun WorkspaceEntityStorageDiffBuilder.addModuleSourcePackagingElementEntity(module: ModuleId, source: EntitySource) = addEntity( - ModifiableModuleSourcePackagingElementEntity::class.java, source) { this.module = module } +fun WorkspaceEntityStorageDiffBuilder.addModuleSourcePackagingElementEntity(module: ModuleId?, source: EntitySource): ModuleSourcePackagingElementEntity { + return addEntity(ModifiableModuleSourcePackagingElementEntity::class.java, source) { + this.module = module + } +} class ModifiableModuleTestOutputPackagingElementEntity : ModifiableWorkspaceEntityBase() { - var module: ModuleId by EntityDataDelegation() + var module: ModuleId? by EntityDataDelegation() } -fun WorkspaceEntityStorageDiffBuilder.addModuleTestOutputPackagingElementEntity(module: ModuleId, source: EntitySource) = addEntity( - ModifiableModuleTestOutputPackagingElementEntity::class.java, source) { this.module = module } - -class ModifiableDirectoryCopyPackagingElementEntity : ModifiableWorkspaceEntityBase() { - var directory: VirtualFileUrl by VirtualFileUrlProperty() +fun WorkspaceEntityStorageDiffBuilder.addModuleTestOutputPackagingElementEntity(module: ModuleId?, source: EntitySource): ModuleTestOutputPackagingElementEntity { + return addEntity(ModifiableModuleTestOutputPackagingElementEntity::class.java, source) { + this.module = module + } } -fun WorkspaceEntityStorageDiffBuilder.addDirectoryCopyPackagingElementEntity(directory: VirtualFileUrl, source: EntitySource) = addEntity( - ModifiableDirectoryCopyPackagingElementEntity::class.java, source) { this.directory = directory } +abstract class ModifiableFileOrDirectoryPackagingElement : ModifiableWorkspaceEntityBase() { + var filePath: VirtualFileUrl by VirtualFileUrlProperty() +} -class ModifiableExtractedDirectoryPackagingElementEntity : ModifiableWorkspaceEntityBase() { - var archive: VirtualFileUrl by VirtualFileUrlProperty() +class ModifiableDirectoryCopyPackagingElementEntity : ModifiableFileOrDirectoryPackagingElement() + +fun WorkspaceEntityStorageDiffBuilder.addDirectoryCopyPackagingElementEntity(filePath: VirtualFileUrl, source: EntitySource): DirectoryCopyPackagingElementEntity { + return addEntity(ModifiableDirectoryCopyPackagingElementEntity::class.java, source) { + this.filePath = filePath + } +} + +class ModifiableExtractedDirectoryPackagingElementEntity : ModifiableFileOrDirectoryPackagingElement() { var pathInArchive: String by EntityDataDelegation() } -fun WorkspaceEntityStorageDiffBuilder.addExtractedDirectoryPackagingElementEntity(archive: VirtualFileUrl, - pathInArchive: String, source: EntitySource) = addEntity( - ModifiableExtractedDirectoryPackagingElementEntity::class.java, source) { - this.archive = archive - this.pathInArchive = pathInArchive +fun WorkspaceEntityStorageDiffBuilder.addExtractedDirectoryPackagingElementEntity(filePath: VirtualFileUrl, + pathInArchive: String, + source: EntitySource): ExtractedDirectoryPackagingElementEntity { + return addEntity(ModifiableExtractedDirectoryPackagingElementEntity::class.java, source) { + this.filePath = filePath + this.pathInArchive = pathInArchive + } } -class ModifiableFileCopyPackagingElementEntity : ModifiableWorkspaceEntityBase() { - var file: VirtualFileUrl by VirtualFileUrlProperty() +class ModifiableFileCopyPackagingElementEntity : ModifiableFileOrDirectoryPackagingElement() { var renamedOutputFileName: String? by EntityDataDelegation() } -fun WorkspaceEntityStorageDiffBuilder.addFileCopyPackagingElementEntity(file: VirtualFileUrl, - renamedOutputFileName: String?, source: EntitySource) = addEntity( - ModifiableFileCopyPackagingElementEntity::class.java, source) { - this.file = file - this.renamedOutputFileName = renamedOutputFileName +fun WorkspaceEntityStorageDiffBuilder.addFileCopyPackagingElementEntity(filePath: VirtualFileUrl, + renamedOutputFileName: String?, + source: EntitySource): FileCopyPackagingElementEntity { + return addEntity(ModifiableFileCopyPackagingElementEntity::class.java, source) { + this.filePath = filePath + this.renamedOutputFileName = renamedOutputFileName + } } -class ModifiableCustomPackagingElementEntity : ModifiableWorkspaceEntityBase() { +class ModifiableCustomPackagingElementEntity : ModifiableCompositePackagingElementEntity(CustomPackagingElementEntity::class.java) { var typeId: String by EntityDataDelegation() var propertiesXmlTag: String by EntityDataDelegation() } fun WorkspaceEntityStorageDiffBuilder.addCustomPackagingElementEntity(typeId: String, - propertiesXmlTag: String, source: EntitySource) = addEntity( - ModifiableCustomPackagingElementEntity::class.java, source) { - this.typeId = typeId - this.propertiesXmlTag = propertiesXmlTag + propertiesXmlTag: String, + children: List, + source: EntitySource): CustomPackagingElementEntity { + return addEntity(ModifiableCustomPackagingElementEntity::class.java, source) { + this.typeId = typeId + this.propertiesXmlTag = propertiesXmlTag + this.children = children.asSequence() + } } diff --git a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/impl/VersionedEntityStorageImpl.kt b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/impl/VersionedEntityStorageImpl.kt index 71be254a55ee..69c35278665f 100644 --- a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/impl/VersionedEntityStorageImpl.kt +++ b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/impl/VersionedEntityStorageImpl.kt @@ -56,7 +56,7 @@ internal class ValuesCache { } } -class VersionedEntityStorageOnBuilder(private val builder: WorkspaceEntityStorageBuilder) : VersionedEntityStorage { +class VersionedEntityStorageOnBuilder(val builder: WorkspaceEntityStorageBuilder) : VersionedEntityStorage { private val currentSnapshot: AtomicReference = AtomicReference() private val valuesCache: ValuesCache get() = getCurrentSnapshot().cache @@ -67,6 +67,9 @@ class VersionedEntityStorageOnBuilder(private val builder: WorkspaceEntityStorag override val current: WorkspaceEntityStorage get() = getCurrentSnapshot().storage + override val base: WorkspaceEntityStorage + get() = builder + override fun cachedValue(value: CachedValue): R = valuesCache.cachedValue(value, current) override fun cachedValue(value: CachedValueWithParameter, parameter: P): R = @@ -100,6 +103,9 @@ class VersionedEntityStorageOnStorage(private val storage: WorkspaceEntityStorag override val current: WorkspaceEntityStorage get() = storage + override val base: WorkspaceEntityStorage + get() = storage + override fun cachedValue(value: CachedValue): R = valuesCache.cachedValue(value, current) override fun cachedValue(value: CachedValueWithParameter, parameter: P): R = @@ -117,6 +123,9 @@ class DummyVersionedEntityStorage(private val builder: WorkspaceEntityStorageBui override val current: WorkspaceEntityStorage get() = builder + override val base: WorkspaceEntityStorage + get() = builder + override fun cachedValue(value: CachedValue): R = value.source(current) override fun cachedValue(value: CachedValueWithParameter, parameter: P): R = value.source(current, parameter) override fun clearCachedValue(value: CachedValue) { } @@ -139,6 +148,9 @@ open class VersionedEntityStorageImpl(initialStorage: WorkspaceEntityStorage) : override val current: WorkspaceEntityStorage get() = currentPointer.storage + override val base: WorkspaceEntityStorage + get() = current + override val version: Long get() = currentPointer.version diff --git a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/impl/WorkspaceEntityStorageImpl.kt b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/impl/WorkspaceEntityStorageImpl.kt index a516558929fd..1fbc47c058f3 100644 --- a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/impl/WorkspaceEntityStorageImpl.kt +++ b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/impl/WorkspaceEntityStorageImpl.kt @@ -796,6 +796,10 @@ internal sealed class AbstractEntityStorage(internal val consistencyCheckingMode return entitiesByType[entityClass.toClassId()]?.all()?.map { it.createEntity(this) } as? Sequence ?: emptySequence() } + override fun entitiesAmount(entityClass: Class): Int { + return entitiesByType[entityClass.toClassId()]?.size() ?: 0 + } + internal fun entityDataById(id: EntityId): WorkspaceEntityData? = entitiesByType[id.clazz]?.get(id.arrayId) internal fun entityDataByIdOrDie(id: EntityId): WorkspaceEntityData { diff --git a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/impl/external/ExternalEntityMappingImpl.kt b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/impl/external/ExternalEntityMappingImpl.kt index 28b51e95e21e..52d7ee5bf044 100644 --- a/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/impl/external/ExternalEntityMappingImpl.kt +++ b/platform/workspaceModel/storage/src/com/intellij/workspaceModel/storage/impl/external/ExternalEntityMappingImpl.kt @@ -10,7 +10,6 @@ import com.intellij.workspaceModel.storage.impl.EntityId import com.intellij.workspaceModel.storage.impl.WorkspaceEntityBase import com.intellij.workspaceModel.storage.impl.WorkspaceEntityStorageBuilderImpl import com.intellij.workspaceModel.storage.impl.containers.BidirectionalMap -import org.jetbrains.annotations.TestOnly import java.util.* internal open class ExternalEntityMappingImpl internal constructor(internal open val index: BidirectionalMap) @@ -28,8 +27,7 @@ internal open class ExternalEntityMappingImpl internal constructor(internal o return index[entity.id] } - @TestOnly - fun size(): Int = index.size + override fun size(): Int = index.size internal fun setTypedEntityStorage(storage: AbstractEntityStorage) { entityStorage = storage @@ -182,4 +180,5 @@ internal object EmptyExternalEntityMapping : ExternalEntityMapping { override fun getEntities(data: Any): List = emptyList() override fun getDataByEntity(entity: WorkspaceEntity): Any? = null override fun forEach(action: (key: WorkspaceEntity, value: Any) -> Unit) {} + override fun size(): Int = 0 }