[platform] make EP used in artifact configuration dynamic (IDEA-238263)

GitOrigin-RevId: 46b338834a94feae122d9362a0481b82a1c289d8
This commit is contained in:
nik
2020-04-21 15:37:15 +00:00
committed by intellij-monorepo-bot
parent c44aac84f8
commit ef730e53c8
7 changed files with 314 additions and 37 deletions
@@ -35,6 +35,7 @@ import org.jetbrains.jps.model.serialization.artifact.ArtifactPropertiesState;
import org.jetbrains.jps.model.serialization.artifact.ArtifactState;
import java.util.*;
import java.util.function.Function;
@State(name = ArtifactManagerImpl.COMPONENT_NAME, storages = @Storage(value = "artifacts", stateSplitter = ArtifactManagerStateSplitter.class))
public final class ArtifactManagerImpl extends ArtifactManager implements PersistentStateComponent<ArtifactManagerState>, Disposable {
@@ -55,6 +56,7 @@ public final class ArtifactManagerImpl extends ArtifactManager implements Persis
myModel = new ArtifactManagerModel();
myResolvingContext = new DefaultPackagingElementResolvingContext(myProject);
((ArtifactPointerManagerImpl)ArtifactPointerManager.getInstance(project)).setArtifactManager(this);
new DynamicArtifactExtensionsLoader(this).installListeners(this);
}
@Override
@@ -94,36 +96,51 @@ public final class ArtifactManagerImpl extends ArtifactManager implements Persis
public ArtifactManagerState getState() {
final ArtifactManagerState state = new ArtifactManagerState();
for (Artifact artifact : getAllArtifactsIncludingInvalid()) {
final ArtifactState artifactState;
if (artifact instanceof InvalidArtifact) {
artifactState = ((InvalidArtifact)artifact).getState();
}
else {
artifactState = new ArtifactState();
artifactState.setBuildOnMake(artifact.isBuildOnMake());
artifactState.setName(artifact.getName());
artifactState.setOutputPath(artifact.getOutputPath());
artifactState.setRootElement(serializePackagingElement(artifact.getRootElement()));
artifactState.setArtifactType(artifact.getArtifactType().getId());
ProjectModelExternalSource externalSource = artifact.getExternalSource();
if (externalSource != null && ProjectUtilCore.isExternalStorageEnabled(myProject)) {
//we can add this attribute only if the artifact configuration will be stored separately, otherwise we will get modified files in .idea/artifacts.
artifactState.setExternalSystemId(externalSource.getId());
}
for (ArtifactPropertiesProvider provider : artifact.getPropertiesProviders()) {
final ArtifactPropertiesState propertiesState = serializeProperties(provider, artifact.getProperties(provider));
if (propertiesState != null) {
artifactState.getPropertiesList().add(propertiesState);
}
}
artifactState.getPropertiesList().sort(Comparator.comparing(ArtifactPropertiesState::getId));
}
state.getArtifacts().add(artifactState);
state.getArtifacts().add(saveArtifact(artifact));
}
return state;
}
ArtifactState saveArtifact(Artifact artifact) {
ArtifactState artifactState;
if (artifact instanceof InvalidArtifact) {
artifactState = ((InvalidArtifact)artifact).getState();
}
else {
artifactState = new ArtifactState();
artifactState.setBuildOnMake(artifact.isBuildOnMake());
artifactState.setName(artifact.getName());
artifactState.setOutputPath(artifact.getOutputPath());
artifactState.setRootElement(serializePackagingElement(artifact.getRootElement()));
artifactState.setArtifactType(artifact.getArtifactType().getId());
ProjectModelExternalSource externalSource = artifact.getExternalSource();
if (externalSource != null && ProjectUtilCore.isExternalStorageEnabled(myProject)) {
//we can add this attribute only if the artifact configuration will be stored separately, otherwise we will get modified files in .idea/artifacts.
artifactState.setExternalSystemId(externalSource.getId());
}
for (ArtifactPropertiesProvider provider : artifact.getPropertiesProviders()) {
final ArtifactPropertiesState propertiesState = serializeProperties(provider, artifact.getProperties(provider));
if (propertiesState != null) {
artifactState.getPropertiesList().add(propertiesState);
}
}
artifactState.getPropertiesList().sort(Comparator.comparing(ArtifactPropertiesState::getId));
}
return artifactState;
}
public void replaceArtifacts(@NotNull Collection<? extends Artifact> toReplace, Function<Artifact, ArtifactImpl> replacement) {
if (toReplace.isEmpty()) return;
ArtifactModelImpl model = createModifiableModel();
for (Artifact artifact : toReplace) {
model.removeArtifact(artifact);
model.addArtifact(replacement.apply(artifact));
}
model.commit();
}
@Nullable
private static <S> ArtifactPropertiesState serializeProperties(ArtifactPropertiesProvider provider, ArtifactProperties<S> properties) {
final Element options = XmlSerializer.serialize(properties.getState());
@@ -196,7 +213,7 @@ public final class ArtifactManagerImpl extends ArtifactManager implements Persis
}
}
private ArtifactImpl loadArtifact(ArtifactState state) {
ArtifactImpl loadArtifact(ArtifactState state) {
ArtifactType type = ArtifactType.findById(state.getArtifactType());
ProjectModelExternalSource externalSource = findExternalSource(state.getExternalSystemId());
if (type == null) {
@@ -299,7 +316,7 @@ public final class ArtifactManagerImpl extends ArtifactManager implements Persis
}
@Override
public ModifiableArtifactModel createModifiableModel() {
public ArtifactModelImpl createModifiableModel() {
return new ArtifactModelImpl(this, getArtifactsList());
}
@@ -79,13 +79,17 @@ public class ArtifactModelImpl extends ArtifactModelBase implements ModifiableAr
final String uniqueName = generateUniqueName(name);
final String outputPath = ArtifactUtil.getDefaultArtifactOutputPath(uniqueName, myArtifactManager.getProject());
final ArtifactImpl artifact = new ArtifactImpl(uniqueName, artifactType, false, rootElement, outputPath, externalSource, myDispatcher);
addArtifact(artifact);
return artifact;
}
void addArtifact(ArtifactImpl artifact) {
myOriginalArtifacts.add(artifact);
myArtifact2ModifiableCopy.put(artifact, artifact);
myModifiable2Original.put(artifact, artifact);
artifactsChanged();
myDispatcher.getMulticaster().artifactAdded(artifact);
return artifact;
}
private String generateUniqueName(String baseName) {
@@ -0,0 +1,71 @@
// 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.artifacts
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.Artifact
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.util.Processor
import org.jetbrains.annotations.NotNull
internal class DynamicArtifactExtensionsLoader(private val artifactManager: ArtifactManagerImpl) {
fun installListeners(disposable: Disposable) {
ArtifactType.EP_NAME.getPoint(null).addExtensionPointListener(object : ExtensionPointListener<ArtifactType> {
override fun extensionAdded(extension: ArtifactType, pluginDescriptor: PluginDescriptor) {
runWriteAction {
reloadArtifacts(artifactManager.allArtifactsIncludingInvalid.filter {
(it as? InvalidArtifact)?.state?.artifactType == extension.id
})
}
}
override fun extensionRemoved(extension: ArtifactType, pluginDescriptor: PluginDescriptor) {
reloadArtifacts(artifactManager.getArtifactsByType(extension))
}
}, false, disposable)
PackagingElementType.EP_NAME.getPoint(null).addExtensionPointListener(object : ExtensionPointListener<PackagingElementType<out PackagingElement<*>>> {
override fun extensionAdded(extension: PackagingElementType<out PackagingElement<*>>, pluginDescriptor: PluginDescriptor) {
runWriteAction {
reloadArtifacts(artifactManager.allArtifactsIncludingInvalid.filter {
it.artifactType == InvalidArtifactType.getInstance()
})
}
}
override fun extensionRemoved(extension: PackagingElementType<out PackagingElement<*>>, pluginDescriptor: PluginDescriptor) {
reloadArtifacts(artifactManager.artifactsList.filter { containsElementsOfType(it, extension) })
}
}, false, disposable)
ArtifactPropertiesProvider.EP_NAME.getPoint(null).addExtensionPointListener(object : ExtensionPointListener<ArtifactPropertiesProvider> {
override fun extensionAdded(extension: ArtifactPropertiesProvider, pluginDescriptor: PluginDescriptor) {
runWriteAction {
reloadArtifacts(artifactManager.allArtifactsIncludingInvalid.filter { artifact ->
(artifact as? InvalidArtifact)?.state?.propertiesList?.any { it.id == extension.id } ?: false
})
}
}
override fun extensionRemoved(extension: ArtifactPropertiesProvider, pluginDescriptor: PluginDescriptor) {
reloadArtifacts(artifactManager.artifactsList.filter { extension in it.propertiesProviders })
}
}, false, disposable)
}
private fun <E : PackagingElement<*>> containsElementsOfType(artifact: Artifact, type: PackagingElementType<E>): Boolean {
return !ArtifactUtil.processPackagingElements(artifact, type, Processor { false },
artifactManager.resolvingContext, false)
}
private fun reloadArtifacts(toReplace: @NotNull Collection<Artifact>) {
artifactManager.replaceArtifacts(toReplace) {
artifactManager.loadArtifact(artifactManager.saveArtifact(it))
}
}
}
@@ -17,13 +17,14 @@ package com.intellij.packaging.impl.artifacts;
import com.intellij.openapi.roots.ProjectModelExternalSource;
import com.intellij.packaging.elements.PackagingElementFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.model.serialization.artifact.ArtifactState;
public class InvalidArtifact extends ArtifactImpl {
private final ArtifactState myState;
private final String myErrorMessage;
public InvalidArtifact(ArtifactState state, String errorMessage, ProjectModelExternalSource externalSource) {
public InvalidArtifact(@NotNull ArtifactState state, String errorMessage, ProjectModelExternalSource externalSource) {
super(state.getName(), InvalidArtifactType.getInstance(), false, PackagingElementFactory.getInstance().createArtifactRootElement(), "",
externalSource);
myState = state;
@@ -0,0 +1,173 @@
// 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.compiler.artifacts
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.module.impl.ProjectLoadingErrorsHeadlessNotifier
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.InvalidArtifactType
import com.intellij.packaging.impl.artifacts.PlainArtifactType
import com.intellij.packaging.ui.ArtifactEditorContext
import com.intellij.packaging.ui.ArtifactPropertiesEditor
import com.intellij.packaging.ui.PackagingElementPresentation
import com.intellij.testFramework.HeavyPlatformTestCase
import com.intellij.util.ui.EmptyIcon
import java.util.function.Consumer
import javax.swing.Icon
class DynamicArtifactExtensionsLoaderTest : HeavyPlatformTestCase() {
fun `test unload and load artifact type`() {
ProjectLoadingErrorsHeadlessNotifier.setErrorHandler(Consumer {}, testRootDisposable)
val artifactManager = ArtifactManager.getInstance(myProject)
runWithRegisteredExtension(MockArtifactType(), ArtifactType.EP_NAME) {
artifactManager.addArtifact("mock", MockArtifactType.getInstance(), PackagingElementFactory.getInstance().createArtifactRootElement())
}
val invalid = assertOneElement(artifactManager.allArtifactsIncludingInvalid)
assertEquals(InvalidArtifactType.getInstance(), invalid.artifactType)
assertEquals("mock", invalid.name)
registerExtension(MockArtifactType(), ArtifactType.EP_NAME, testRootDisposable)
assertOneElement(artifactManager.allArtifactsIncludingInvalid)
val artifact = assertOneElement(artifactManager.getArtifactsByType(MockArtifactType.getInstance()))
assertEquals("mock", artifact.name)
}
fun `test unload and load packaging element type`() {
val artifactManager = ArtifactManager.getInstance(myProject)
runWithRegisteredExtension(MockPackagingElementType(), PackagingElementType.EP_NAME) {
val root = PackagingElementFactory.getInstance().createArtifactRootElement()
root.addFirstChild(MockPackagingElement().apply { this.state.data = "data" })
artifactManager.addArtifact("mock", PlainArtifactType.getInstance(), root)
}
val invalid = assertOneElement(artifactManager.allArtifactsIncludingInvalid)
assertEquals(InvalidArtifactType.getInstance(), invalid.artifactType)
assertEquals("mock", invalid.name)
registerExtension(MockPackagingElementType(), PackagingElementType.EP_NAME, testRootDisposable)
assertOneElement(artifactManager.allArtifactsIncludingInvalid)
val artifact = assertOneElement(artifactManager.getArtifactsByType(PlainArtifactType.getInstance()))
assertEquals("mock", artifact.name)
assertEquals("data", (artifact.rootElement.children.single() as MockPackagingElement).state.data)
}
fun `test unload and load artifact properties`() {
val artifactManager = ArtifactManager.getInstance(myProject)
runWithRegisteredExtension(MockArtifactPropertiesProvider(), ArtifactPropertiesProvider.EP_NAME) {
val model = artifactManager.createModifiableModel()
val artifact = model.addArtifact("mock", PlainArtifactType.getInstance())
artifact.setProperties(MockArtifactPropertiesProvider.getInstance(), MockArtifactProperties().apply { data = "data" })
runWriteAction { model.commit() }
}
val invalid = assertOneElement(artifactManager.allArtifactsIncludingInvalid)
assertEquals(InvalidArtifactType.getInstance(), invalid.artifactType)
assertEquals("mock", invalid.name)
registerExtension(MockArtifactPropertiesProvider(), ArtifactPropertiesProvider.EP_NAME, testRootDisposable)
assertOneElement(artifactManager.allArtifactsIncludingInvalid)
val artifact = assertOneElement(artifactManager.getArtifactsByType(PlainArtifactType.getInstance()))
assertEquals("mock", artifact.name)
assertEquals("data", (artifact.getProperties(MockArtifactPropertiesProvider.getInstance()) as MockArtifactProperties).data)
}
private inline fun <T> runWithRegisteredExtension(extension: T, extensionPoint: ExtensionPointName<T>, action: () -> Unit) {
val disposable = Disposer.newDisposable()
registerExtension(extension, extensionPoint, disposable)
try {
action()
}
finally {
Disposer.dispose(disposable)
}
}
private fun <T> registerExtension(type: T, extensionPointName: ExtensionPointName<T>, disposable: Disposable) {
val artifactTypeDisposable = Disposer.newDisposable()
Disposer.register(disposable, Disposable {
runWriteAction {
Disposer.dispose(artifactTypeDisposable)
}
})
extensionPointName.getPoint(null).registerExtension(type, artifactTypeDisposable)
}
override fun setUp() {
super.setUp()
ProjectLoadingErrorsHeadlessNotifier.setErrorHandler(Consumer {}, testRootDisposable)
}
}
private class MockArtifactType : ArtifactType("mock", "Mock") {
companion object {
fun getInstance() = EP_NAME.findExtension(MockArtifactType::class.java)!!
}
override fun getIcon(): Icon = EmptyIcon.ICON_16
override fun getDefaultPathFor(kind: PackagingElementOutputKind): String? = ""
override fun createRootElement(artifactName: String): CompositePackagingElement<*> {
return PackagingElementFactory.getInstance().createArtifactRootElement()
}
}
private class MockPackagingElement : PackagingElement<MockPackagingElementState>(PackagingElementType.EP_NAME.findExtensionOrFail(MockPackagingElementType::class.java)) {
private val state: MockPackagingElementState = MockPackagingElementState("")
override fun getState(): MockPackagingElementState = state
override fun loadState(state: MockPackagingElementState) {
this.state.data = state.data
}
override fun isEqualTo(element: PackagingElement<*>): Boolean = (element as? MockPackagingElement)?.state?.data == state.data
override fun createPresentation(context: ArtifactEditorContext): PackagingElementPresentation {
throw UnsupportedOperationException()
}
}
private class MockPackagingElementState(var data: String = "")
private class MockPackagingElementType : PackagingElementType<MockPackagingElement>("mock-element", "Mock Element") {
override fun canCreate(context: ArtifactEditorContext, artifact: Artifact): Boolean = true
override fun chooseAndCreate(context: ArtifactEditorContext,
artifact: Artifact,
parent: CompositePackagingElement<*>): MutableList<out PackagingElement<*>> {
throw UnsupportedOperationException()
}
override fun createEmpty(project: Project): MockPackagingElement {
return MockPackagingElement()
}
}
private class MockArtifactProperties : ArtifactProperties<MockArtifactProperties>() {
var data: String = ""
override fun getState(): MockArtifactProperties? {
return this
}
override fun loadState(state: MockArtifactProperties) {
data = state.data
}
override fun createEditor(context: ArtifactEditorContext): ArtifactPropertiesEditor {
throw UnsupportedOperationException()
}
}
private class MockArtifactPropertiesProvider : ArtifactPropertiesProvider("mock-properties") {
companion object {
fun getInstance(): MockArtifactPropertiesProvider = EP_NAME.findExtensionOrFail(MockArtifactPropertiesProvider::class.java)
}
override fun createProperties(artifactType: ArtifactType): ArtifactProperties<*> = MockArtifactProperties()
}
@@ -9,6 +9,8 @@ import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.extensions.ExtensionPointListener;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.DumbAwareAction;
@@ -27,7 +29,9 @@ import com.intellij.openapi.ui.NamedConfigurable;
import com.intellij.openapi.ui.NonEmptyInputValidator;
import com.intellij.openapi.util.Comparing;
import com.intellij.packaging.artifacts.*;
import com.intellij.packaging.elements.ComplexPackagingElementType;
import com.intellij.packaging.elements.CompositePackagingElement;
import com.intellij.packaging.elements.PackagingElementType;
import com.intellij.packaging.impl.artifacts.ArtifactUtil;
import com.intellij.packaging.impl.artifacts.InvalidArtifact;
import com.intellij.packaging.impl.artifacts.PackagingElementPath;
@@ -39,10 +43,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.*;
public class ArtifactsStructureConfigurable extends BaseStructureConfigurable {
private ArtifactsStructureConfigurableContextImpl myPackagingEditorContext;
@@ -50,6 +51,16 @@ public class ArtifactsStructureConfigurable extends BaseStructureConfigurable {
public ArtifactsStructureConfigurable(@NotNull Project project) {
super(project, new ArtifactStructureConfigurableState());
PackagingElementType.EP_NAME.getPoint(null).addExtensionPointListener(new ExtensionPointListener<PackagingElementType>() {
@Override
public void extensionRemoved(@NotNull PackagingElementType extension, @NotNull PluginDescriptor pluginDescriptor) {
if (extension instanceof ComplexPackagingElementType && myDefaultSettings.getTypesToShowContent().contains(extension)) {
List<ComplexPackagingElementType<?>> updated = new ArrayList<>(myDefaultSettings.getTypesToShowContent());
updated.remove(extension);
myDefaultSettings.setTypesToShowContent(updated);
}
}
}, false, this);
}
@Override
+3 -3
View File
@@ -155,11 +155,11 @@
<with attribute="implementationClass" implements="com.intellij.openapi.roots.ui.OrderRootTypeUIFactory"/>
</extensionPoint>
<extensionPoint qualifiedName="com.intellij.packaging.elementType" interface="com.intellij.packaging.elements.PackagingElementType"/>
<extensionPoint qualifiedName="com.intellij.packaging.artifactPropertiesProvider" interface="com.intellij.packaging.artifacts.ArtifactPropertiesProvider"/>
<extensionPoint qualifiedName="com.intellij.packaging.elementType" interface="com.intellij.packaging.elements.PackagingElementType" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.packaging.artifactPropertiesProvider" interface="com.intellij.packaging.artifacts.ArtifactPropertiesProvider" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.packaging.sourceItemProvider" interface="com.intellij.packaging.ui.PackagingSourceItemsProvider" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.packaging.sourceItemFilter" interface="com.intellij.packaging.ui.PackagingSourceItemFilter" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.packaging.artifactType" interface="com.intellij.packaging.artifacts.ArtifactType"/>
<extensionPoint qualifiedName="com.intellij.packaging.artifactType" interface="com.intellij.packaging.artifacts.ArtifactType" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.frameworkSupport" interface="com.intellij.ide.util.frameworkSupport.FrameworkSupportProvider"
dynamic="true"/>