replace custom source root types to a special 'unknown' type and back on plugin unload/load (IDEA-235292)

GitOrigin-RevId: 19abeea379ba8786646362b91c8599ba7cd3a40a
This commit is contained in:
Eugene Zhuravlev
2020-05-21 18:19:33 +03:00
committed by intellij-monorepo-bot
parent 37ff8bba7b
commit 10c788f05e
19 changed files with 522 additions and 36 deletions
+2
View File
@@ -71,5 +71,7 @@
<orderEntry type="module" module-name="intellij.platform.statistics" scope="TEST" />
<orderEntry type="library" scope="TEST" name="jaxb-api" level="project" />
<orderEntry type="library" name="fastutil-min" level="project" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.jps.model.impl" />
</component>
</module>
@@ -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 = "<component name=\"NewModuleRootManager\" inherit-compiler-output=\"true\">\n" +
" <exclude-output />\n" +
" <content url=\"file://$MODULE_DIR$\">\n" +
" <sourceFolder url=\"file://$MODULE_DIR$/source\" isTestSource=\"false\" packagePrefix=\"org.jetbrains\" generated=\"true\" />\n" +
" <sourceFolder url=\"file://$MODULE_DIR$/testSource\" isTestSource=\"true\" packagePrefix=\"org.jetbrains\" />\n" +
" </content>\n" +
" <orderEntry type=\"sourceFolder\" forTests=\"false\" />\n" +
"</component>";
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<JpsModuleSourceRootPropertiesSerializer<?>> findSerializers(Collection<JpsModuleSourceRootType<?>> rootTypes) {
final Set<JpsModuleSourceRootType<?>> typesSet = rootTypes instanceof Set ? (Set<JpsModuleSourceRootType<?>>)rootTypes : new HashSet<>(rootTypes);
Set<JpsModuleSourceRootPropertiesSerializer<?>> 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<? extends JpsElement> 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();
//}
}
@@ -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 {
}
@@ -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<UnknownSourceRootTypeProperties<?>> implements JpsModuleSourceRootType<UnknownSourceRootTypeProperties<?>> {
private static final Map<String, UnknownSourceRootType> 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));
}
}
@@ -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<Data> extends JpsElementBase<UnknownSourceRootTypeProperties<Data>> {
@Nullable
private final Data myPropertiesData;
public UnknownSourceRootTypeProperties(@Nullable Data propertiesData) {
myPropertiesData = propertiesData;
}
@Nullable
public Data getPropertiesData() {
return myPropertiesData;
}
@NotNull
@Override
public UnknownSourceRootTypeProperties<Data> createCopy() {
return new UnknownSourceRootTypeProperties<>(myPropertiesData);
}
@Override
public void applyChanges(@NotNull UnknownSourceRootTypeProperties modified) {
// not supported
}
}
@@ -28,4 +28,6 @@ public abstract class JpsPluginManager {
@NotNull
public abstract <T> Collection<T> loadExtensions(@NotNull Class<T> extensionClass);
public abstract int getModificationStamp();
}
@@ -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<Class, Object> myServices = new ConcurrentHashMap<>(16, 0.75f, 1);
private final ConcurrentMap<Class, List<?>> 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 <T> Iterable<T> getExtensions(Class<T> 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<T>)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 <T> Collection<T> loadExtensions(Class<T> extensionClass) {
JpsPluginManager pluginManager = myPluginManager;
@@ -115,5 +129,10 @@ public class JpsServiceManagerImpl extends JpsServiceManager {
ServiceLoader<T> loader = ServiceLoader.load(extensionClass, extensionClass.getClassLoader());
return ContainerUtil.newArrayList(loader);
}
@Override
public int getModificationStamp() {
return 0;
}
}
}
@@ -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 <P extends JpsElement> JpsModuleSourceRootPropertiesSerializer<P> getSerializer(JpsModuleSourceRootType<P> type) {
if (type instanceof UnknownSourceRootType) {
return (JpsModuleSourceRootPropertiesSerializer<P>)UnknownSourceRootPropertiesSerializer.forType((UnknownSourceRootType)type);
}
for (JpsModelSerializerExtension extension : JpsModelSerializerExtension.getExtensions()) {
for (JpsModuleSourceRootPropertiesSerializer<?> serializer : extension.getModuleSourceRootPropertiesSerializers()) {
if (serializer.getType().equals(type)) {
@@ -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<UnknownSourceRootTypeProperties<?>> {
public UnknownSourceRootPropertiesSerializer(UnknownSourceRootType type) {
super(type, type.getUnknownTypeId());
}
@Override
public UnknownSourceRootTypeProperties<Element> 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);
}
}
@@ -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<PluginDescriptor> 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<Boolean> initial = new Ref<>(Boolean.TRUE);
JpsPluginBean.EP_NAME.getPoint(null).addExtensionPointListener(new ExtensionPointListener<JpsPluginBean>() {
@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<Boolean> 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<JpsModuleSourceRootType<?>, 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<String> before = new HashSet<>();
for (JpsModelSerializerExtension extension : loadExtensions(JpsModelSerializerExtension.class)) {
for (JpsModuleSourceRootPropertiesSerializer<?> serializer : extension.getModuleSourceRootPropertiesSerializers()) {
before.add(serializer.getTypeId());
}
}
myExternalBuildPlugins.add(pluginDescriptor);
myModificationStamp.incrementAndGet();
Map<String, JpsModuleSourceRootPropertiesSerializer<?>> 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<JpsModuleSourceRootPropertiesSerializer<?>> unregisteredSerializers) {
if (unregisteredSerializers.isEmpty()) {
return;
}
Map<JpsModuleSourceRootType<?>, 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<JpsModuleSourceRootPropertiesSerializer<?>> registeredSerializers) {
if (registeredSerializers.isEmpty()) {
return;
}
Map<String, JpsModuleSourceRootPropertiesSerializer<?>> 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 <P extends JpsElement> Element serializeProperties(SourceFolder root, @NotNull JpsModuleSourceRootPropertiesSerializer<P> 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 <P extends JpsElement> void changeType(SourceFolder root, @NotNull JpsModuleSourceRootPropertiesSerializer<P> 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 <T> Collection<T> loadExtensions(@NotNull Class<T> extensionClass) {
String resourceName = "META-INF/services/" + extensionClass.getName();
Set<Class<T>> classes = new LinkedHashSet<>();
Set<ClassLoader> 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 <T> Collection<T> loadExtensionsFrom(@NotNull Collection<ClassLoader> loaders, @NotNull Class<T> extensionClass) {
if (loaders.isEmpty()) {
return Collections.emptyList();
}
String resourceName = "META-INF/services/" + extensionClass.getName();
Set<Class<T>> classes = new LinkedHashSet<>();
Set<String> loadedUrls = new HashSet<>();
for (ClassLoader loader : loaders) {
try {
@@ -12,7 +12,7 @@
<extensionPoint name="errorHandler" interface="com.intellij.openapi.diagnostic.ErrorReportSubmitter" dynamic="true"/>
<extensionPoint name="jps.plugin"
beanClass="com.intellij.jps.impl.JpsPluginBean"/>
beanClass="com.intellij.jps.impl.JpsPluginBean" dynamic="true"/>
<extensionPoint name="preloadingActivity"
interface="com.intellij.openapi.application.PreloadingActivity"
@@ -31,6 +31,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
public class ModuleRootModificationUtil {
public static void addContentRoot(@NotNull Module module, @NotNull String path) {
@@ -135,14 +136,22 @@ public class ModuleRootModificationUtil {
}
public static void updateModel(@NotNull Module module, @NotNull Consumer<? super ModifiableRootModel> task) {
modifyModel(module, model -> {
task.consume(model);
return Boolean.TRUE;
});
}
public static void modifyModel(@NotNull Module module, @NotNull Function<? super ModifiableRootModel, Boolean> 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()) {
@@ -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();
<P extends JpsElement> void changeType(JpsModuleSourceRootType<P> newType, P properties);
}
@@ -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 <P extends JpsElement> void changeType(JpsModuleSourceRootType<P> newType, P properties) {
myJpsElement = JpsElementFactory.getInstance().createModuleSourceRoot(myJpsElement.getUrl(), newType, properties);
}
private boolean isForGeneratedSources() {
JavaSourceRootProperties properties = getJavaProperties();
JavaResourceRootProperties resourceJavaProperties = getResourceJavaProperties();
@@ -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 <P extends JpsElement> void changeType(JpsModuleSourceRootType<P> newType, P properties) {
mySourceRoot = JpsElementFactory.getInstance().createModuleSourceRoot(mySourceRoot.getUrl(), newType, properties);
}
}
@@ -171,7 +171,7 @@ class LegacyBridgeModuleLibraryTest {
StoreUtil.saveDocumentsAndProjectSettings(project)
val template = "\$MODULE_DIR\$"
assertTrue(moduleFile.readText().contains("""<orderEntry type="module-library">
assertTrue(moduleFile.readText().replace("\r\n", "\n").contains("""<orderEntry type="module-library">
<library name="$mavenLibraryName">
<CLASSES>
<root url="$template/$antLibraryName.jar" />
@@ -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()
@@ -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<P> = (JpsModelSerializerExtension.getExtensions()
val serializer: JpsModuleSourceRootPropertiesSerializer<P> = if (type is UnknownSourceRootType)
UnknownSourceRootPropertiesSerializer.forType(type as UnknownSourceRootType) as JpsModuleSourceRootPropertiesSerializer<P>
else
(JpsModelSerializerExtension.getExtensions()
.flatMap { it.moduleSourceRootPropertiesSerializers }
.firstOrNull { it.type == type }) as? JpsModuleSourceRootPropertiesSerializer<P>
?: error("Module source root type $type is not registered as JpsModelSerializerExtension")
.firstOrNull { it.type == type }) as? JpsModuleSourceRootPropertiesSerializer<P> ?: error("Module source root type $type is not registered as JpsModelSerializerExtension")
val entitySource = currentContentEntry.value.entity.entitySource
val sourceRootEntity = diff.addSourceRootEntity(
@@ -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<out JpsElement> = 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 <P : JpsElement> changeType(newType: JpsModuleSourceRootType<P>, 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<out JpsElement> {
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 {