[platform] make facetType extension point dynamic (IDEA-225619)

GitOrigin-RevId: f5365be5b326b65082f06b14bff1d3fcc810fd30
This commit is contained in:
nik
2020-01-20 08:01:26 +00:00
committed by intellij-monorepo-bot
parent 099d5fcf7c
commit 18141a240d
11 changed files with 314 additions and 47 deletions
@@ -6,6 +6,8 @@ import com.intellij.facet.mock.MockFacetConfiguration;
import com.intellij.facet.mock.MockFacetType;
import com.intellij.facet.mock.MockSubFacetType;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.roots.ProjectModelExternalSource;
import com.intellij.testFramework.HeavyPlatformTestCase;
import org.jetbrains.annotations.Nullable;
@@ -32,12 +34,14 @@ public abstract class FacetTestCase extends HeavyPlatformTestCase {
}
protected void removeAllFacets() {
final FacetManager manager = getFacetManager();
final ModifiableFacetModel model = manager.createModifiableModel();
for (Facet facet : manager.getAllFacets()) {
model.removeFacet(facet);
for (Module module : ModuleManager.getInstance(myProject).getModules()) {
FacetManager manager = FacetManager.getInstance(module);
ModifiableFacetModel model = manager.createModifiableModel();
for (Facet<?> facet : manager.getAllFacets()) {
model.removeFacet(facet);
}
commit(model);
}
commit(model);
}
protected FacetManager getFacetManager() {
@@ -0,0 +1,132 @@
// 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.facet
import com.intellij.facet.impl.invalid.InvalidFacetManager
import com.intellij.facet.mock.MockFacet
import com.intellij.facet.mock.MockFacetConfiguration
import com.intellij.facet.mock.MockFacetType
import com.intellij.facet.mock.MockSubFacetType
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.JDOMUtil
import com.intellij.testFramework.HeavyPlatformTestCase
class FacetTypeUnloadingTest : HeavyPlatformTestCase() {
fun `test unload and load facet`() {
val facetManager = FacetManager.getInstance(module)
runWithRegisteredFacetTypes(MockFacetType()) {
runWriteAction {
val configuration = MockFacetConfiguration().apply {
data = "my data"
}
val model = facetManager.createModifiableModel()
model.addFacet(MockFacet(module, "mock", configuration))
model.commit()
}
assertEquals("mock", facetManager.getFacetsByType(MockFacetType.ID).single().name)
}
assertTrue(facetManager.getFacetsByType(MockFacetType.ID).isEmpty())
val invalidFacet = InvalidFacetManager.getInstance(myProject).invalidFacets.single()
assertEquals("mock", invalidFacet.name)
assertEquals("<configuration data=\"my data\" />", JDOMUtil.write(invalidFacet.configuration.facetState.configuration))
registerFacetType(MockFacetType(), testRootDisposable)
assertTrue(InvalidFacetManager.getInstance(myProject).invalidFacets.isEmpty())
val mockFacet = facetManager.getFacetsByType(MockFacetType.ID).single()
assertEquals("mock", mockFacet.name)
assertEquals("my data", mockFacet.configuration.data)
}
fun `test unload and load facet with sub facets`() {
val facetManager = FacetManager.getInstance(module)
runWithRegisteredFacetTypes(MockFacetType(), MockSubFacetType()) {
runWriteAction {
val model = facetManager.createModifiableModel()
val facet = MockFacet(module, "mock")
model.addFacet(facet)
model.addFacet(MockSubFacetType.getInstance().createFacet(module, "sub-facet", MockFacetConfiguration(), facet))
model.commit()
}
assertEquals("mock", facetManager.getFacetsByType(MockFacetType.ID).single().name)
assertEquals("sub-facet", facetManager.getFacetsByType(MockSubFacetType.ID).single().name)
}
assertTrue(facetManager.getFacetsByType(MockFacetType.ID).isEmpty())
assertTrue(facetManager.getFacetsByType(MockSubFacetType.ID).isEmpty())
val invalidFacet = InvalidFacetManager.getInstance(myProject).invalidFacets.single()
assertEquals("mock", invalidFacet.name)
val subFacetState = invalidFacet.configuration.facetState.subFacets.single()
assertEquals("sub-facet", subFacetState.name)
registerFacetType(MockFacetType(), testRootDisposable)
registerFacetType(MockSubFacetType(), testRootDisposable)
assertTrue(InvalidFacetManager.getInstance(myProject).invalidFacets.isEmpty())
val mockFacet = facetManager.getFacetsByType(MockFacetType.ID).single()
assertEquals("mock", mockFacet.name)
val mockSubFacet = facetManager.getFacetsByType(MockSubFacetType.ID).single()
assertEquals("sub-facet", mockSubFacet.name)
assertSame(mockFacet, mockSubFacet.underlyingFacet)
}
fun `test unload and load sub facet`() {
val facetManager = FacetManager.getInstance(module)
registerFacetType(MockFacetType(), testRootDisposable)
runWithRegisteredFacetTypes(MockSubFacetType()) {
runWriteAction {
val model = facetManager.createModifiableModel()
val facet = MockFacet(module, "mock")
model.addFacet(facet)
model.addFacet(MockSubFacetType.getInstance().createFacet(module, "sub-facet", MockFacetConfiguration(), facet))
model.commit()
}
assertEquals("mock", facetManager.getFacetsByType(MockFacetType.ID).single().name)
assertEquals("sub-facet", facetManager.getFacetsByType(MockSubFacetType.ID).single().name)
}
val mockFacet = facetManager.getFacetsByType(MockFacetType.ID).single()
assertEquals("mock", mockFacet.name)
assertTrue(facetManager.getFacetsByType(MockSubFacetType.ID).isEmpty())
val invalidFacet = InvalidFacetManager.getInstance(myProject).invalidFacets.single()
assertEquals("sub-facet", invalidFacet.name)
assertSame(mockFacet, invalidFacet.underlyingFacet)
registerFacetType(MockSubFacetType(), testRootDisposable)
assertTrue(InvalidFacetManager.getInstance(myProject).invalidFacets.isEmpty())
val mockSubFacet = facetManager.getFacetsByType(MockSubFacetType.ID).single()
assertEquals("sub-facet", mockSubFacet.name)
assertSame(mockFacet, mockSubFacet.underlyingFacet)
}
private inline fun runWithRegisteredFacetTypes(vararg types: FacetType<*, *>, action: () -> Unit) {
val disposable = Disposer.newDisposable()
for (type in types) {
registerFacetType(type, disposable)
}
try {
action()
}
finally {
Disposer.dispose(disposable)
}
}
private fun registerFacetType(type: FacetType<*, *>, disposable: Disposable) {
val facetTypeDisposable = Disposer.newDisposable()
Disposer.register(disposable, Disposable {
runWriteAction {
Disposer.dispose(facetTypeDisposable)
}
})
FacetType.EP_NAME.getPoint(null).registerExtension(type, facetTypeDisposable)
}
override fun setUp() {
super.setUp()
//initialize facet types and register listeners
FacetTypeRegistry.getInstance().facetTypes
}
}
@@ -7,6 +7,8 @@ import com.intellij.facet.ui.DefaultFacetSettingsEditor;
import com.intellij.facet.ui.FacetEditor;
import com.intellij.facet.ui.MultipleFacetSettingsEditor;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.PluginAware;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.project.Project;
@@ -24,13 +26,14 @@ import javax.swing.*;
* &lt;/extensions&gt;
* </pre>
*/
public abstract class FacetType<F extends Facet, C extends FacetConfiguration> {
public abstract class FacetType<F extends Facet, C extends FacetConfiguration> implements PluginAware {
public static final ExtensionPointName<FacetType> EP_NAME = ExtensionPointName.create("com.intellij.facetType");
private final @NotNull FacetTypeId<F> myId;
private final @NotNull String myStringId;
private final @NotNull String myPresentableName;
private final @Nullable FacetTypeId myUnderlyingFacetType;
private PluginDescriptor myPluginDescriptor;
public static <T extends FacetType> T findInstance(Class<T> aClass) {
return EP_NAME.findExtension(aClass);
@@ -90,6 +93,15 @@ public abstract class FacetType<F extends Facet, C extends FacetConfiguration> {
return myUnderlyingFacetType;
}
@Override
public void setPluginDescriptor(@NotNull PluginDescriptor pluginDescriptor) {
myPluginDescriptor = pluginDescriptor;
}
public final PluginDescriptor getPluginDescriptor() {
return myPluginDescriptor;
}
/**
* @deprecated this method is not called by IDE core anymore. Use {@link com.intellij.framework.detection.FrameworkDetector} extension
* to provide automatic detection for facets
@@ -29,6 +29,13 @@ public interface ModifiableFacetModel extends FacetModel {
void addFacet(Facet<?> facet, @Nullable ProjectModelExternalSource externalSource);
void removeFacet(Facet<?> facet);
/**
* Replaces {@code original} facet by {@code replacement}. The only difference with {@code removeFacet(original); addFacet(replacement); }
* is that this method preserves order of facets in internal structures to avoid modifications of *.iml files.
*/
@ApiStatus.Internal
void replaceFacet(@NotNull Facet<?> original, @NotNull Facet<?> replacement);
void rename(Facet<?> facet, String newName);
@Nullable
@@ -38,10 +38,10 @@ public abstract class FacetManagerBase extends FacetManager {
}
@NotNull
private static <F extends Facet<?>, C extends FacetConfiguration> F createFacet(@NotNull Module module, @NotNull FacetType<F, C> type,
@NotNull String name,
@NotNull C configuration,
@Nullable Facet<?> underlying) {
protected static <F extends Facet<?>, C extends FacetConfiguration> F createFacet(@NotNull Module module, @NotNull FacetType<F, C> type,
@NotNull String name,
@NotNull C configuration,
@Nullable Facet<?> underlying) {
final F facet = type.createFacet(module, name, configuration, underlying);
assertTrue(facet.getModule() == module, facet, "module");
assertTrue(facet.getConfiguration() == configuration, facet, "configuration");
@@ -126,17 +126,19 @@ public abstract class FacetManagerBase extends FacetManager {
@NotNull
public static InvalidFacet createInvalidFacet(@NotNull Module module, @NotNull FacetState state, @Nullable Facet<?> underlyingFacet,
@NotNull String errorMessage,
boolean unknownType) {
boolean unknownType, boolean reportError) {
Project project = module.getProject();
final InvalidFacetManager invalidFacetManager = InvalidFacetManager.getInstance(project);
final InvalidFacetType type = InvalidFacetType.getInstance();
final InvalidFacetConfiguration configuration = new InvalidFacetConfiguration(state, errorMessage);
final InvalidFacet facet = createFacet(module, type, StringUtil.notNullize(state.getName()), configuration, underlyingFacet);
if (!invalidFacetManager.isIgnored(facet)) {
FacetLoadingErrorDescription description = new FacetLoadingErrorDescription(facet);
ProjectLoadingErrorsNotifier.getInstance(project).registerError(description);
if (unknownType) {
UnknownFeaturesCollector.getInstance(project).registerUnknownFeature("com.intellij.facetType", state.getFacetType(), "Facet");
if (reportError) {
InvalidFacetManager invalidFacetManager = InvalidFacetManager.getInstance(project);
if (!invalidFacetManager.isIgnored(facet)) {
FacetLoadingErrorDescription description = new FacetLoadingErrorDescription(facet);
ProjectLoadingErrorsNotifier.getInstance(project).registerError(description);
if (unknownType) {
UnknownFeaturesCollector.getInstance(project).registerUnknownFeature("com.intellij.facetType", state.getFacetType(), "Facet");
}
}
}
return facet;
@@ -134,7 +134,7 @@ public final class FacetManagerImpl extends FacetManagerBase implements ModuleCo
ModifiableFacetModel model,
final Facet<?> underlyingFacet,
final String errorMessage, boolean unknownType) {
model.addFacet(createInvalidFacet(getModule(), state, underlyingFacet, errorMessage, unknownType));
model.addFacet(createInvalidFacet(getModule(), state, underlyingFacet, errorMessage, unknownType, true));
}
private <F extends Facet<C>, C extends FacetConfiguration> void addFacet(final FacetType<F, C> type, final FacetState state, final Facet<?> underlyingFacet,
@@ -161,24 +161,39 @@ public final class FacetManagerImpl extends FacetManagerBase implements ModuleCo
}
if (facet == null) {
final C configuration = type.createDefaultConfiguration();
final Element config = state.getConfiguration();
FacetUtil.loadFacetConfiguration(configuration, config);
String name = state.getName();
facet = createFacet(type, name, configuration, underlyingFacet);
if (facet instanceof JDOMExternalizable) {
//todo[nik] remove
((JDOMExternalizable)facet).readExternal(config);
}
String externalSystemId = state.getExternalSystemId();
if (externalSystemId != null) {
facet.setExternalSource(ExternalProjectSystemRegistry.getInstance().getSourceById(externalSystemId));
}
facet = createFacetFromState(getModule(), type, state, underlyingFacet);
model.addFacet(facet);
}
addFacets(state.getSubFacets(), facet, model);
}
@ApiStatus.Internal
@NotNull
public static Facet<?> createFacetFromStateRaw(@NotNull Module module, @NotNull FacetType<?, ?> type, @NotNull FacetState state,
@Nullable Facet<?> underlyingFacet) {
//noinspection unchecked
return createFacetFromState(module, type, state, underlyingFacet);
}
@NotNull
private static <F extends Facet<C>, C extends FacetConfiguration> F createFacetFromState(Module module, FacetType<F, C> type,
FacetState state, Facet<?> underlyingFacet) {
C configuration = type.createDefaultConfiguration();
Element config = state.getConfiguration();
FacetUtil.loadFacetConfiguration(configuration, config);
String name = state.getName();
F facet = createFacet(module, type, name, configuration, underlyingFacet);
if (facet instanceof JDOMExternalizable) {
//todo[nik] remove
((JDOMExternalizable)facet).readExternal(config);
}
String externalSystemId = state.getExternalSystemId();
if (externalSystemId != null) {
facet.setExternalSource(ExternalProjectSystemRegistry.getInstance().getSourceById(externalSystemId));
}
return facet;
}
@Override
public void noStateLoaded() {
doLoadState(null);
@@ -224,12 +239,8 @@ public final class FacetManagerImpl extends FacetManagerBase implements ModuleCo
if (!filter.test(facet)) continue;
final Facet<?> underlyingFacet = facet.getUnderlyingFacet();
FacetState facetState = createFacetState(facet, myModule.getProject());
if (!(facet instanceof InvalidFacet)) {
final Element config = FacetUtil.saveFacetConfiguration(facet);
if (config == null) continue;
facetState.setConfiguration(config);
}
FacetState facetState = saveFacetConfiguration(facet);
if (facetState == null) continue;
getOrCreateTargetFacetList(underlyingFacet, states, myModule.getProject()).add(facetState);
states.put(facet, facetState.getSubFacets());
@@ -237,6 +248,17 @@ public final class FacetManagerImpl extends FacetManagerBase implements ModuleCo
return managerState;
}
@ApiStatus.Internal
public static @Nullable FacetState saveFacetConfiguration(Facet<?> facet) {
FacetState facetState = createFacetState(facet, facet.getModule().getProject());
if (!(facet instanceof InvalidFacet)) {
final Element config = FacetUtil.saveFacetConfiguration(facet);
if (config == null) return null;
facetState.setConfiguration(config);
}
return facetState;
}
/**
* Configuration of some facet may be stored in one file, but configuration of its underlying facet may be stored in another file. For such
* sub-facets we create parent elements which don't store configuration but only name and type.
@@ -72,6 +72,15 @@ public class FacetModelImpl extends FacetModelBase implements ModifiableFacetMod
facetsChanged();
}
@Override
public void replaceFacet(@NotNull Facet<?> original, @NotNull Facet<?> replacement) {
int index = myFacets.indexOf(original);
if (index != -1) {
myFacets.set(index, replacement);
facetsChanged();
}
}
@Override
public void rename(final Facet<?> facet, final String newName) {
if (!newName.equals(facet.getName())) {
@@ -2,9 +2,16 @@
package com.intellij.facet.impl
import com.intellij.facet.*
import com.intellij.facet.impl.invalid.InvalidFacetManager
import com.intellij.facet.impl.invalid.InvalidFacetType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.ExtensionPointListener
import com.intellij.openapi.extensions.PluginDescriptor
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import org.jetbrains.jps.model.serialization.facet.FacetState
import java.util.*
class FacetTypeRegistryImpl : FacetTypeRegistry() {
@@ -23,13 +30,72 @@ class FacetTypeRegistryImpl : FacetTypeRegistry() {
myTypeIds[id] = typeId
}
private fun loadInvalidFacetsOfType(project: Project, facetType: FacetType<*, *>) {
val modulesWithFacets = InvalidFacetManager.getInstance(project).invalidFacets
.filter { it.configuration.facetState.facetType == facetType.stringId }
.mapTo(HashSet()) { it.module }
for (module in modulesWithFacets) {
val model = FacetManager.getInstance(module).createModifiableModel()
val invalidFacets = model.getFacetsByType(InvalidFacetType.TYPE_ID).filter { it.configuration.facetState.facetType == facetType.stringId }
for (invalidFacet in invalidFacets) {
val newFacet = FacetManagerImpl.createFacetFromStateRaw(module, facetType, invalidFacet.configuration.facetState,
invalidFacet.underlyingFacet)
model.replaceFacet(invalidFacet, newFacet)
for (subFacet in invalidFacet.configuration.facetState.subFacets) {
model.addFacet(FacetManagerBase.createInvalidFacet(module, subFacet, newFacet, invalidFacet.configuration.errorMessage, false, false))
}
}
model.commit()
}
}
@Synchronized
override fun unregisterFacetType(facetType: FacetType<*, *>) {
val id = facetType.id
val stringId = facetType.stringId
LOG.assertTrue(myFacetTypes.remove(id) != null, "Facet type '$stringId' is not registered")
myFacetTypes.remove(id)
myTypeIds.remove(stringId)
try {
ProjectManager.getInstance().openProjects.forEach {
convertFacetsToInvalid(it, facetType)
}
}
finally {
val id = facetType.id
val stringId = facetType.stringId
LOG.assertTrue(myFacetTypes.remove(id) != null, "Facet type '$stringId' is not registered")
myFacetTypes.remove(id)
myTypeIds.remove(stringId)
}
}
private fun convertFacetsToInvalid(project: Project, facetType: FacetType<*, *>) {
val modulesWithFacets = ProjectFacetManager.getInstance(project).getFacets(facetType.id).mapTo(HashSet()) { it.module }
for (module in modulesWithFacets) {
val model = FacetManager.getInstance(module).createModifiableModel()
val subFacets = model.allFacets.groupBy { it.underlyingFacet }
val facets = model.getFacetsByType(facetType.id)
for (facet in facets) {
val facetState = saveFacetWithSubFacets(facet, subFacets) ?: continue
val pluginName = facetType.pluginDescriptor?.name?.let { " '$it'" } ?: ""
val errorMessage = "Plugin$pluginName which provides support for '${facetType.presentableName}' facets is unloaded"
val reportError = !ApplicationManager.getApplication().isUnitTestMode
val invalidFacet = FacetManagerBase.createInvalidFacet(module, facetState, facet.underlyingFacet, errorMessage, true, reportError)
removeAllSubFacets(model, facet, subFacets)
model.replaceFacet(facet, invalidFacet)
}
model.commit()
}
}
private fun removeAllSubFacets(model: ModifiableFacetModel, facet: Facet<*>, subFacets: Map<Facet<*>, List<Facet<*>>>) {
val toRemove = subFacets[facet] ?: return
for (subFacet in toRemove) {
removeAllSubFacets(model, subFacet, subFacets)
model.removeFacet(subFacet)
}
}
private fun saveFacetWithSubFacets(facet: Facet<*>?, subFacets: Map<Facet<FacetConfiguration>, List<Facet<*>>>): FacetState? {
val state = FacetManagerImpl.saveFacetConfiguration(facet) ?: return null
(subFacets[facet] ?: emptyList()).mapNotNullTo(state.subFacets) { saveFacetWithSubFacets(it, subFacets) }
return state
}
@Synchronized
@@ -72,18 +138,26 @@ class FacetTypeRegistryImpl : FacetTypeRegistry() {
if (myExtensionsLoaded) {
return
}
FacetType.EP_NAME.getPoint(null).addExtensionPointListener(
FacetType.EP_NAME.forEachExtensionSafe {
registerFacetType(it)
}
myExtensionsLoaded = true
FacetType.EP_NAME.addExtensionPointListener(
object : ExtensionPointListener<FacetType<*, *>?> {
override fun extensionAdded(extension: FacetType<*, *>, pluginDescriptor: PluginDescriptor) {
registerFacetType(extension)
runWriteAction {
ProjectManager.getInstance().openProjects.forEach {
loadInvalidFacetsOfType(it, extension)
}
}
}
override fun extensionRemoved(extension: FacetType<*, *>,
pluginDescriptor: PluginDescriptor) {
unregisterFacetType(extension)
}
}, true, null)
myExtensionsLoaded = true
}, null)
}
companion object {
@@ -422,7 +422,7 @@
<extensionPoint name="projectTemplateParameterFactory" interface="com.intellij.ide.util.projectWizard.ProjectTemplateParameterFactory"/>
<extensionPoint name="projectTemplateFileProcessor" interface="com.intellij.ide.util.projectWizard.ProjectTemplateFileProcessor"/>
<extensionPoint name="facetType" interface="com.intellij.facet.FacetType"/>
<extensionPoint name="facetType" interface="com.intellij.facet.FacetType" dynamic="true"/>
<extensionPoint name="facet.toolWindow" beanClass="com.intellij.facet.ui.FacetDependentToolWindow">
<with attribute="factoryClass" implements="com.intellij.openapi.wm.ToolWindowFactory"/>
@@ -74,7 +74,7 @@ internal open class FacetModelViaWorkspaceModel(protected val legacyBridgeModule
name = entity.name
setFacetType(entity.facetType)
configuration = entity.configurationXmlTag?.let { JDOMUtil.load(it) }
}, underlyingFacet, ProjectBundle.message("error.message.unknown.facet.type.0", entity.facetType), true)
}, underlyingFacet, ProjectBundle.message("error.message.unknown.facet.type.0", entity.facetType), true, true)
}
val configuration = facetType.createDefaultConfiguration()
@@ -47,6 +47,11 @@ internal class ModifiableFacetModelViaWorkspaceModel(private val initialStorage:
facetsChanged()
}
override fun replaceFacet(original: Facet<*>, replacement: Facet<*>) {
removeFacet(original)
addFacet(replacement)
}
private fun removeFacetEntityWithSubFacets(entity: FacetEntity) {
if (entity !in entityToFacet) return