[platform] make facetType extension point dynamic (IDEA-225619)
authornik <Nikolay.Chashnikov@jetbrains.com>
Mon, 20 Jan 2020 07:54:35 +0000 (10:54 +0300)
committerintellij-monorepo-bot <intellij-monorepo-bot-no-reply@jetbrains.com>
Mon, 20 Jan 2020 08:01:26 +0000 (08:01 +0000)
GitOrigin-RevId: f5365be5b326b65082f06b14bff1d3fcc810fd30

java/idea-ui/testSrc/com/intellij/facet/FacetTestCase.java
java/idea-ui/testSrc/com/intellij/facet/FacetTypeUnloadingTest.kt [new file with mode: 0644]
platform/lang-api/src/com/intellij/facet/FacetType.java
platform/lang-api/src/com/intellij/facet/ModifiableFacetModel.java
platform/lang-impl/src/com/intellij/facet/FacetManagerBase.java
platform/lang-impl/src/com/intellij/facet/FacetManagerImpl.java
platform/lang-impl/src/com/intellij/facet/impl/FacetModelImpl.java
platform/lang-impl/src/com/intellij/facet/impl/FacetTypeRegistryImpl.kt
platform/platform-resources/src/META-INF/LangExtensionPoints.xml
platform/workspaceModel-ide/src/com/intellij/workspace/legacyBridge/facet/FacetManagerViaWorkspaceModel.kt
platform/workspaceModel-ide/src/com/intellij/workspace/legacyBridge/facet/ModifiableFacetModelViaWorkspaceModel.kt

index ead801ae81be40ea9ccf49d7bd7d652713883b27..6d9c321ae7cb7a92cd6c22d23bd295b62adfee87 100644 (file)
@@ -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() {
diff --git a/java/idea-ui/testSrc/com/intellij/facet/FacetTypeUnloadingTest.kt b/java/idea-ui/testSrc/com/intellij/facet/FacetTypeUnloadingTest.kt
new file mode 100644 (file)
index 0000000..08b035c
--- /dev/null
@@ -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
+  }
+}
\ No newline at end of file
index ed96360a5aa043de6913062762b3a046d4233c31..a0bea301bc79348088c84d05e0726919895fbf77 100644 (file)
@@ -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
index aabd7c4a099d4bcf9bed8eac9f22bef450fac90c..0944f436bebcc9685d2abcfa25883e7aee00168f 100644 (file)
@@ -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
index ad32997d9ea5fb3ddcf3418cfdcc27a6070cf278..5b42667ce1c5e5632d49c6ca90504a6ea182072a 100644 (file)
@@ -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;
index 5d4e4d208f55f2a6159e530ca6293c7d938e4831..4905e204bd56f10fe2249b47ccc9eba0d82f4384 100644 (file)
@@ -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.
index c205fcfd2eac65504e8cad23f30e6b0b56189c74..3735970905154a8708b4603515d7c2ef8094b539 100644 (file)
@@ -73,6 +73,15 @@ public class FacetModelImpl extends FacetModelBase implements ModifiableFacetMod
   }
 
   @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())) {
       myFacet2NewName.put(facet, newName);
index 816cea5657e9f18fc0f20c3974898bb3c9ab635d..b5c75cc311aa3564938749b05eb1abc985c2c88b 100644 (file)
@@ -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 {
index e1c771ddb1fabf0b0ef9bc7e2a95415bc11aa72f..e48d3d284298ebe1536434628bb72a0473c1fa46 100644 (file)
     <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"/>
index fdc2cdcec3129064052dd2cc6e0601a56dba4490..a1b7bb7f01ad9ef25bb217fd6c47b1ff6017fa0e 100644 (file)
@@ -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()
index b684f4973a34e8faa602d588093a762760c19ebc..b20b76771e7b4e4d9b0546c81b211d5089e1bfe1 100644 (file)
@@ -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