diff --git a/java/java-tests/intellij.java.tests.iml b/java/java-tests/intellij.java.tests.iml index 1f69a4579d0e..957b2aefbbbe 100644 --- a/java/java-tests/intellij.java.tests.iml +++ b/java/java-tests/intellij.java.tests.iml @@ -71,5 +71,7 @@ + + \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/roots/ModuleRootsExternalizationTest.java b/java/java-tests/testSrc/com/intellij/roots/ModuleRootsExternalizationTest.java index 4dd04a7c4d4e..42a595e46af3 100644 --- a/java/java-tests/testSrc/com/intellij/roots/ModuleRootsExternalizationTest.java +++ b/java/java-tests/testSrc/com/intellij/roots/ModuleRootsExternalizationTest.java @@ -4,6 +4,7 @@ package com.intellij.roots; import com.intellij.application.options.ReplacePathToMacroMap; import com.intellij.configurationStore.StoreUtil; import com.intellij.ide.highlighter.ModuleFileType; +import com.intellij.jps.impl.JpsIdePluginManagerImpl; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ExpandMacroToPathMap; import com.intellij.openapi.module.Module; @@ -20,10 +21,20 @@ import com.intellij.testFramework.JavaModuleTestCase; import com.intellij.testFramework.PsiTestUtil; import org.jdom.Element; import org.jdom.JDOMException; +import org.jetbrains.jps.model.JpsElement; +import org.jetbrains.jps.model.java.JavaSourceRootProperties; +import org.jetbrains.jps.model.java.JavaSourceRootType; +import org.jetbrains.jps.model.java.JpsJavaExtensionService; +import org.jetbrains.jps.model.module.JpsModuleSourceRootType; +import org.jetbrains.jps.model.module.UnknownSourceRootType; +import org.jetbrains.jps.model.module.UnknownSourceRootTypeProperties; +import org.jetbrains.jps.model.serialization.JpsModelSerializerExtension; +import org.jetbrains.jps.model.serialization.module.JpsModuleSourceRootPropertiesSerializer; import java.io.File; import java.io.IOException; -import java.util.Iterator; +import java.util.*; +import java.util.stream.Collectors; import static com.intellij.testFramework.assertions.Assertions.assertThat; @@ -140,6 +151,81 @@ public class ModuleRootsExternalizationTest extends JavaModuleTestCase { ); } + public void testChangeRootType() throws JDOMException, IOException { + File content = new File(getProject().getBasePath()); + File source = new File(content, "source"); + File testSource = new File(content, "testSource"); + FileUtil.createDirectory(source); + FileUtil.createDirectory(testSource); + final VirtualFile contentFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(content); + assertNotNull(contentFile); + refreshRecursively(contentFile); + final VirtualFile sourceFile = LocalFileSystem.getInstance().findFileByIoFile(source); + assertNotNull(sourceFile); + final VirtualFile testSourceFile = LocalFileSystem.getInstance().findFileByIoFile(testSource); + assertNotNull(testSourceFile); + + final File moduleFile = new File(content, "test.iml"); + final Module module = createModule(moduleFile); + + PsiTestUtil.addContentRoot(module, contentFile); + PsiTestUtil.addSourceRoot( + module, sourceFile, JavaSourceRootType.SOURCE, JpsJavaExtensionService.getInstance().createSourceRootProperties("org.jetbrains", true) + ); + PsiTestUtil.addSourceRoot( + module, testSourceFile, JavaSourceRootType.TEST_SOURCE, JpsJavaExtensionService.getInstance().createSourceRootProperties("org.jetbrains", false) + ); + + StoreUtil.saveDocumentsAndProjectSettings(myProject); + + String expectedXml = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + assertEquals(expectedXml, JDOMUtil.writeElement(JDOMUtil.load(moduleFile).getChild("component"))); + + JpsIdePluginManagerImpl.replaceWithUnknownRootType(myProject, findSerializers(Arrays.asList(JavaSourceRootType.SOURCE, JavaSourceRootType.TEST_SOURCE))); + for (ContentEntry entry : ModuleRootManager.getInstance(module).getContentEntries()) { + for (SourceFolder folder : entry.getSourceFolders()) { + assertTrue("Root type expected to be 'Unknown' for " + folder.getUrl(), folder.getRootType() instanceof UnknownSourceRootType); + JpsElement properties = folder.getJpsElement().getProperties(folder.getRootType()); + assertTrue(properties instanceof UnknownSourceRootTypeProperties); + assertTrue(((UnknownSourceRootTypeProperties)properties).getPropertiesData() instanceof Element); + } + } + StoreUtil.saveSettings(myProject, true); + assertEquals(expectedXml, JDOMUtil.writeElement(JDOMUtil.load(moduleFile).getChild("component"))); + + JpsIdePluginManagerImpl.updateCustomRootTypes(myProject, findSerializers(Arrays.asList(JavaSourceRootType.SOURCE, JavaSourceRootType.TEST_SOURCE))); + for (ContentEntry entry : ModuleRootManager.getInstance(module).getContentEntries()) { + for (SourceFolder folder : entry.getSourceFolders()) { + assertFalse("'Unknown' root type is not expected: " + folder.getUrl(), folder.getRootType() instanceof UnknownSourceRootType); + JpsElement properties = folder.getJpsElement().getProperties(folder.getRootType()); + assertTrue(properties instanceof JavaSourceRootProperties); + assertEquals("org.jetbrains", ((JavaSourceRootProperties)properties).getPackagePrefix()); + } + } + StoreUtil.saveSettings(myProject, true); + assertEquals(expectedXml, JDOMUtil.writeElement(JDOMUtil.load(moduleFile).getChild("component"))); + } + + + private static Collection> findSerializers(Collection> rootTypes) { + final Set> typesSet = rootTypes instanceof Set ? (Set>)rootTypes : new HashSet<>(rootTypes); + Set> result = new HashSet<>(); + for (JpsModelSerializerExtension ext : JpsModelSerializerExtension.getExtensions()) { + result.addAll( + ext.getModuleSourceRootPropertiesSerializers().stream().filter(serializer -> typesSet.contains(serializer.getType())).collect(Collectors.toSet()) + ); + } + return result; + } + public void testModuleLibraries() throws IOException, JDOMException { File moduleFile = new File(myProject.getBasePath(), "test.iml"); Module module = createModule(moduleFile); @@ -284,4 +370,32 @@ public class ModuleRootsExternalizationTest extends JavaModuleTestCase { final String substituted = map.substitute(path, false); assertEquals(path, substituted); } + + //public void testLoadRegisteredSaveUnknown() { + // + // Element loadedXml = new Element(JpsModuleRootModelSerializer.SOURCE_FOLDER_TAG); + // loadedXml.setAttribute(JpsModuleRootModelSerializer.PACKAGE_PREFIX_ATTRIBUTE, "package-prefix"); + // loadedXml.setAttribute(JpsJavaModelSerializerExtension.IS_GENERATED_ATTRIBUTE, Boolean.TRUE.toString()); + // + // JpsElementFactory.getInstance().createModuleSourceRoot("file://someFile", JavaSourceRootType.SOURCE, JpsJavaExtensionService.getInstance().createSourceRootProperties("package_prefix", true)); + // + // JpsModuleSourceRootPropertiesSerializer javaSerializer = null; + // for (JpsModelSerializerExtension ext : JpsModelSerializerExtension.getExtensions()) { + // javaSerializer = ext.getModuleSourceRootPropertiesSerializers().stream().filter(serializer -> JavaSourceRootType.SOURCE.equals(serializer.getType())).findFirst().orElse(null); + // if (javaSerializer != null) { + // break; + // } + // } + // assertNotNull(javaSerializer); + // + // //JavaSourceRootProperties properties = JpsJavaExtensionService.getInstance().createSourceRootProperties("package_prefix", true); + // + // JpsElement javaProperties = javaSerializer.loadProperties(loadedXml); + // + // UnknownSourceRootPropertiesSerializer unknownTypeSerializer = + // UnknownSourceRootPropertiesSerializer.forType(javaSerializer.getTypeId(), JavaSourceRootType.SOURCE.isForTests()); + // + // Element savedXml = new Element(JpsModuleRootModelSerializer.SOURCE_FOLDER_TAG); + // //unknownTypeSerializer.saveProperties(); + //} } diff --git a/java/java-tests/testSrc/com/intellij/roots/SourceRootPropertiesTest.java b/java/java-tests/testSrc/com/intellij/roots/SourceRootPropertiesTest.java new file mode 100644 index 000000000000..462e1b4cd499 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/roots/SourceRootPropertiesTest.java @@ -0,0 +1,9 @@ +// 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.roots; + +import junit.framework.TestCase; + +public class SourceRootPropertiesTest extends TestCase { + + +} diff --git a/jps/model-api/src/org/jetbrains/jps/model/module/UnknownSourceRootType.java b/jps/model-api/src/org/jetbrains/jps/model/module/UnknownSourceRootType.java new file mode 100644 index 000000000000..191694795168 --- /dev/null +++ b/jps/model-api/src/org/jetbrains/jps/model/module/UnknownSourceRootType.java @@ -0,0 +1,38 @@ +// 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 org.jetbrains.jps.model.module; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.model.ex.JpsElementTypeBase; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class UnknownSourceRootType extends JpsElementTypeBase> implements JpsModuleSourceRootType> { + private static final Map ourTypeNameToInstanceMap = new ConcurrentHashMap<>(); + private final String myUnknownTypeId; + private final boolean myForTests; + + private UnknownSourceRootType(String unknownTypeId, boolean forTests) { + myUnknownTypeId = unknownTypeId; + myForTests = forTests; + } + + public String getUnknownTypeId() { + return myUnknownTypeId; + } + + @Override + public boolean isForTests() { + return myForTests; + } + + @NotNull + @Override + public UnknownSourceRootTypeProperties createDefaultProperties() { + return new UnknownSourceRootTypeProperties<>(null); + } + + public static UnknownSourceRootType getInstance(String typeId, boolean forTests) { + return ourTypeNameToInstanceMap.computeIfAbsent((forTests? "tst:" : "src:") + typeId, id -> new UnknownSourceRootType(typeId, forTests)); + } +} diff --git a/jps/model-api/src/org/jetbrains/jps/model/module/UnknownSourceRootTypeProperties.java b/jps/model-api/src/org/jetbrains/jps/model/module/UnknownSourceRootTypeProperties.java new file mode 100644 index 000000000000..aacb376d64d6 --- /dev/null +++ b/jps/model-api/src/org/jetbrains/jps/model/module/UnknownSourceRootTypeProperties.java @@ -0,0 +1,31 @@ +// 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 org.jetbrains.jps.model.module; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.model.ex.JpsElementBase; + +public class UnknownSourceRootTypeProperties extends JpsElementBase> { + @Nullable + private final Data myPropertiesData; + + public UnknownSourceRootTypeProperties(@Nullable Data propertiesData) { + myPropertiesData = propertiesData; + } + + @Nullable + public Data getPropertiesData() { + return myPropertiesData; + } + + @NotNull + @Override + public UnknownSourceRootTypeProperties createCopy() { + return new UnknownSourceRootTypeProperties<>(myPropertiesData); + } + + @Override + public void applyChanges(@NotNull UnknownSourceRootTypeProperties modified) { + // not supported + } +} diff --git a/jps/model-impl/src/org/jetbrains/jps/plugin/JpsPluginManager.java b/jps/model-impl/src/org/jetbrains/jps/plugin/JpsPluginManager.java index e21a807f1d72..0b6f582464e4 100644 --- a/jps/model-impl/src/org/jetbrains/jps/plugin/JpsPluginManager.java +++ b/jps/model-impl/src/org/jetbrains/jps/plugin/JpsPluginManager.java @@ -28,4 +28,6 @@ public abstract class JpsPluginManager { @NotNull public abstract Collection loadExtensions(@NotNull Class extensionClass); + + public abstract int getModificationStamp(); } diff --git a/jps/model-impl/src/org/jetbrains/jps/service/impl/JpsServiceManagerImpl.java b/jps/model-impl/src/org/jetbrains/jps/service/impl/JpsServiceManagerImpl.java index 4a23cc861832..8a0aa6cc5b2a 100644 --- a/jps/model-impl/src/org/jetbrains/jps/service/impl/JpsServiceManagerImpl.java +++ b/jps/model-impl/src/org/jetbrains/jps/service/impl/JpsServiceManagerImpl.java @@ -24,10 +24,12 @@ import org.jetbrains.jps.service.JpsServiceManager; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; public class JpsServiceManagerImpl extends JpsServiceManager { private final ConcurrentMap myServices = new ConcurrentHashMap<>(16, 0.75f, 1); private final ConcurrentMap> myExtensions = new ConcurrentHashMap<>(16, 0.75f, 1); + private final AtomicInteger myModificationStamp = new AtomicInteger(0); private volatile JpsPluginManager myPluginManager; @Override @@ -62,7 +64,7 @@ public class JpsServiceManagerImpl extends JpsServiceManager { @Override public Iterable getExtensions(Class extensionClass) { - List cached = myExtensions.get(extensionClass); + List cached = cleanupExtensionCache()? null : myExtensions.get(extensionClass); if (cached == null) { // confine costly service initialization to single thread for defined startup profile synchronized (myExtensions) { @@ -80,6 +82,18 @@ public class JpsServiceManagerImpl extends JpsServiceManager { return (List)cached; } + private boolean cleanupExtensionCache() { + JpsPluginManager manager = myPluginManager; + if (manager != null) { + int stamp = manager.getModificationStamp(); + if (myModificationStamp.getAndSet(stamp) != stamp) { + myExtensions.clear(); + return true; + } + } + return false; + } + @NotNull private Collection loadExtensions(Class extensionClass) { JpsPluginManager pluginManager = myPluginManager; @@ -115,5 +129,10 @@ public class JpsServiceManagerImpl extends JpsServiceManager { ServiceLoader loader = ServiceLoader.load(extensionClass, extensionClass.getClassLoader()); return ContainerUtil.newArrayList(loader); } + + @Override + public int getModificationStamp() { + return 0; + } } } diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/module/JpsModuleRootModelSerializer.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/module/JpsModuleRootModelSerializer.java index 76c8e7f9854e..e1fba4fecd75 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/module/JpsModuleRootModelSerializer.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/module/JpsModuleRootModelSerializer.java @@ -31,7 +31,6 @@ import org.jetbrains.jps.model.library.sdk.JpsSdkType; import org.jetbrains.jps.model.module.*; import org.jetbrains.jps.model.serialization.JpsModelSerializerExtension; import org.jetbrains.jps.model.serialization.impl.JpsSerializationFormatException; -import org.jetbrains.jps.model.serialization.java.JpsJavaModelSerializerExtension; import org.jetbrains.jps.model.serialization.library.JpsLibraryTableSerializer; import org.jetbrains.jps.model.serialization.library.JpsSdkTableSerializer; @@ -178,8 +177,9 @@ public class JpsModuleRootModelSerializer { @NotNull private static JpsModuleSourceRootPropertiesSerializer getSourceRootPropertiesSerializer(@NotNull Element sourceElement) { String typeAttribute = sourceElement.getAttributeValue(SOURCE_ROOT_TYPE_ATTRIBUTE); + boolean forTests = Boolean.parseBoolean(sourceElement.getAttributeValue(IS_TEST_SOURCE_ATTRIBUTE)); if (typeAttribute == null) { - typeAttribute = Boolean.parseBoolean(sourceElement.getAttributeValue(IS_TEST_SOURCE_ATTRIBUTE)) ? JAVA_TEST_ROOT_TYPE_ID : JAVA_SOURCE_ROOT_TYPE_ID; + typeAttribute = forTests ? JAVA_TEST_ROOT_TYPE_ID : JAVA_SOURCE_ROOT_TYPE_ID; } for (JpsModelSerializerExtension extension : JpsModelSerializerExtension.getExtensions()) { for (JpsModuleSourceRootPropertiesSerializer serializer : extension.getModuleSourceRootPropertiesSerializers()) { @@ -189,7 +189,7 @@ public class JpsModuleRootModelSerializer { } } LOG.warn("Unknown module source root type " + typeAttribute); - return JpsJavaModelSerializerExtension.JAVA_SOURCE_ROOT_PROPERTIES_SERIALIZER; + return UnknownSourceRootPropertiesSerializer.forType(UnknownSourceRootType.getInstance(typeAttribute, forTests)); } public static void saveRootModel(JpsModule module, Element rootModelElement) { @@ -287,6 +287,9 @@ public class JpsModuleRootModelSerializer { @Nullable private static

JpsModuleSourceRootPropertiesSerializer

getSerializer(JpsModuleSourceRootType

type) { + if (type instanceof UnknownSourceRootType) { + return (JpsModuleSourceRootPropertiesSerializer

)UnknownSourceRootPropertiesSerializer.forType((UnknownSourceRootType)type); + } for (JpsModelSerializerExtension extension : JpsModelSerializerExtension.getExtensions()) { for (JpsModuleSourceRootPropertiesSerializer serializer : extension.getModuleSourceRootPropertiesSerializers()) { if (serializer.getType().equals(type)) { diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/module/UnknownSourceRootPropertiesSerializer.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/module/UnknownSourceRootPropertiesSerializer.java new file mode 100644 index 000000000000..1eccc5f860fc --- /dev/null +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/module/UnknownSourceRootPropertiesSerializer.java @@ -0,0 +1,36 @@ +// 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 org.jetbrains.jps.model.serialization.module; + +import com.intellij.openapi.util.JDOMUtil; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.model.module.UnknownSourceRootType; +import org.jetbrains.jps.model.module.UnknownSourceRootTypeProperties; + +public class UnknownSourceRootPropertiesSerializer extends JpsModuleSourceRootPropertiesSerializer> { + public UnknownSourceRootPropertiesSerializer(UnknownSourceRootType type) { + super(type, type.getUnknownTypeId()); + } + + @Override + public UnknownSourceRootTypeProperties loadProperties(@NotNull Element sourceRootTag) { + return new UnknownSourceRootTypeProperties<>(sourceRootTag.getParent() != null? sourceRootTag.clone() : sourceRootTag); + } + + @Override + public void saveProperties(@NotNull UnknownSourceRootTypeProperties properties, @NotNull Element sourceRootTag) { + Object data = properties.getPropertiesData(); + if (data instanceof Element) { + JDOMUtil.copyMissingContent((Element)data, sourceRootTag); + } + } + + public static UnknownSourceRootPropertiesSerializer forType(String unknownTypeId, boolean isTests) { + return forType(UnknownSourceRootType.getInstance(unknownTypeId, isTests)); + } + + @NotNull + public static UnknownSourceRootPropertiesSerializer forType(UnknownSourceRootType type) { + return new UnknownSourceRootPropertiesSerializer(type); + } +} diff --git a/platform/platform-impl/src/com/intellij/jps/impl/JpsIdePluginManagerImpl.java b/platform/platform-impl/src/com/intellij/jps/impl/JpsIdePluginManagerImpl.java index 6a32247cc42a..52bc551d9246 100644 --- a/platform/platform-impl/src/com/intellij/jps/impl/JpsIdePluginManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/jps/impl/JpsIdePluginManagerImpl.java @@ -2,8 +2,26 @@ package com.intellij.jps.impl; import com.intellij.openapi.extensions.*; +import com.intellij.openapi.module.Module; +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.ModuleRootModificationUtil; +import com.intellij.openapi.roots.SourceFolder; +import com.intellij.openapi.util.Ref; import com.intellij.util.containers.ContainerUtil; +import org.jdom.Element; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.model.JpsElement; +import org.jetbrains.jps.model.module.JpsModuleSourceRootType; +import org.jetbrains.jps.model.module.UnknownSourceRootType; +import org.jetbrains.jps.model.module.UnknownSourceRootTypeProperties; +import org.jetbrains.jps.model.serialization.JpsModelSerializerExtension; +import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer; +import org.jetbrains.jps.model.serialization.module.JpsModuleSourceRootPropertiesSerializer; +import org.jetbrains.jps.model.serialization.module.UnknownSourceRootPropertiesSerializer; import org.jetbrains.jps.plugin.JpsPluginManager; import java.io.BufferedReader; @@ -13,9 +31,11 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; public final class JpsIdePluginManagerImpl extends JpsPluginManager { private final List myExternalBuildPlugins = new CopyOnWriteArrayList<>(); + private final AtomicInteger myModificationStamp = new AtomicInteger(0); public JpsIdePluginManagerImpl() { ExtensionsArea rootArea = Extensions.getRootArea(); @@ -25,30 +45,191 @@ public final class JpsIdePluginManagerImpl extends JpsPluginManager { //todo[nik] get rid of this check: currently this class is used in intellij.platform.jps.build tests instead of JpsPluginManagerImpl because intellij.platform.ide.impl module is added to classpath via testFramework if (rootArea.hasExtensionPoint(JpsPluginBean.EP_NAME)) { + final Ref initial = new Ref<>(Boolean.TRUE); JpsPluginBean.EP_NAME.getPoint(null).addExtensionPointListener(new ExtensionPointListener() { @Override public void extensionAdded(@NotNull JpsPluginBean extension, @NotNull PluginDescriptor pluginDescriptor) { - ContainerUtil.addIfNotNull(myExternalBuildPlugins, pluginDescriptor); + if (initial.get()) { + myExternalBuildPlugins.add(pluginDescriptor); + } + else { + handlePluginAdded(pluginDescriptor); + } + } + + @Override + public void extensionRemoved(@NotNull JpsPluginBean extension, @NotNull PluginDescriptor pluginDescriptor) { + handlePluginRemoved(pluginDescriptor); } }, true, null); + initial.set(Boolean.FALSE); } if (rootArea.hasExtensionPoint("com.intellij.compileServer.plugin")) { ExtensionPoint extensionPoint = rootArea.getExtensionPoint("com.intellij.compileServer.plugin"); + final Ref initial = new Ref<>(Boolean.TRUE); //noinspection unchecked extensionPoint.addExtensionPointListener(new ExtensionPointListener() { @Override public void extensionAdded(@NotNull Object extension, @NotNull PluginDescriptor pluginDescriptor) { - ContainerUtil.addIfNotNull(myExternalBuildPlugins, pluginDescriptor); + if (initial.get()) { + myExternalBuildPlugins.add(pluginDescriptor); + } + else { + handlePluginAdded(pluginDescriptor); + } + } + + @Override + public void extensionRemoved(@NotNull Object extension, @NotNull PluginDescriptor pluginDescriptor) { + handlePluginRemoved(pluginDescriptor); } }, true, null); + initial.set(Boolean.FALSE); } } + private void handlePluginRemoved(@NotNull PluginDescriptor pluginDescriptor) { + Map, JpsModuleSourceRootPropertiesSerializer> removed = new HashMap<>(); + for (JpsModelSerializerExtension extension : loadExtensions(JpsModelSerializerExtension.class)) { + for (JpsModuleSourceRootPropertiesSerializer serializer : extension.getModuleSourceRootPropertiesSerializers()) { + removed.put(serializer.getType(), serializer); + } + } + + if (myExternalBuildPlugins.remove(pluginDescriptor)) { + myModificationStamp.incrementAndGet(); + } + else{ + return; + } + + for (JpsModelSerializerExtension extension : loadExtensions(JpsModelSerializerExtension.class)) { + for (JpsModuleSourceRootPropertiesSerializer serializer : extension.getModuleSourceRootPropertiesSerializers()) { + removed.remove(serializer.getType()); + } + } + + if (!removed.isEmpty()) { + for (Project project : ProjectManager.getInstance().getOpenProjects()) { + replaceWithUnknownRootType(project, removed.values()); + } + } + } + + private void handlePluginAdded(@NotNull PluginDescriptor pluginDescriptor) { + if (myExternalBuildPlugins.contains(pluginDescriptor)) { + return; + } + Set before = new HashSet<>(); + for (JpsModelSerializerExtension extension : loadExtensions(JpsModelSerializerExtension.class)) { + for (JpsModuleSourceRootPropertiesSerializer serializer : extension.getModuleSourceRootPropertiesSerializers()) { + before.add(serializer.getTypeId()); + } + } + + myExternalBuildPlugins.add(pluginDescriptor); + myModificationStamp.incrementAndGet(); + + Map> added = new HashMap<>(); + for (JpsModelSerializerExtension extension : loadExtensions(JpsModelSerializerExtension.class)) { + for (JpsModuleSourceRootPropertiesSerializer serializer : extension.getModuleSourceRootPropertiesSerializers()) { + added.put(serializer.getTypeId(), serializer); + } + } + added.keySet().removeAll(before); + + if (!added.isEmpty()) { + for (Project project : ProjectManager.getInstance().getOpenProjects()) { + updateCustomRootTypes(project, added.values()); + } + } + } + + public static void replaceWithUnknownRootType(Project project, Collection> unregisteredSerializers) { + if (unregisteredSerializers.isEmpty()) { + return; + } + Map, JpsModuleSourceRootPropertiesSerializer> serializers = new HashMap<>(); + for (JpsModuleSourceRootPropertiesSerializer serializer : unregisteredSerializers) { + serializers.put(serializer.getType(), serializer); + } + for (Module module : ModuleManager.getInstance(project).getModules()) { + ModuleRootModificationUtil.modifyModel(module, model -> { + boolean shouldCommit = false; + for (ContentEntry contentEntry : model.getContentEntries()) { + for (SourceFolder folder : contentEntry.getSourceFolders()) { + JpsModuleSourceRootPropertiesSerializer removedSerializer = serializers.get(folder.getRootType()); + if (removedSerializer != null) { + changeType( + folder, + UnknownSourceRootPropertiesSerializer.forType(removedSerializer.getTypeId(), folder.getRootType().isForTests()), + serializeProperties(folder, removedSerializer) + ); + shouldCommit = true; + } + } + } + return shouldCommit; + }); + } + } + + public static void updateCustomRootTypes(Project project, Collection> registeredSerializers) { + if (registeredSerializers.isEmpty()) { + return; + } + Map> serializers = new HashMap<>(); + for (JpsModuleSourceRootPropertiesSerializer ser : registeredSerializers) { + 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; + } + } + } + } + return shouldCommit; + }); + } + } + + @Nullable + private static

Element serializeProperties(SourceFolder root, @NotNull JpsModuleSourceRootPropertiesSerializer

serializer) { + P properties = root.getJpsElement().getProperties(serializer.getType()); + if (properties != null) { + Element sourceElement = new Element(JpsModuleRootModelSerializer.SOURCE_FOLDER_TAG); + serializer.saveProperties(properties, sourceElement); + return sourceElement; + } + return null; + } + + private static

void changeType(SourceFolder root, @NotNull JpsModuleSourceRootPropertiesSerializer

serializer, @Nullable Element serializedProps) { + root.changeType( + serializer.getType(), + serializedProps != null ? serializer.loadProperties(serializedProps) : serializer.getType().createDefaultProperties() + ); + } + + @Override + public int getModificationStamp() { + return myModificationStamp.get(); + } + @NotNull @Override public Collection loadExtensions(@NotNull Class extensionClass) { - String resourceName = "META-INF/services/" + extensionClass.getName(); - Set> classes = new LinkedHashSet<>(); Set loaders = new LinkedHashSet<>(); for (PluginDescriptor plugin : myExternalBuildPlugins) { ContainerUtil.addIfNotNull(loaders, plugin.getPluginClassLoader()); @@ -56,7 +237,16 @@ public final class JpsIdePluginManagerImpl extends JpsPluginManager { if (loaders.isEmpty()) { loaders.add(getClass().getClassLoader()); } + return loadExtensionsFrom(loaders, extensionClass); + } + @NotNull + private static Collection loadExtensionsFrom(@NotNull Collection loaders, @NotNull Class extensionClass) { + if (loaders.isEmpty()) { + return Collections.emptyList(); + } + String resourceName = "META-INF/services/" + extensionClass.getName(); + Set> classes = new LinkedHashSet<>(); Set loadedUrls = new HashSet<>(); for (ClassLoader loader : loaders) { try { diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index 1423a722f67a..846cd713b05a 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -12,7 +12,7 @@ + beanClass="com.intellij.jps.impl.JpsPluginBean" dynamic="true"/> task) { + modifyModel(module, model -> { + task.consume(model); + return Boolean.TRUE; + }); + } + + public static void modifyModel(@NotNull Module module, @NotNull Function modifier) { ModifiableRootModel model = ReadAction.compute(() -> ModuleRootManager.getInstance(module).getModifiableModel()); try { - task.consume(model); - - ApplicationManager.getApplication().invokeAndWait(() -> { - if (module.isDisposed()) return; - WriteAction.run(model::commit); - }); + if (modifier.apply(model)) { + ApplicationManager.getApplication().invokeAndWait(() -> { + if (!module.isDisposed()) { + WriteAction.run(model::commit); + } + }); + } } finally { if (!model.isDisposed()) { diff --git a/platform/projectModel-api/src/com/intellij/openapi/roots/SourceFolder.java b/platform/projectModel-api/src/com/intellij/openapi/roots/SourceFolder.java index 8506f623bbdb..9d73f6d6b7dc 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/roots/SourceFolder.java +++ b/platform/projectModel-api/src/com/intellij/openapi/roots/SourceFolder.java @@ -17,6 +17,7 @@ package com.intellij.openapi.roots; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.model.JpsElement; import org.jetbrains.jps.model.module.JpsModuleSourceRoot; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; @@ -55,4 +56,6 @@ public interface SourceFolder extends ContentFolder { @NotNull JpsModuleSourceRoot getJpsElement(); + +

void changeType(JpsModuleSourceRootType

newType, P properties); } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/SourceFolderImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/SourceFolderImpl.java index eda7df3e0c1b..da061fed2b67 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/SourceFolderImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/SourceFolderImpl.java @@ -14,7 +14,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.JpsElement; import org.jetbrains.jps.model.JpsElementFactory; -import org.jetbrains.jps.model.java.*; +import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; +import org.jetbrains.jps.model.java.JavaResourceRootProperties; +import org.jetbrains.jps.model.java.JavaSourceRootProperties; import org.jetbrains.jps.model.module.JpsModuleSourceRoot; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; import org.jetbrains.jps.model.module.JpsTypedModuleSourceRoot; @@ -25,7 +27,7 @@ import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer */ @ApiStatus.Internal public class SourceFolderImpl extends ContentFolderBaseImpl implements SourceFolder, ClonableContentFolder { - private final JpsModuleSourceRoot myJpsElement; + private JpsModuleSourceRoot myJpsElement; @NonNls public static final String ELEMENT_NAME = JpsModuleRootModelSerializer.SOURCE_FOLDER_TAG; @NonNls public static final String TEST_SOURCE_ATTR = JpsModuleRootModelSerializer.IS_TEST_SOURCE_ATTRIBUTE; static final String DEFAULT_PACKAGE_PREFIX = ""; @@ -112,6 +114,11 @@ public class SourceFolderImpl extends ContentFolderBaseImpl implements SourceFol return myJpsElement; } + @Override + public

void changeType(JpsModuleSourceRootType

newType, P properties) { + myJpsElement = JpsElementFactory.getInstance().createModuleSourceRoot(myJpsElement.getUrl(), newType, properties); + } + private boolean isForGeneratedSources() { JavaSourceRootProperties properties = getJavaProperties(); JavaResourceRootProperties resourceJavaProperties = getResourceJavaProperties(); diff --git a/platform/projectModel-impl/src/com/intellij/project/model/impl/module/content/JpsSourceFolder.java b/platform/projectModel-impl/src/com/intellij/project/model/impl/module/content/JpsSourceFolder.java index e25a8085eae6..328d83e74ff5 100644 --- a/platform/projectModel-impl/src/com/intellij/project/model/impl/module/content/JpsSourceFolder.java +++ b/platform/projectModel-impl/src/com/intellij/project/model/impl/module/content/JpsSourceFolder.java @@ -18,6 +18,8 @@ package com.intellij.project.model.impl.module.content; import com.intellij.openapi.roots.SourceFolder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.model.JpsElement; +import org.jetbrains.jps.model.JpsElementFactory; import org.jetbrains.jps.model.JpsSimpleElement; import org.jetbrains.jps.model.java.JavaSourceRootProperties; import org.jetbrains.jps.model.java.JavaSourceRootType; @@ -25,13 +27,15 @@ import org.jetbrains.jps.model.module.JpsModuleSourceRoot; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; public class JpsSourceFolder extends JpsContentFolderBase implements SourceFolder { - private final JpsModuleSourceRoot mySourceRoot; + @NotNull + private JpsModuleSourceRoot mySourceRoot; - public JpsSourceFolder(JpsModuleSourceRoot sourceRoot, JpsContentEntry contentEntry) { + public JpsSourceFolder(@NotNull JpsModuleSourceRoot sourceRoot, JpsContentEntry contentEntry) { super(sourceRoot.getUrl(), contentEntry); mySourceRoot = sourceRoot; } + @NotNull public JpsModuleSourceRoot getSourceRoot() { return mySourceRoot; } @@ -78,4 +82,9 @@ public class JpsSourceFolder extends JpsContentFolderBase implements SourceFolde public JpsModuleSourceRoot getJpsElement() { return mySourceRoot; } + + @Override + public

void changeType(JpsModuleSourceRootType

newType, P properties) { + mySourceRoot = JpsElementFactory.getInstance().createModuleSourceRoot(mySourceRoot.getUrl(), newType, properties); + } } diff --git a/platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/LegacyBridgeModuleLibraryTest.kt b/platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/LegacyBridgeModuleLibraryTest.kt index 71fb4d06bb79..0d78231cc415 100644 --- a/platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/LegacyBridgeModuleLibraryTest.kt +++ b/platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/LegacyBridgeModuleLibraryTest.kt @@ -171,7 +171,7 @@ class LegacyBridgeModuleLibraryTest { StoreUtil.saveDocumentsAndProjectSettings(project) val template = "\$MODULE_DIR\$" - assertTrue(moduleFile.readText().contains(""" + assertTrue(moduleFile.readText().replace("\r\n", "\n").contains(""" diff --git a/platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/LegacyBridgeModulesTest.kt b/platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/LegacyBridgeModulesTest.kt index 387778b83e85..00a9e319ee9c 100644 --- a/platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/LegacyBridgeModulesTest.kt +++ b/platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/LegacyBridgeModulesTest.kt @@ -2,11 +2,13 @@ package com.intellij.workspace.jps import com.intellij.configurationStore.StoreUtil import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.WriteAction import com.intellij.openapi.application.invokeAndWaitIfNeeded import com.intellij.openapi.application.runWriteActionAndWait import com.intellij.openapi.command.WriteCommandAction -import com.intellij.openapi.module.* +import com.intellij.openapi.module.EmptyModuleType +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.module.ModuleType +import com.intellij.openapi.module.ModuleTypeId import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.project.ex.ProjectManagerEx @@ -28,9 +30,9 @@ import com.intellij.workspace.api.* import com.intellij.workspace.ide.* import com.intellij.workspace.legacyBridge.intellij.LegacyBridgeModule import com.intellij.workspace.toVirtualFileUrl -import org.jetbrains.jps.model.java.JavaSourceRootProperties -import org.jetbrains.jps.model.java.JavaSourceRootType import org.jetbrains.jps.model.java.LanguageLevel +import org.jetbrains.jps.model.module.UnknownSourceRootType +import org.jetbrains.jps.model.module.UnknownSourceRootTypeProperties import org.jetbrains.jps.model.serialization.JDomSerializationUtil import org.jetbrains.jps.model.serialization.library.JpsLibraryTableSerializer import org.junit.Assert.* @@ -517,8 +519,8 @@ class LegacyBridgeModulesTest { val contentEntry = ModuleRootManager.getInstance(module).contentEntries.single() val sourceFolder = contentEntry.sourceFolders.single() - assertSame(JavaSourceRootType.SOURCE, sourceFolder.rootType) - assertTrue(sourceFolder.jpsElement.properties is JavaSourceRootProperties) + assertSame(UnknownSourceRootType.getInstance("unsupported-custom-source-root-type", false), sourceFolder.rootType) + assertTrue(sourceFolder.jpsElement.properties is UnknownSourceRootTypeProperties<*>) val customRoot = WorkspaceModel.getInstance(project).entityStore.current.entities(CustomSourceRootPropertiesEntity::class.java) .toList().single() diff --git a/platform/workspaceModel-ide/src/com/intellij/workspace/legacyBridge/roots/LegacyBridgeModifiableContentEntryImpl.kt b/platform/workspaceModel-ide/src/com/intellij/workspace/legacyBridge/roots/LegacyBridgeModifiableContentEntryImpl.kt index fe360eb909fe..aabce82d5087 100644 --- a/platform/workspaceModel-ide/src/com/intellij/workspace/legacyBridge/roots/LegacyBridgeModifiableContentEntryImpl.kt +++ b/platform/workspaceModel-ide/src/com/intellij/workspace/legacyBridge/roots/LegacyBridgeModifiableContentEntryImpl.kt @@ -23,9 +23,11 @@ import org.jetbrains.jps.model.java.JavaResourceRootProperties import org.jetbrains.jps.model.java.JavaSourceRootProperties import org.jetbrains.jps.model.java.JavaSourceRootType import org.jetbrains.jps.model.module.JpsModuleSourceRootType +import org.jetbrains.jps.model.module.UnknownSourceRootType import org.jetbrains.jps.model.serialization.JpsModelSerializerExtension import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer import org.jetbrains.jps.model.serialization.module.JpsModuleSourceRootPropertiesSerializer +import org.jetbrains.jps.model.serialization.module.UnknownSourceRootPropertiesSerializer internal class LegacyBridgeModifiableContentEntryImpl( private val diff: TypedEntityStorageDiffBuilder, @@ -53,10 +55,12 @@ internal class LegacyBridgeModifiableContentEntryImpl( } @Suppress("UNCHECKED_CAST") - val serializer: JpsModuleSourceRootPropertiesSerializer

= (JpsModelSerializerExtension.getExtensions() + val serializer: JpsModuleSourceRootPropertiesSerializer

= if (type is UnknownSourceRootType) + UnknownSourceRootPropertiesSerializer.forType(type as UnknownSourceRootType) as JpsModuleSourceRootPropertiesSerializer

+ else + (JpsModelSerializerExtension.getExtensions() .flatMap { it.moduleSourceRootPropertiesSerializers } - .firstOrNull { it.type == type }) as? JpsModuleSourceRootPropertiesSerializer

- ?: error("Module source root type $type is not registered as JpsModelSerializerExtension") + .firstOrNull { it.type == type }) as? JpsModuleSourceRootPropertiesSerializer

?: error("Module source root type $type is not registered as JpsModelSerializerExtension") val entitySource = currentContentEntry.value.entity.entitySource val sourceRootEntity = diff.addSourceRootEntity( diff --git a/platform/workspaceModel-ide/src/com/intellij/workspace/legacyBridge/typedModel/module/ContentFolderViaTypedEntity.kt b/platform/workspaceModel-ide/src/com/intellij/workspace/legacyBridge/typedModel/module/ContentFolderViaTypedEntity.kt index e2b39595f4cb..ba4df923afb5 100644 --- a/platform/workspaceModel-ide/src/com/intellij/workspace/legacyBridge/typedModel/module/ContentFolderViaTypedEntity.kt +++ b/platform/workspaceModel-ide/src/com/intellij/workspace/legacyBridge/typedModel/module/ContentFolderViaTypedEntity.kt @@ -10,15 +10,16 @@ import com.intellij.workspace.api.* import com.intellij.workspace.legacyBridge.intellij.LegacyBridgeFilePointerScope import org.jetbrains.jps.model.JpsElement import org.jetbrains.jps.model.JpsElementFactory -import org.jetbrains.jps.model.java.JavaSourceRootType import org.jetbrains.jps.model.java.JpsJavaExtensionService import org.jetbrains.jps.model.module.JpsModuleSourceRoot import org.jetbrains.jps.model.module.JpsModuleSourceRootType +import org.jetbrains.jps.model.module.UnknownSourceRootType import org.jetbrains.jps.model.serialization.JpsModelSerializerExtension import org.jetbrains.jps.model.serialization.java.JpsJavaModelSerializerExtension import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer.SOURCE_ROOT_TYPE_ATTRIBUTE import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer.URL_ATTRIBUTE +import org.jetbrains.jps.model.serialization.module.UnknownSourceRootPropertiesSerializer internal abstract class ContentFolderViaTypedEntity(private val entry: ContentEntryViaTypedEntity, private val contentFolderUrl: VirtualFileUrl) : ContentFolder { override fun getContentEntry(): ContentEntryViaTypedEntity = entry @@ -34,7 +35,7 @@ internal class SourceFolderViaTypedEntity(private val entry: ContentEntryViaType override fun getFile(): VirtualFile? = entry.model.filePointerProvider.getAndCacheFilePointer(sourceRootEntity.url, LegacyBridgeFilePointerScope.SourceRoot).file private var packagePrefixVar: String? = null - private val sourceRootType: JpsModuleSourceRootType<*> by lazy { getSourceRootType(sourceRootEntity.rootType) } + private var sourceRootType: JpsModuleSourceRootType = getSourceRootType(sourceRootEntity) override fun getRootType() = sourceRootType override fun isTestSource() = sourceRootEntity.tests @@ -71,7 +72,10 @@ internal class SourceFolderViaTypedEntity(private val entry: ContentEntryViaType val customSourceRoot = sourceRootEntity.asCustomSourceRoot() ?: return elementFactory.createDummyElement() if (customSourceRoot.propertiesXmlTag.isEmpty()) return rootType.createDefaultProperties() - val serializer = JpsModelSerializerExtension.getExtensions() + val serializer = if (rootType is UnknownSourceRootType) + UnknownSourceRootPropertiesSerializer.forType(rootType as UnknownSourceRootType) + else + JpsModelSerializerExtension.getExtensions () .flatMap { it.moduleSourceRootPropertiesSerializers } .firstOrNull { it.type == rootType } if (serializer == null) { @@ -91,6 +95,10 @@ internal class SourceFolderViaTypedEntity(private val entry: ContentEntryViaType } } + override fun

changeType(newType: JpsModuleSourceRootType

, properties: P) { + sourceRootType = newType + } + override fun hashCode() = entry.url.hashCode() override fun equals(other: Any?): Boolean { if (other !is SourceFolderViaTypedEntity) return false @@ -142,13 +150,13 @@ internal class SourceFolderViaTypedEntity(private val entry: ContentEntryViaType packagePrefixVar = packagePrefix } - private fun getSourceRootType(rootType: String): JpsModuleSourceRootType<*> { + private fun getSourceRootType(entity: SourceRootEntity): JpsModuleSourceRootType { JpsModelSerializerExtension.getExtensions().forEach { extensions -> extensions.moduleSourceRootPropertiesSerializers.forEach { - if (it.typeId == rootType) return it.type + if (it.typeId == entity.rootType) return it.type } } - return JavaSourceRootType.SOURCE + return UnknownSourceRootType.getInstance(entity.rootType, entity.tests) } companion object {