[workspace model] support loading and unloading plugins defining custom module root types

Since properties of custom source roots are stored in xml format in the workspace model entities it's enough to just drop caches if type is changed. However it's also required to unregister plugin which provides root type after root types are changed to 'unknown' (otherwise when ModifiableRootModel is created it'll already use UnknownSourceRootType for these roots), and collect affected roots before creating ModifiableRootModel when a new root type is registered.

GitOrigin-RevId: b39e769a1a42b5119d3b7fff61156f7e9bf19882
This commit is contained in:
Nikolay Chashnikov
2020-05-26 12:32:55 +03:00
committed by intellij-monorepo-bot
parent 82ee0cc693
commit c570e228df
4 changed files with 53 additions and 28 deletions
@@ -7,8 +7,10 @@ import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.roots.ContentEntry;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.roots.SourceFolder;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.util.containers.ContainerUtil;
import org.jdom.Element;
@@ -32,6 +34,7 @@ import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
public final class JpsIdePluginManagerImpl extends JpsPluginManager {
private final List<PluginDescriptor> myExternalBuildPlugins = new CopyOnWriteArrayList<>();
@@ -89,6 +92,8 @@ public final class JpsIdePluginManagerImpl extends JpsPluginManager {
}
private void handlePluginRemoved(@NotNull PluginDescriptor pluginDescriptor) {
if (!myExternalBuildPlugins.contains(pluginDescriptor)) return;
Map<JpsModuleSourceRootType<?>, JpsModuleSourceRootPropertiesSerializer<?>> removed = new HashMap<>();
for (JpsModelSerializerExtension extension : loadExtensions(JpsModelSerializerExtension.class)) {
for (JpsModuleSourceRootPropertiesSerializer<?> serializer : extension.getModuleSourceRootPropertiesSerializers()) {
@@ -96,14 +101,7 @@ public final class JpsIdePluginManagerImpl extends JpsPluginManager {
}
}
if (myExternalBuildPlugins.remove(pluginDescriptor)) {
myModificationStamp.incrementAndGet();
}
else{
return;
}
for (JpsModelSerializerExtension extension : loadExtensions(JpsModelSerializerExtension.class)) {
for (JpsModelSerializerExtension extension : loadExtensions(JpsModelSerializerExtension.class, descriptor -> !descriptor.equals(pluginDescriptor))) {
for (JpsModuleSourceRootPropertiesSerializer<?> serializer : extension.getModuleSourceRootPropertiesSerializers()) {
removed.remove(serializer.getType());
}
@@ -114,6 +112,8 @@ public final class JpsIdePluginManagerImpl extends JpsPluginManager {
replaceWithUnknownRootType(project, removed.values());
}
}
myExternalBuildPlugins.remove(pluginDescriptor);
myModificationStamp.incrementAndGet();
}
private void handlePluginAdded(@NotNull PluginDescriptor pluginDescriptor) {
@@ -183,24 +183,32 @@ public final class JpsIdePluginManagerImpl extends JpsPluginManager {
serializers.put(ser.getTypeId(), ser);
}
for (Module module : ModuleManager.getInstance(project).getModules()) {
ModuleRootModificationUtil.modifyModel(module, model -> {
boolean shouldCommit = false;
for (ContentEntry contentEntry : model.getContentEntries()) {
for (SourceFolder folder : contentEntry.getSourceFolders()) {
if (folder.getRootType() instanceof UnknownSourceRootType) {
UnknownSourceRootType type = (UnknownSourceRootType)folder.getRootType();
JpsModuleSourceRootPropertiesSerializer<?> serializer = serializers.get(type.getUnknownTypeId());
if (serializer != null) {
UnknownSourceRootTypeProperties<?> properties = folder.getJpsElement().getProperties(type);
Object data = properties != null? properties.getPropertiesData() : null;
changeType(folder, serializer, data instanceof Element ? (Element)data : null);
shouldCommit = true;
}
Map<SourceFolder, Pair<JpsModuleSourceRootPropertiesSerializer<?>, Element>> foldersToUpdate = new HashMap<>();
for (ContentEntry contentEntry : ModuleRootManager.getInstance(module).getContentEntries()) {
for (SourceFolder folder : contentEntry.getSourceFolders()) {
if (folder.getRootType() instanceof UnknownSourceRootType) {
UnknownSourceRootType type = (UnknownSourceRootType)folder.getRootType();
JpsModuleSourceRootPropertiesSerializer<?> serializer = serializers.get(type.getUnknownTypeId());
if (serializer != null) {
UnknownSourceRootTypeProperties<?> properties = folder.getJpsElement().getProperties(type);
Object data = properties != null ? properties.getPropertiesData() : null;
foldersToUpdate.put(folder, new Pair<>(serializer, data instanceof Element ? (Element)data : null));
}
}
}
return shouldCommit;
});
}
if (!foldersToUpdate.isEmpty()) {
ModuleRootModificationUtil.updateModel(module, model -> {
for (ContentEntry contentEntry : model.getContentEntries()) {
for (SourceFolder folder : contentEntry.getSourceFolders()) {
Pair<JpsModuleSourceRootPropertiesSerializer<?>, Element> pair = foldersToUpdate.get(folder);
if (pair != null) {
changeType(folder, pair.first, pair.second);
}
}
}
});
}
}
}
@@ -230,9 +238,16 @@ public final class JpsIdePluginManagerImpl extends JpsPluginManager {
@NotNull
@Override
public <T> Collection<T> loadExtensions(@NotNull Class<T> extensionClass) {
return loadExtensions(extensionClass, null);
}
@NotNull
private <T> Collection<T> loadExtensions(@NotNull Class<T> extensionClass, @Nullable Predicate<PluginDescriptor> filter) {
Set<ClassLoader> loaders = new LinkedHashSet<>();
for (PluginDescriptor plugin : myExternalBuildPlugins) {
ContainerUtil.addIfNotNull(loaders, plugin.getPluginClassLoader());
if (filter == null || filter.test(plugin)) {
ContainerUtil.addIfNotNull(loaders, plugin.getPluginClassLoader());
}
}
if (loaders.isEmpty()) {
loaders.add(getClass().getClassLoader());
@@ -57,5 +57,10 @@ public interface SourceFolder extends ContentFolder {
@NotNull
JpsModuleSourceRoot getJpsElement();
/**
* This method is used internally to change root type to 'unknown' and back when the plugin which provides the custom root type is
* unloaded or loader. It isn't intended to change root type to some other arbitrary type and must not be used in plugins.
*/
@ApiStatus.Internal
<P extends JpsElement> void changeType(JpsModuleSourceRootType<P> newType, P properties);
}
@@ -53,11 +53,11 @@ class LegacyBridgeModuleRootComponent(
init {
MODULE_EXTENSION_NAME.getPoint(legacyBridgeModule).addExtensionPointListener(object : ExtensionPointListener<ModuleExtension?> {
override fun extensionAdded(extension: ModuleExtension, pluginDescriptor: PluginDescriptor) {
modelValue.dropCache()
dropRootModelCache()
}
override fun extensionRemoved(extension: ModuleExtension, pluginDescriptor: PluginDescriptor) {
modelValue.dropCache()
dropRootModelCache()
}
}, false, null)
}
@@ -75,6 +75,10 @@ class LegacyBridgeModuleRootComponent(
override fun dropCaches() {
orderRootsCache.clearCache()
dropRootModelCache()
}
internal fun dropRootModelCache() {
modelValue.dropCache()
}
@@ -3,12 +3,14 @@ package com.intellij.workspace.legacyBridge.typedModel.module
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.roots.ContentFolder
import com.intellij.openapi.roots.ExcludeFolder
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.SourceFolder
import com.intellij.openapi.util.JDOMUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.workspace.api.*
import com.intellij.workspace.legacyBridge.intellij.LegacyBridgeFilePointerProvider
import com.intellij.workspace.legacyBridge.intellij.LegacyBridgeFilePointerScope
import com.intellij.workspace.legacyBridge.intellij.LegacyBridgeModuleRootComponent
import org.jetbrains.jps.model.JpsElement
import org.jetbrains.jps.model.JpsElementFactory
import org.jetbrains.jps.model.java.JpsJavaExtensionService
@@ -100,7 +102,7 @@ internal class SourceFolderViaTypedEntity(private val entry: ContentEntryViaType
}
override fun <P : JpsElement> changeType(newType: JpsModuleSourceRootType<P>, properties: P) {
sourceRootType = newType
(ModuleRootManager.getInstance(contentEntry.rootModel.module) as LegacyBridgeModuleRootComponent).dropRootModelCache()
}
override fun hashCode() = entry.url.hashCode()
@@ -140,7 +142,6 @@ internal class SourceFolderViaTypedEntity(private val entry: ContentEntryViaType
if (javaResourceRoot != null) return
updater { diff ->
// TODO Replace TempEntitySource with sourceRootEntity.source
diff.addJavaSourceRootEntity(sourceRootEntity, false, packagePrefix, sourceRootEntity.entitySource)
}
} else {