From d7136f71ee39d12c7ddcdfabaddcff27d88668e8 Mon Sep 17 00:00:00 2001 From: Nikolay Chashnikov Date: Mon, 28 Sep 2020 17:40:43 +0300 Subject: [PATCH] [eclipse] support loading and saving module configuration in '.classpath' format (IDEA-246236) ModuleEntity loaded from .classpath file uses special EclipseProjectFile entity source. Additional properties are stored in EclipseProjectPropertiesEntity (a replacement for EclipseModuleManager in the workspace model). Few things aren't implemented yet: * reloading module configuration when .classpath or *.eml file is changed; * using line separator configured in the project; * proper handling of invalid references to JDK. GitOrigin-RevId: c71b84c783543070c7f3594668e4d52a987355cc --- .../JpsProjectModelSynchronizer.kt | 9 + .../src/AbstractEclipseClasspathReader.java | 36 +- plugins/eclipse/intellij.eclipse.iml | 1 + plugins/eclipse/resources/META-INF/plugin.xml | 2 + .../src/org/jdom/output/EclipseJDOMUtil.java | 5 +- .../config/EclipseModuleManagerImpl.java | 26 +- .../config/EclipseModuleManagerSerializer.kt | 91 +++ .../config/EclipseModuleRootsSerializer.kt | 578 ++++++++++++++++++ .../idea/eclipse/config/EmlFileLoader.kt | 238 ++++++++ .../idea/eclipse/config/EmlFileSaver.kt | 169 +++++ .../idea/eclipse/config/eclipseEntities.kt | 93 +++ .../idea/eclipse/config/pathConversion.kt | 275 +++++++++ .../config/relativeModulePathSupport.kt | 58 ++ .../idea/eclipse/conversion/EJavadocUtil.java | 12 +- .../conversion/EclipseClasspathReader.java | 2 +- .../conversion/EclipseClasspathWriter.java | 32 +- .../conversion/IdeaSpecificSettings.java | 14 +- .../idea/eclipse/EclipseClasspathTest.kt | 9 +- 18 files changed, 1592 insertions(+), 58 deletions(-) create mode 100644 plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EclipseModuleManagerSerializer.kt create mode 100644 plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EclipseModuleRootsSerializer.kt create mode 100644 plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EmlFileLoader.kt create mode 100644 plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EmlFileSaver.kt create mode 100644 plugins/eclipse/src/org/jetbrains/idea/eclipse/config/eclipseEntities.kt create mode 100644 plugins/eclipse/src/org/jetbrains/idea/eclipse/config/pathConversion.kt create mode 100644 plugins/eclipse/src/org/jetbrains/idea/eclipse/config/relativeModulePathSupport.kt diff --git a/platform/platform-impl/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectModelSynchronizer.kt b/platform/platform-impl/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectModelSynchronizer.kt index ef3e91100d23..9136159887f9 100644 --- a/platform/platform-impl/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectModelSynchronizer.kt +++ b/platform/platform-impl/src/com/intellij/workspaceModel/ide/impl/jps/serialization/JpsProjectModelSynchronizer.kt @@ -29,6 +29,7 @@ import com.intellij.workspaceModel.ide.impl.recordModuleLoadingActivity import com.intellij.workspaceModel.storage.* import com.intellij.workspaceModel.storage.bridgeEntities.ModuleDependencyItem import com.intellij.workspaceModel.storage.bridgeEntities.ModuleId +import org.jetbrains.annotations.TestOnly import org.jetbrains.jps.util.JpsPathUtil import java.util.* import java.util.concurrent.atomic.AtomicReference @@ -218,6 +219,14 @@ class JpsProjectModelSynchronizer(private val project: Project) : Disposable { data.saveEntities(storage, affectedSources, writer) } + @TestOnly + fun markAllEntitiesAsDirty() { + val allSources = WorkspaceModel.getInstance(project).entityStorage.current.entitiesBySource { true }.keys + synchronized(sourcesToSave) { + sourcesToSave.addAll(allSources) + } + } + private fun getAndResetIncomingChanges(): JpsConfigurationFilesChange? { synchronized(incomingChanges) { if (incomingChanges.isEmpty()) return null diff --git a/plugins/eclipse/common-eclipse-util/src/AbstractEclipseClasspathReader.java b/plugins/eclipse/common-eclipse-util/src/AbstractEclipseClasspathReader.java index a7072ff16b67..f6103345126c 100644 --- a/plugins/eclipse/common-eclipse-util/src/AbstractEclipseClasspathReader.java +++ b/plugins/eclipse/common-eclipse-util/src/AbstractEclipseClasspathReader.java @@ -112,7 +112,7 @@ public abstract class AbstractEclipseClasspathReader { catch (PatternSyntaxException e) { isTestFolder = false; } - String linked = expandLinkedResourcesPath(macroMap, path); + String linked = expandLinkedResourcesPath(myRootPath, macroMap, path); if (linked == null) { addSourceFolderToCurrentContentRoot(rootModel, srcUrl, isTestFolder); } @@ -132,7 +132,7 @@ public abstract class AbstractEclipseClasspathReader { } else if (kind.equals(EclipseXml.OUTPUT_KIND)) { String output = getPathRelativeToRoot(path); - String linked = expandLinkedResourcesPath(macroMap, path); + String linked = expandLinkedResourcesPath(myRootPath, macroMap, path); if (linked != null) { output = linked; if (eclipseModuleManager != null) { @@ -142,7 +142,7 @@ public abstract class AbstractEclipseClasspathReader { setupOutput(rootModel, output); } else if (kind.equals(EclipseXml.LIB_KIND)) { - String linked = expandLinkedResourcesPath(macroMap, path); + String linked = expandLinkedResourcesPath(myRootPath, macroMap, path); String url; if (linked != null) { url = prepareValidUrlInsideJar(pathToUrl(linked)); @@ -161,7 +161,7 @@ public abstract class AbstractEclipseClasspathReader { final String sourcePath = element.getAttributeValue(EclipseXml.SOURCEPATH_ATTR); String srcUrl = null; if (sourcePath != null) { - final String linkedSrc = expandLinkedResourcesPath(macroMap, sourcePath); + final String linkedSrc = expandLinkedResourcesPath(myRootPath, macroMap, sourcePath); if (linkedSrc != null) { srcUrl = prepareValidUrlInsideJar(pathToUrl(linkedSrc)); if (eclipseModuleManager != null) { @@ -248,7 +248,7 @@ public abstract class AbstractEclipseClasspathReader { return path.isEmpty() ? myRootPath : (myRootPath + "/" + path); } - private static String getNativeLibraryRoot(Element element) { + public static String getNativeLibraryRoot(Element element) { final Element attributes = element.getChild(EclipseXml.ATTRIBUTES_TAG); if (attributes != null) { for (Element attributeElement : attributes.getChildren(EclipseXml.ATTRIBUTE_TAG)) { @@ -260,17 +260,17 @@ public abstract class AbstractEclipseClasspathReader { return null; } - protected static int srcVarStart(String srcPath) { + public static int srcVarStart(String srcPath) { return srcPath.startsWith("/") ? 1 : 0; } @NotNull - protected static String getPresentableName(@NotNull String path) { + public static String getPresentableName(@NotNull String path) { return getPresentableName(path, null); } @NotNull - protected static String getPresentableName(@NotNull String path, Set names) { + public static String getPresentableName(@NotNull String path, Set names) { String pathComponent = getLastPathComponent(path); if (pathComponent != null && names != null && !names.add(pathComponent)) { return path; @@ -287,7 +287,7 @@ public abstract class AbstractEclipseClasspathReader { } @NotNull - protected static String getVariableRelatedPath(@NotNull String var, String path) { + public static String getVariableRelatedPath(@NotNull String var, String path) { return "$" + var + "$" + (path == null ? "" : "/" + path); } @@ -296,7 +296,7 @@ public abstract class AbstractEclipseClasspathReader { return "file://" + FileUtil.toSystemIndependentName(path); } - protected static EPathVariable createEPathVariable(String pathAttr, int varStart) { + public static EPathVariable createEPathVariable(String pathAttr, int varStart) { int slash = pathAttr.indexOf("/", varStart); if (slash > 0) { return new EPathVariable(pathAttr.substring(varStart, slash), pathAttr.substring(slash + 1)); @@ -313,9 +313,9 @@ public abstract class AbstractEclipseClasspathReader { } @Nullable - protected String expandLinkedResourcesPath(final ExpandMacroToPathMap macroMap, - final String path) { - final EclipseProjectFinder.LinkedResource linkedResource = EclipseProjectFinder.findLinkedResource(myRootPath, path); + public static String expandLinkedResourcesPath(String rootPath, final ExpandMacroToPathMap macroMap, + final String path) { + final EclipseProjectFinder.LinkedResource linkedResource = EclipseProjectFinder.findLinkedResource(rootPath, path); if (linkedResource != null) { if (linkedResource.containsPathVariable()) { final String toPathVariableFormat = @@ -375,7 +375,7 @@ public abstract class AbstractEclipseClasspathReader { } } - protected static class EPathVariable { + public static class EPathVariable { @NotNull private final String myVariable; private final String myRelatedPath; @@ -384,5 +384,13 @@ public abstract class AbstractEclipseClasspathReader { myVariable = variable; myRelatedPath = relatedPath; } + + public @NotNull String getVariable() { + return myVariable; + } + + public String getRelatedPath() { + return myRelatedPath; + } } } diff --git a/plugins/eclipse/intellij.eclipse.iml b/plugins/eclipse/intellij.eclipse.iml index c4d2b8184f08..5cc0ccd50a54 100644 --- a/plugins/eclipse/intellij.eclipse.iml +++ b/plugins/eclipse/intellij.eclipse.iml @@ -23,5 +23,6 @@ + \ No newline at end of file diff --git a/plugins/eclipse/resources/META-INF/plugin.xml b/plugins/eclipse/resources/META-INF/plugin.xml index 9fbd3f15478c..9fbaff8e7062 100644 --- a/plugins/eclipse/resources/META-INF/plugin.xml +++ b/plugins/eclipse/resources/META-INF/plugin.xml @@ -20,6 +20,8 @@ com.intellij.modules.java + + diff --git a/plugins/eclipse/src/org/jdom/output/EclipseJDOMUtil.java b/plugins/eclipse/src/org/jdom/output/EclipseJDOMUtil.java index e60ee9349949..a68437eee80b 100644 --- a/plugins/eclipse/src/org/jdom/output/EclipseJDOMUtil.java +++ b/plugins/eclipse/src/org/jdom/output/EclipseJDOMUtil.java @@ -34,7 +34,10 @@ public final class EclipseJDOMUtil { } public static void output(@NotNull Element element, @NotNull @NonNls Writer writer, @NotNull Project project) throws IOException { - String lineSeparator = CodeStyle.getSettings(project).getLineSeparator(); + output(element, writer, CodeStyle.getSettings(project).getLineSeparator()); + } + + public static void output(@NotNull Element element, @NonNls @NotNull Writer writer, String lineSeparator) throws IOException { writer.write(""); writer.write(lineSeparator); createOutputter(lineSeparator).output(element, writer); diff --git a/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EclipseModuleManagerImpl.java b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EclipseModuleManagerImpl.java index 7d5da8d04bdb..2cbee0a8a41f 100644 --- a/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EclipseModuleManagerImpl.java +++ b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EclipseModuleManagerImpl.java @@ -21,25 +21,25 @@ import java.util.Set; @State(name = "EclipseModuleManager") public class EclipseModuleManagerImpl implements EclipseModuleManager, PersistentStateComponent, StateStorageChooserEx { - @NonNls private static final String VALUE_ATTR = "value"; - @NonNls private static final String VARELEMENT = "varelement"; - @NonNls private static final String VAR_ATTRIBUTE = "var"; - @NonNls private static final String CONELEMENT = "conelement"; - @NonNls private static final String FORCED_JDK = "forced_jdk"; - @NonNls private static final String SRC_DESCRIPTION = "src_description"; - @NonNls private static final String EXPECTED_POSITION = "expected_position"; - @NonNls private static final String SRC_FOLDER = "src_folder"; + @NonNls static final String VALUE_ATTR = "value"; + @NonNls static final String VARELEMENT = "varelement"; + @NonNls static final String VAR_ATTRIBUTE = "var"; + @NonNls static final String CONELEMENT = "conelement"; + @NonNls static final String FORCED_JDK = "forced_jdk"; + @NonNls static final String SRC_DESCRIPTION = "src_description"; + @NonNls static final String EXPECTED_POSITION = "expected_position"; + @NonNls static final String SRC_FOLDER = "src_folder"; private CachedXmlDocumentSet myDocumentSet; private final Map myEclipseVariablePaths = new LinkedHashMap<>(); private final Set myEclipseUrls = new LinkedHashSet<>(); private final Set myUnknownCons = new LinkedHashSet<>(); private boolean myForceConfigureJDK; - @NonNls private static final String SRC_PREFIX = "src:"; - @NonNls private static final String SRC_LINK_PREFIX = "linksrc:"; - @NonNls private static final String LINK_PREFIX = "link:"; - @NonNls private static final String PREFIX_ATTR = "kind"; + @NonNls static final String SRC_PREFIX = "src:"; + @NonNls static final String SRC_LINK_PREFIX = "linksrc:"; + @NonNls static final String LINK_PREFIX = "link:"; + @NonNls static final String PREFIX_ATTR = "kind"; private final Module myModule; - @NonNls private static final String LIBELEMENT = "libelement"; + @NonNls static final String LIBELEMENT = "libelement"; private int myExpectedModuleSourcePlace; private final Map mySrcPlace = new LinkedHashMap<>(); private String myInvalidJdk; diff --git a/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EclipseModuleManagerSerializer.kt b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EclipseModuleManagerSerializer.kt new file mode 100644 index 000000000000..9d9cfba82e98 --- /dev/null +++ b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EclipseModuleManagerSerializer.kt @@ -0,0 +1,91 @@ +// 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.idea.eclipse.config + +import com.intellij.workspaceModel.ide.impl.jps.serialization.CustomModuleComponentSerializer +import com.intellij.workspaceModel.ide.impl.jps.serialization.ErrorReporter +import com.intellij.workspaceModel.ide.impl.jps.serialization.JpsFileContentReader +import com.intellij.workspaceModel.ide.impl.jps.serialization.JpsFileContentWriter +import com.intellij.workspaceModel.storage.VirtualFileUrl +import com.intellij.workspaceModel.storage.VirtualFileUrlManager +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder +import com.intellij.workspaceModel.storage.bridgeEntities.ModuleEntity +import org.jdom.Element +import org.jetbrains.idea.eclipse.config.EclipseModuleManagerImpl.* +import org.jetbrains.jps.eclipse.model.JpsEclipseClasspathSerializer +import org.jetbrains.jps.model.serialization.JDomSerializationUtil +import org.jetbrains.jps.model.serialization.JpsProjectLoader + +/** + * Implements loading and saving configuration from [EclipseModuleManagerImpl] in iml file when workspace model is used + */ +class EclipseModuleManagerSerializer : CustomModuleComponentSerializer { + override fun loadComponent(builder: WorkspaceEntityStorageBuilder, + moduleEntity: ModuleEntity, + reader: JpsFileContentReader, + imlFileUrl: VirtualFileUrl, + errorReporter: ErrorReporter, + virtualFileManager: VirtualFileUrlManager) { + val componentTag = reader.loadComponent(imlFileUrl.url, "EclipseModuleManager") ?: return + val entity = builder.addEclipseProjectPropertiesEntity(moduleEntity, moduleEntity.entitySource) + builder.modifyEntity(ModifiableEclipseProjectPropertiesEntity::class.java, entity) { + componentTag.getChildren(LIBELEMENT).forEach { + eclipseUrls.add(virtualFileManager.fromUrl(it.getAttributeValue(VALUE_ATTR)!!)) + } + componentTag.getChildren(VARELEMENT).forEach { + variablePaths[it.getAttributeValue(VAR_ATTRIBUTE)!!] = + it.getAttributeValue(PREFIX_ATTR, "") + it.getAttributeValue(VALUE_ATTR) + } + componentTag.getChildren(CONELEMENT).forEach { + unknownCons.add(it.getAttributeValue(VALUE_ATTR)!!) + } + forceConfigureJdk = componentTag.getAttributeValue(FORCED_JDK)?.toBoolean() ?: false + val srcDescriptionTag = componentTag.getChild(SRC_DESCRIPTION) + if (srcDescriptionTag != null) { + expectedModuleSourcePlace = srcDescriptionTag.getAttributeValue(EXPECTED_POSITION)?.toInt() ?: 0 + srcDescriptionTag.getChildren(SRC_FOLDER).forEach { + srcPlace[it.getAttributeValue(VALUE_ATTR)!!] = it.getAttributeValue(EXPECTED_POSITION)!!.toInt() + } + } + } + } + + override fun saveComponent(moduleEntity: ModuleEntity, imlFileUrl: VirtualFileUrl, writer: JpsFileContentWriter) { + val moduleOptions = moduleEntity.customImlData?.customModuleOptions + if (moduleOptions != null && moduleOptions[JpsProjectLoader.CLASSPATH_ATTRIBUTE] == JpsEclipseClasspathSerializer.CLASSPATH_STORAGE_ID) { + return + } + val eclipseProperties = moduleEntity.eclipseProperties + if (eclipseProperties == null || eclipseProperties.eclipseUrls.isEmpty() && eclipseProperties.variablePaths.isEmpty() + && !eclipseProperties.forceConfigureJdk && eclipseProperties.unknownCons.isEmpty()) { + return + } + + val componentTag = JDomSerializationUtil.createComponentElement("EclipseModuleManager") + eclipseProperties.eclipseUrls.forEach { + componentTag.addContent(Element(LIBELEMENT).setAttribute(VALUE_ATTR, it.url)) + } + eclipseProperties.variablePaths.forEach { name, path -> + val prefix = listOf(SRC_PREFIX, SRC_LINK_PREFIX, LINK_PREFIX).firstOrNull { name.startsWith(it) } ?: "" + val varTag = Element(VARELEMENT) + varTag.setAttribute(VAR_ATTRIBUTE, name.removePrefix(prefix)) + if (prefix != "") { + varTag.setAttribute(PREFIX_ATTR, prefix) + } + varTag.setAttribute(VALUE_ATTR, path) + componentTag.addContent(varTag) + } + eclipseProperties.unknownCons.forEach { + componentTag.addContent(Element(CONELEMENT).setAttribute(VALUE_ATTR, it)) + } + if (eclipseProperties.forceConfigureJdk) { + componentTag.setAttribute(FORCED_JDK, true.toString()) + } + val srcDescriptionTag = Element(SRC_DESCRIPTION) + srcDescriptionTag.setAttribute(EXPECTED_POSITION, eclipseProperties.expectedModuleSourcePlace.toString()) + eclipseProperties.srcPlace.forEach { url, position -> + srcDescriptionTag.addContent(Element(SRC_FOLDER).setAttribute(VALUE_ATTR, url).setAttribute(EXPECTED_POSITION, position.toString())) + } + componentTag.addContent(srcDescriptionTag) + writer.saveComponent(imlFileUrl.url, "EclipseModuleManager", componentTag) + } +} \ No newline at end of file diff --git a/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EclipseModuleRootsSerializer.kt b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EclipseModuleRootsSerializer.kt new file mode 100644 index 000000000000..ce40506a5cfe --- /dev/null +++ b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EclipseModuleRootsSerializer.kt @@ -0,0 +1,578 @@ +// 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.idea.eclipse.config + +import com.intellij.configurationStore.StorageManagerFileWriteRequestor +import com.intellij.configurationStore.getOrCreateVirtualFile +import com.intellij.configurationStore.runAsWriteActionIfNeeded +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar +import com.intellij.openapi.util.JDOMUtil +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.util.Function +import com.intellij.util.SystemProperties +import com.intellij.workspaceModel.ide.JpsFileEntitySource +import com.intellij.workspaceModel.ide.impl.jps.serialization.* +import com.intellij.workspaceModel.storage.* +import com.intellij.workspaceModel.storage.bridgeEntities.* +import org.jdom.Element +import org.jdom.output.EclipseJDOMUtil +import org.jetbrains.idea.eclipse.AbstractEclipseClasspathReader +import org.jetbrains.idea.eclipse.AbstractEclipseClasspathReader.expandLinkedResourcesPath +import org.jetbrains.idea.eclipse.EclipseXml +import org.jetbrains.idea.eclipse.IdeaXml +import org.jetbrains.idea.eclipse.conversion.EJavadocUtil +import org.jetbrains.idea.eclipse.conversion.EclipseClasspathReader +import org.jetbrains.idea.eclipse.conversion.EclipseClasspathWriter +import org.jetbrains.idea.eclipse.conversion.EclipseClasspathWriter.addOrderEntry +import org.jetbrains.idea.eclipse.importWizard.EclipseNatureImporter +import org.jetbrains.jps.eclipse.model.JpsEclipseClasspathSerializer +import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer.JAVA_SOURCE_ROOT_TYPE_ID +import org.jetbrains.jps.util.JpsPathUtil +import java.io.IOException +import java.io.OutputStreamWriter +import java.nio.charset.StandardCharsets +import java.nio.file.Path +import java.nio.file.Paths + +/** + * Implements loading and saving module configuration from workspace model in '.classpath' file + */ +class EclipseModuleRootsSerializer : CustomModuleRootsSerializer, StorageManagerFileWriteRequestor { + override val id: String + get() = JpsEclipseClasspathSerializer.CLASSPATH_STORAGE_ID + + companion object { + private val LOG = logger() + } + + override fun createEntitySource(imlFileUrl: VirtualFileUrl, + internalEntitySource: JpsFileEntitySource, + customDir: String?, + virtualFileManager: VirtualFileUrlManager): EntitySource? { + val storageRootUrl = getStorageRoot(imlFileUrl, customDir, virtualFileManager) + val classpathUrl = storageRootUrl.append(EclipseXml.CLASSPATH_FILE) + return EclipseProjectFile(classpathUrl, internalEntitySource) + } + + override fun loadRoots(builder: WorkspaceEntityStorageBuilder, + moduleEntity: ModuleEntity, + reader: JpsFileContentReader, + customDir: String?, + imlFileUrl: VirtualFileUrl, + internalModuleListSerializer: JpsModuleListSerializer?, + errorReporter: ErrorReporter, + virtualFileManager: VirtualFileUrlManager) { + val storageRootUrl = getStorageRoot(imlFileUrl, customDir, virtualFileManager) + val entitySource = moduleEntity.entitySource as EclipseProjectFile + val contentRootEntity = builder.addContentRootEntity(storageRootUrl, emptyList(), emptyList(), moduleEntity) + + val classpathTag = reader.loadComponent(entitySource.classpathFile.url, "", null) + if (classpathTag != null) { + val relativePathResolver = ModuleRelativePathResolver(internalModuleListSerializer, reader, virtualFileManager) + loadClasspathTags(classpathTag, builder, contentRootEntity, storageRootUrl, reader, relativePathResolver, errorReporter, imlFileUrl, + virtualFileManager) + } + else { + builder.addJavaModuleSettingsEntity(false, true, storageRootUrl.append("bin"), null, + moduleEntity, entitySource) + } + + val emlUrl = getEmlFileUrl(imlFileUrl) + val emlTag = reader.loadComponent(emlUrl, "", null) + if (emlTag != null) { + reader.getExpandMacroMap(imlFileUrl.url).substitute(emlTag, SystemInfo.isFileSystemCaseSensitive) + EmlFileLoader(moduleEntity, builder, reader.getExpandMacroMap(emlUrl), errorReporter, virtualFileManager).loadEml(emlTag, contentRootEntity) + } + else { + val javaSettings = moduleEntity.javaSettings + if (javaSettings != null) { + builder.modifyEntity(ModifiableJavaModuleSettingsEntity::class.java, javaSettings) { + excludeOutput = false + } + } + else { + builder.addJavaModuleSettingsEntity(true, false, null, null, moduleEntity, entitySource) + } + } + } + + private fun getEmlFileUrl(imlFileUrl: VirtualFileUrl) = imlFileUrl.url.removeSuffix(".iml") + EclipseXml.IDEA_SETTINGS_POSTFIX + + private fun loadClasspathTags(classpathTag: Element, + builder: WorkspaceEntityStorageBuilder, + contentRootEntity: ContentRootEntity, + storageRootUrl: VirtualFileUrl, + reader: JpsFileContentReader, + relativePathResolver: ModuleRelativePathResolver, + errorReporter: ErrorReporter, + imlFileUrl: VirtualFileUrl, + virtualUrlManager: VirtualFileUrlManager) { + fun reportError(message: String) { + errorReporter.reportError(message, storageRootUrl.append(EclipseXml.CLASSPATH_FILE)) + } + fun getUrlByRelativePath(path: String): VirtualFileUrl { + return if (path.isEmpty()) storageRootUrl else storageRootUrl.append(FileUtil.toSystemIndependentName(path)) + } + + val moduleEntity = contentRootEntity.module + fun editEclipseProperties(action: (ModifiableEclipseProjectPropertiesEntity) -> Unit) { + val eclipseProperties = moduleEntity.eclipseProperties ?: builder.addEclipseProjectPropertiesEntity(moduleEntity, + moduleEntity.entitySource) + builder.modifyEntity(ModifiableEclipseProjectPropertiesEntity::class.java, eclipseProperties) { + action(this) + } + } + + val storageRootPath = JpsPathUtil.urlToPath(storageRootUrl.url) + val libraryNames = HashSet() + val expandMacroMap = reader.getExpandMacroMap(imlFileUrl.url) + + val dependencies = ArrayList() + dependencies.add(ModuleDependencyItem.ModuleSourceDependency) + + classpathTag.getChildren(EclipseXml.CLASSPATHENTRY_TAG).forEachIndexed { index, entryTag -> + val kind = entryTag.getAttributeValue(EclipseXml.KIND_ATTR) + if (kind == null) { + reportError("'${EclipseXml.KIND_ATTR}' attribute is missing in '${EclipseXml.CLASSPATHENTRY_TAG}' tag") + return@forEachIndexed + } + val path = entryTag.getAttributeValue(EclipseXml.PATH_ATTR) + if (path == null) { + reportError("'${EclipseXml.PATH_ATTR}' attribute is missing in '${EclipseXml.CLASSPATHENTRY_TAG}' tag") + return@forEachIndexed + } + val exported = EclipseXml.TRUE_VALUE == entryTag.getAttributeValue(EclipseXml.EXPORTED_ATTR) + + when (kind) { + EclipseXml.SRC_KIND -> { + if (path.startsWith("/")) { + dependencies.add(ModuleDependencyItem.Exportable.ModuleDependency(ModuleId(path.removePrefix("/")), exported, + ModuleDependencyItem.DependencyScope.COMPILE, false)) + } + else { + val linkedPath = expandLinkedResourcesPath(storageRootPath, expandMacroMap, path) + val srcUrl: VirtualFileUrl + if (linkedPath == null) { + srcUrl = getUrlByRelativePath(path) + builder.addSourceRootEntity(contentRootEntity, srcUrl, false, JAVA_SOURCE_ROOT_TYPE_ID, + contentRootEntity.entitySource) + } + else { + srcUrl = convertToRootUrl(linkedPath, virtualUrlManager) + editEclipseProperties { + it.setVariable(EclipseModuleManagerImpl.SRC_LINK_PREFIX, path, srcUrl.url) + } + val newContentRoot = moduleEntity.contentRoots.firstOrNull { it.url == srcUrl } + ?: builder.addContentRootEntity(srcUrl, emptyList(), emptyList(), moduleEntity) + builder.addSourceRootEntity(newContentRoot, srcUrl, false, JAVA_SOURCE_ROOT_TYPE_ID, newContentRoot.entitySource) + } + dependencies.removeIf { it is ModuleDependencyItem.ModuleSourceDependency } + dependencies.add(ModuleDependencyItem.ModuleSourceDependency) + editEclipseProperties { + it.expectedModuleSourcePlace = dependencies.size - 1 + it.srcPlace[srcUrl.url] = index + } + } + } + EclipseXml.OUTPUT_KIND -> { + val linked = expandLinkedResourcesPath(storageRootPath, expandMacroMap, path) + val outputUrl: VirtualFileUrl + if (linked != null) { + outputUrl = virtualUrlManager.fromUrl(pathToUrl(linked)) + editEclipseProperties { + it.setVariable(EclipseModuleManagerImpl.LINK_PREFIX, path, outputUrl.url) + } + } + else { + outputUrl = getUrlByRelativePath(path) + } + builder.addJavaModuleSettingsEntity(false, true, outputUrl, null, + moduleEntity, contentRootEntity.entitySource) + } + EclipseXml.LIB_KIND -> { + val linked = expandLinkedResourcesPath(storageRootPath, expandMacroMap, path) + val url: VirtualFileUrl + if (linked != null) { + url = convertToRootUrl(linked, virtualUrlManager) + editEclipseProperties { + it.setVariable(EclipseModuleManagerImpl.LINK_PREFIX, path, url.url) + } + } + else { + url = convertRelativePathToUrl(path, contentRootEntity, relativePathResolver, virtualUrlManager) + } + editEclipseProperties { + it.eclipseUrls.add(url) + } + val sourcePath = entryTag.getAttributeValue(EclipseXml.SOURCEPATH_ATTR) + val srcUrl: VirtualFileUrl? + if (sourcePath != null) { + val linkedSrc = expandLinkedResourcesPath(storageRootPath, expandMacroMap, sourcePath) + if (linkedSrc != null) { + srcUrl = convertToRootUrl(linkedSrc, virtualUrlManager) + editEclipseProperties { + it.setVariable(EclipseModuleManagerImpl.SRC_LINK_PREFIX, path, srcUrl.url) + } + } + else { + srcUrl = convertRelativePathToUrl(sourcePath, contentRootEntity, relativePathResolver, virtualUrlManager) + } + } + else { + srcUrl = null + } + val nativeRoot = AbstractEclipseClasspathReader.getNativeLibraryRoot(entryTag)?.let { + convertRelativePathToUrl(it, contentRootEntity, relativePathResolver, virtualUrlManager) + } + val name = AbstractEclipseClasspathReader.getPresentableName(path, libraryNames) + val roots = createLibraryRoots(url, srcUrl, nativeRoot, entryTag, moduleEntity, relativePathResolver, virtualUrlManager) + val libraryEntity = builder.addLibraryEntity(name, LibraryTableId.ModuleLibraryTableId(moduleEntity.persistentId()), roots, + emptyList(), contentRootEntity.entitySource) + dependencies.add(ModuleDependencyItem.Exportable.LibraryDependency(libraryEntity.persistentId(), exported, + ModuleDependencyItem.DependencyScope.COMPILE)) + } + EclipseXml.VAR_KIND -> { + val slash = path.indexOf('/') + if (slash == 0) { + reportError("'${EclipseXml.PATH_ATTR}' attribute format is incorrect for '${EclipseXml.VAR_KIND}': $path") + return@forEachIndexed + } + val libName = AbstractEclipseClasspathReader.getPresentableName(path, libraryNames) + val url = convertVariablePathToUrl(expandMacroMap, path, 0, virtualUrlManager) + editEclipseProperties { + it.setVariable("", path, url.url) + } + val srcPath = entryTag.getAttributeValue(EclipseXml.SOURCEPATH_ATTR) + val srcUrl: VirtualFileUrl? + if (srcPath != null) { + srcUrl = convertVariablePathToUrl(expandMacroMap, srcPath, AbstractEclipseClasspathReader.srcVarStart(srcPath), + virtualUrlManager) + editEclipseProperties { + it.setVariable(EclipseModuleManagerImpl.SRC_LINK_PREFIX, srcPath, srcUrl.url) + } + } + else { + srcUrl = null + } + + val nativeRoot = AbstractEclipseClasspathReader.getNativeLibraryRoot(entryTag)?.let { + convertRelativePathToUrl(it, contentRootEntity, relativePathResolver, virtualUrlManager) + } + val roots = createLibraryRoots(url, srcUrl, nativeRoot, entryTag, moduleEntity, relativePathResolver, virtualUrlManager) + val libraryEntity = builder.addLibraryEntity(libName, LibraryTableId.ModuleLibraryTableId(moduleEntity.persistentId()), + roots, emptyList(), contentRootEntity.entitySource) + dependencies.add(ModuleDependencyItem.Exportable.LibraryDependency(libraryEntity.persistentId(), exported, + ModuleDependencyItem.DependencyScope.COMPILE)) + + } + EclipseXml.CON_KIND -> { + if (path == EclipseXml.ECLIPSE_PLATFORM) { + val libraryId = LibraryId(IdeaXml.ECLIPSE_LIBRARY, + LibraryTableId.GlobalLibraryTableId(LibraryTablesRegistrar.APPLICATION_LEVEL)) + dependencies.add(ModuleDependencyItem.Exportable.LibraryDependency(libraryId, exported, + ModuleDependencyItem.DependencyScope.COMPILE)) + } + else if (path.startsWith(EclipseXml.JRE_CONTAINER)) { + val jdkName = AbstractEclipseClasspathReader.getLastPathComponent(path) + dependencies.removeIf { it is ModuleDependencyItem.SdkDependency || it == ModuleDependencyItem.InheritedSdkDependency } + dependencies.add(if (jdkName != null) ModuleDependencyItem.SdkDependency(jdkName, IdeaXml.JAVA_SDK_TYPE) + else ModuleDependencyItem.InheritedSdkDependency) + } + else if (path.startsWith(EclipseXml.USER_LIBRARY)) { + val libraryId = LibraryId(AbstractEclipseClasspathReader.getPresentableName(path), LibraryTableId.ProjectLibraryTableId) + dependencies.add(ModuleDependencyItem.Exportable.LibraryDependency(libraryId, exported, + ModuleDependencyItem.DependencyScope.COMPILE)) + + } + else if (path.startsWith(EclipseXml.JUNIT_CONTAINER)) { + val junitName = IdeaXml.JUNIT + AbstractEclipseClasspathReader.getPresentableName(path) + val url = EclipseClasspathReader.getJunitClsUrl(junitName.contains("4")) + val roots = listOf(LibraryRoot(virtualUrlManager.fromUrl(url), + LibraryRootTypeId(OrderRootType.CLASSES.name()), + LibraryRoot.InclusionOptions.ROOT_ITSELF)) + val libraryEntity = builder.addLibraryEntity(junitName, LibraryTableId.ModuleLibraryTableId(moduleEntity.persistentId()), + roots, emptyList(), contentRootEntity.entitySource) + dependencies.add(ModuleDependencyItem.Exportable.LibraryDependency(libraryEntity.persistentId(), exported, + ModuleDependencyItem.DependencyScope.COMPILE)) + } + else { + val definedCons = EclipseNatureImporter.getAllDefinedCons() + if (path in definedCons) { + editEclipseProperties { + it.knownCons.add(path) + it.srcPlace[path] = index + } + } + else { + editEclipseProperties { + it.unknownCons.add(path) + } + val libraryId = LibraryId(path, LibraryTableId.GlobalLibraryTableId(LibraryTablesRegistrar.APPLICATION_LEVEL)) + dependencies.add(ModuleDependencyItem.Exportable.LibraryDependency(libraryId, exported, + ModuleDependencyItem.DependencyScope.COMPILE)) + } + } + } + else -> { + reportError("Unknown '${EclipseXml.KIND_ATTR}' in '${EclipseXml.CLASSPATHENTRY_TAG}': $kind") + } + } + } + if (dependencies.none { it is ModuleDependencyItem.SdkDependency || it == ModuleDependencyItem.InheritedSdkDependency }) { + editEclipseProperties { + it.forceConfigureJdk = true + it.expectedModuleSourcePlace++ + } + dependencies.add(0, ModuleDependencyItem.InheritedSdkDependency) + } + + builder.modifyEntity(ModifiableModuleEntity::class.java, moduleEntity) { + this.dependencies = dependencies + } + } + + private fun createLibraryRoots(url: VirtualFileUrl, + srcUrl: VirtualFileUrl?, + nativeRoot: VirtualFileUrl?, + entryTag: Element, + moduleEntity: ModuleEntity, + relativePathResolver: ModuleRelativePathResolver, + virtualUrlManager: VirtualFileUrlManager): ArrayList { + val roots = ArrayList() + roots.add(LibraryRoot(url, LibraryRootTypeId(OrderRootType.CLASSES.name()), LibraryRoot.InclusionOptions.ROOT_ITSELF)) + if (srcUrl != null) { + roots.add(LibraryRoot(srcUrl, LibraryRootTypeId(OrderRootType.SOURCES.name()), LibraryRoot.InclusionOptions.ROOT_ITSELF)) + } + if (nativeRoot != null) { + roots.add(LibraryRoot(nativeRoot, LibraryRootTypeId("NATIVE"), LibraryRoot.InclusionOptions.ROOT_ITSELF)) + } + entryTag.getChild("attributes")?.getChildren("attribute") + ?.filter { it.getAttributeValue("name") == EclipseXml.JAVADOC_LOCATION } + ?.mapTo(roots) { + LibraryRoot(convertToJavadocUrl(it.getAttributeValue("value")!!, moduleEntity, relativePathResolver, virtualUrlManager), LibraryRootTypeId("JAVADOC"), + LibraryRoot.InclusionOptions.ROOT_ITSELF) + } + return roots + } + + override fun saveRoots(module: ModuleEntity, + entities: Map, List>, + writer: JpsFileContentWriter, + customDir: String?, + imlFileUrl: VirtualFileUrl, + storage: WorkspaceEntityStorage) { + fun saveXmlFile(path: Path, root: Element) { + //todo get rid of WriteAction here + WriteAction.runAndWait { + getOrCreateVirtualFile(path, this).getOutputStream(this).use { + //todo use proper line separator + EclipseJDOMUtil.output(root, OutputStreamWriter(it, StandardCharsets.UTF_8), SystemProperties.getLineSeparator()) + } + } + } + + @Suppress("UNCHECKED_CAST") + val contentRoots = entities[ContentRootEntity::class.java] as List? ?: emptyList() + val entitySource = contentRoots.asSequence().map { it.entitySource }.filterIsInstance().firstOrNull() ?: return + val classpathFile = VirtualFileManager.getInstance().findFileByUrl(entitySource.classpathFile.url) + val oldClasspath = classpathFile?.inputStream?.use { JDOMUtil.load(it) } + val pathShortener = ModulePathShortener(storage) + if (oldClasspath != null || !entities[SourceRootEntity::class.java].isNullOrEmpty() || module.dependencies.size > 2) { + val newClasspath = saveClasspathTags(module, entities, entitySource, oldClasspath, pathShortener) + if (oldClasspath == null || !JDOMUtil.areElementsEqual(newClasspath, oldClasspath)) { + saveXmlFile(Paths.get(entitySource.classpathFile.filePath!!), newClasspath) + } + } + + val emlFileUrl = getEmlFileUrl(imlFileUrl) + val emlRoot = EmlFileSaver(module, entities, pathShortener, writer.getReplacePathMacroMap(imlFileUrl.url)).saveEml() + if (emlRoot != null) { + saveXmlFile(Paths.get(JpsPathUtil.urlToPath(emlFileUrl)), emlRoot) + } + else { + val emlFile = VirtualFileManager.getInstance().findFileByUrl(emlFileUrl) + if (emlFile != null) { + runAsWriteActionIfNeeded { + try { + emlFile.delete(this) + } + catch (e: IOException) { + } + } + } + } + } + + private fun saveClasspathTags(module: ModuleEntity, + entities: Map, List>, + entitySource: EclipseProjectFile, + oldClasspath: Element?, + pathShortener: ModulePathShortener): Element { + val classpathTag = Element(EclipseXml.CLASSPATH_TAG) + val oldEntries = oldClasspath?.getChildren(EclipseXml.CLASSPATHENTRY_TAG)?.associateBy { + it.getAttributeValue(EclipseXml.KIND_ATTR)!! + EclipseClasspathWriter.getJREKey(it.getAttributeValue(EclipseXml.PATH_ATTR)) + } ?: emptyMap() + + fun addClasspathEntry(kind: String, path: String?, index: Int = -1): Element { + return addOrderEntry(kind, path, classpathTag, index, oldEntries) + } + + val eclipseProperties = module.eclipseProperties + @Suppress("UNCHECKED_CAST") + val sourceRoots = entities[SourceRootEntity::class.java] as List? ?: emptyList() + @Suppress("UNCHECKED_CAST") + val moduleLibraries = (entities[LibraryEntity::class.java] as List? ?: emptyList()).associateBy { it.name } + + for ((itemIndex, item) in module.dependencies.withIndex()) { + when (item) { + ModuleDependencyItem.ModuleSourceDependency -> { + val shouldPlaceSeparately = eclipseProperties?.expectedModuleSourcePlace == itemIndex + for (sourceRoot in sourceRoots) { + var relativePath = convertToEclipsePath(sourceRoot.url, module, entitySource, pathShortener) + if (sourceRoot.contentRoot.url != module.mainContentRoot?.url) { + val linkedPath = eclipseProperties?.getVariable(EclipseModuleManagerImpl.SRC_LINK_PREFIX, sourceRoot.url.url) + if (linkedPath != null) { + relativePath = linkedPath + } + } + val index = eclipseProperties?.srcPlace?.get(sourceRoot.url.url) ?: -1 + addClasspathEntry(EclipseXml.SRC_KIND, relativePath, if (shouldPlaceSeparately) index else -1) + } + } + is ModuleDependencyItem.Exportable.ModuleDependency -> { + val path = "/${item.module.name}" + val oldElement = EclipseClasspathWriter.getOldElement(EclipseXml.SRC_KIND, path, oldEntries) + val classpathEntry = addClasspathEntry(EclipseXml.SRC_KIND, path) + if (oldElement == null) { + EclipseClasspathWriter.setAttributeIfAbsent(classpathEntry, EclipseXml.COMBINEACCESSRULES_ATTR, EclipseXml.FALSE_VALUE) + } + EclipseClasspathWriter.setExported(classpathEntry, item.exported) + } + is ModuleDependencyItem.Exportable.LibraryDependency -> { + val libraryName = item.library.name + if (item.library.tableId is LibraryTableId.ModuleLibraryTableId) { + val libraryRoots = moduleLibraries[libraryName]?.roots ?: emptyList() + val libraryClassesRoots = libraryRoots.filter { it.type.name == OrderRootType.CLASSES.name() } + val firstRoot = libraryClassesRoots.firstOrNull()?.url + if (firstRoot != null) { + val firstUrl = firstRoot.url + if (libraryName.contains(IdeaXml.JUNIT) && firstUrl == EclipseClasspathReader.getJunitClsUrl(libraryName.contains("4"))) { + val classpathEntry = addClasspathEntry(EclipseXml.CON_KIND, + EclipseXml.JUNIT_CONTAINER + "/" + libraryName.substring(IdeaXml.JUNIT.length)) + EclipseClasspathWriter.setExported(classpathEntry, item.exported) + } + else { + var newVarLibrary = false + var link = false + var eclipseVariablePath: String? = eclipseProperties?.getVariable("", firstUrl) + if (eclipseVariablePath == null) { + eclipseVariablePath = eclipseProperties?.getVariable(EclipseModuleManagerImpl.LINK_PREFIX, firstUrl) + link = eclipseVariablePath != null + } + if (eclipseVariablePath == null && eclipseProperties?.eclipseUrls?.contains(firstRoot) != true) { //new library was added + newVarLibrary = true + eclipseVariablePath = convertToEclipsePathWithVariable(libraryClassesRoots) + } + var classpathEntry = if (eclipseVariablePath != null) { + addClasspathEntry(if (link) EclipseXml.LIB_KIND else EclipseXml.VAR_KIND, eclipseVariablePath) + } + else { + LOG.assertTrue(!StringUtil.isEmptyOrSpaces(firstUrl), "Library: $libraryName") + addClasspathEntry(EclipseXml.LIB_KIND, convertToEclipsePath(firstRoot, module, entitySource, pathShortener)) + } + + val srcRelativePath: String? + var eclipseSrcVariablePath: String? = null + var addSrcRoots = true + val librarySourceRoots = libraryRoots.filter { it.type.name == OrderRootType.SOURCES.name() } + val firstSrcRoot = librarySourceRoots.firstOrNull()?.url + if (firstSrcRoot == null) { + srcRelativePath = null + } + else { + val firstSrcUrl = firstSrcRoot.url + srcRelativePath = convertToEclipsePath(firstSrcRoot, module, entitySource, pathShortener) + if (eclipseVariablePath != null) { + eclipseSrcVariablePath = eclipseProperties?.getVariable(EclipseModuleManagerImpl.SRC_PREFIX, firstSrcUrl) + if (eclipseSrcVariablePath == null) { + eclipseSrcVariablePath = eclipseProperties?.getVariable(EclipseModuleManagerImpl.SRC_LINK_PREFIX, firstSrcUrl) + } + if (eclipseSrcVariablePath == null) { + eclipseSrcVariablePath = convertToEclipsePathWithVariable(librarySourceRoots) + if (eclipseSrcVariablePath != null) { + eclipseSrcVariablePath = "/$eclipseSrcVariablePath" + } + else { + if (newVarLibrary) { //new library which cannot be replaced with vars + classpathEntry.detach() + classpathEntry = addClasspathEntry(EclipseXml.LIB_KIND, + convertToEclipsePath(firstRoot, module, entitySource, pathShortener)) + } + else { + LOG.info("Added root $srcRelativePath (in existing var library) can't be replaced with any variable; src roots placed in .eml only") + addSrcRoots = false + } + } + } + } + } + EclipseClasspathWriter.setOrRemoveAttribute(classpathEntry, EclipseXml.SOURCEPATH_ATTR, + if (addSrcRoots) eclipseSrcVariablePath ?: srcRelativePath else null) + EJavadocUtil.setupAttributes(classpathEntry, + { convertToEclipseJavadocPath(it, module, entitySource.internalSource.projectLocation, pathShortener) }, + EclipseXml.JAVADOC_LOCATION, + libraryRoots.filter { it.type.name == "JAVADOC" }.map { it.url }.toList().toTypedArray()) + val nativeRoots = libraryRoots.asSequence().filter { it.type.name == "NATIVE" }.map { it.url }.toList().toTypedArray() + if (nativeRoots.isNotEmpty()) { + EJavadocUtil.setupAttributes(classpathEntry, Function { convertToEclipsePath(it, module, entitySource, pathShortener)!! }, EclipseXml.DLL_LINK, + nativeRoots) + } + EclipseClasspathWriter.setExported(classpathEntry, item.exported) + } + } + } + else { + val path = when { + eclipseProperties?.unknownCons?.contains(libraryName) == true -> libraryName + libraryName == IdeaXml.ECLIPSE_LIBRARY -> EclipseXml.ECLIPSE_PLATFORM + else -> EclipseXml.USER_LIBRARY + '/' + libraryName + } + val classpathEntry = addClasspathEntry(EclipseXml.CON_KIND, path) + EclipseClasspathWriter.setExported(classpathEntry, item.exported) + } + + } + ModuleDependencyItem.InheritedSdkDependency -> { + if (eclipseProperties?.forceConfigureJdk != true) { + addClasspathEntry(EclipseXml.CON_KIND, EclipseXml.JRE_CONTAINER) + } + } + is ModuleDependencyItem.SdkDependency -> { + val jdkLink = "${EclipseXml.JRE_CONTAINER}${if (item.sdkType == "JavaSDK") EclipseXml.JAVA_SDK_TYPE else ""}/${item.sdkName}" + addClasspathEntry(EclipseXml.CON_KIND, jdkLink) + } + } + } + + val compilerOutput = module.javaSettings?.compilerOutput + val outputPath = if (compilerOutput != null) { + val linkedPath = eclipseProperties?.getVariable(EclipseModuleManagerImpl.LINK_PREFIX, compilerOutput.url) + linkedPath ?: convertToEclipsePath(compilerOutput, module, entitySource, pathShortener) + } + else { + "bin" + } + eclipseProperties?.knownCons?.forEach { + addClasspathEntry(EclipseXml.CON_KIND, it, eclipseProperties.srcPlace[it] ?: -1) + } + EclipseClasspathWriter.setAttributeIfAbsent(addClasspathEntry(EclipseXml.OUTPUT_KIND, outputPath), EclipseXml.PATH_ATTR, + EclipseXml.BIN_DIR) + + return classpathTag + } +} \ No newline at end of file diff --git a/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EmlFileLoader.kt b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EmlFileLoader.kt new file mode 100644 index 000000000000..76b1b29fe194 --- /dev/null +++ b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EmlFileLoader.kt @@ -0,0 +1,238 @@ +// 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.idea.eclipse.config + +import com.intellij.openapi.components.ExpandMacroToPathMap +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.util.JDOMUtil +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.util.io.FileUtil +import com.intellij.workspaceModel.ide.impl.jps.serialization.ErrorReporter +import com.intellij.workspaceModel.storage.VirtualFileUrlManager +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageBuilder +import com.intellij.workspaceModel.storage.bridgeEntities.* +import org.jdom.Element +import org.jetbrains.idea.eclipse.IdeaXml +import org.jetbrains.idea.eclipse.conversion.EPathUtil +import org.jetbrains.idea.eclipse.conversion.IdeaSpecificSettings +import org.jetbrains.jps.model.serialization.java.JpsJavaModelSerializerExtension +import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer +import org.jetbrains.jps.util.JpsPathUtil +import java.lang.IllegalArgumentException + +/** + * Loads additional module configuration from *.eml file to [ModuleEntity] + */ +internal class EmlFileLoader( + private val module: ModuleEntity, private val builder: WorkspaceEntityStorageBuilder, + private val expandMacroToPathMap: ExpandMacroToPathMap, private val errorReporter: ErrorReporter, + private val virtualFileManager: VirtualFileUrlManager +) { + fun loadEml(emlTag: Element, contentRoot: ContentRootEntity) { + loadLanguageLevel(emlTag) + loadCompilerSettings(emlTag) + loadContentEntries(emlTag, contentRoot) + loadJdkSettings(emlTag) + + loadDependencies(emlTag) + } + + private fun loadDependencies(emlTag: Element) { + val moduleLibraries = module.dependencies.asSequence().filterIsInstance() + .mapNotNull { it.library.resolve(builder) } + .filter { it.tableId is LibraryTableId.ModuleLibraryTableId } + .associateBy { it.name } + val libraryScopes = HashMap() + emlTag.getChildren("lib").forEach { libTag -> + val name = libTag.getAttributeValue("name")!! + libraryScopes[name] = libTag.getScope() + val moduleLibrary = moduleLibraries[name] + if (moduleLibrary != null) { + loadModuleLibrary(libTag, moduleLibrary) + } + } + val moduleScopes = emlTag.getChildren("module").associateBy( + { it.getAttributeValue("name") }, + { it.getScope() } + ) + builder.modifyEntity(ModifiableModuleEntity::class.java, module) { + dependencies = dependencies.map { dep -> + when (dep) { + is ModuleDependencyItem.Exportable.LibraryDependency -> + libraryScopes[dep.library.name]?.let { dep.copy(scope = it) } ?: dep + is ModuleDependencyItem.Exportable.ModuleDependency -> + moduleScopes[dep.module.name]?.let { dep.copy(scope = it) } ?: dep + else -> dep + } + } + } + } + + private fun Element.getScope(): ModuleDependencyItem.DependencyScope { + return getAttributeValue("scope")?.let { + try { + ModuleDependencyItem.DependencyScope.valueOf(it) + } + catch (e: IllegalArgumentException) { + null + } + } ?: ModuleDependencyItem.DependencyScope.COMPILE + } + + private fun loadModuleLibrary(libTag: Element, library: LibraryEntity) { + val eclipseSrcRoot = library.roots.firstOrNull { it.type.name == OrderRootType.SOURCES.name() } + val rootsToRemove = HashSet() + if (eclipseSrcRoot != null && libTag.getChildren(IdeaSpecificSettings.SRCROOT_ATTR).any { rootTag -> + val url = rootTag.getAttributeValue("url") + val bindAttribute = rootTag.getAttributeValue(IdeaSpecificSettings.SRCROOT_BIND_ATTR) + (bindAttribute == null || bindAttribute.toBoolean()) && url != eclipseSrcRoot.url.url && EPathUtil.areUrlsPointTheSame(url, eclipseSrcRoot.url.url) + }) { + rootsToRemove.add(eclipseSrcRoot) + } + + val rootsToAdd = ArrayList() + libTag.getChildren(IdeaSpecificSettings.SRCROOT_ATTR).mapTo(rootsToAdd) { + LibraryRoot(virtualFileManager.fromUrl(it.getAttributeValue("url")!!), + LibraryRootTypeId(OrderRootType.SOURCES.name()), LibraryRoot.InclusionOptions.ROOT_ITSELF) + } + libTag.getChildren(IdeaSpecificSettings.JAVADOCROOT_ATTR).mapTo(rootsToAdd) { + LibraryRoot(virtualFileManager.fromUrl(it.getAttributeValue("url")!!), + LibraryRootTypeId("JAVADOC"), LibraryRoot.InclusionOptions.ROOT_ITSELF) + } + + fun updateRoots(tagName: String, rootType: String) { + libTag.getChildren(tagName).forEach { rootTag -> + val root = expandMacroToPathMap.substitute(rootTag.getAttributeValue(IdeaSpecificSettings.PROJECT_RELATED)!!, SystemInfo.isFileSystemCaseSensitive) + library.roots.forEach { libRoot -> + if (libRoot.type.name == rootType && EPathUtil.areUrlsPointTheSame(root, libRoot.url.url)) { + rootsToRemove.add(libRoot) + rootsToAdd.add(LibraryRoot(virtualFileManager.fromUrl(root), LibraryRootTypeId(rootType), LibraryRoot.InclusionOptions.ROOT_ITSELF)) + } + } + } + } + + updateRoots(IdeaSpecificSettings.RELATIVE_MODULE_SRC, OrderRootType.SOURCES.name()) + updateRoots(IdeaSpecificSettings.RELATIVE_MODULE_CLS, OrderRootType.CLASSES.name()) + updateRoots(IdeaSpecificSettings.RELATIVE_MODULE_JAVADOC, "JAVADOC") + if (rootsToAdd.isNotEmpty() || rootsToRemove.isNotEmpty()) { + builder.modifyEntity(ModifiableLibraryEntity::class.java, library) { + roots = roots - rootsToRemove + rootsToAdd + } + } + } + + private fun loadJdkSettings(emlTag: Element) { + val sdkItem = if (emlTag.getAttributeValue(IdeaSpecificSettings.INHERIT_JDK).toBoolean()) { + ModuleDependencyItem.InheritedSdkDependency + } + else { + emlTag.getAttributeValue("jdk") + ?.let { ModuleDependencyItem.SdkDependency(it, "JavaSDK") } + } + + if (sdkItem != null) { + builder.modifyEntity(ModifiableModuleEntity::class.java, module) { + val newDependencies = dependencies.map { + when (it) { + is ModuleDependencyItem.SdkDependency -> sdkItem + ModuleDependencyItem.InheritedSdkDependency -> sdkItem + else -> it + } + } + dependencies = if (newDependencies.size < dependencies.size) listOf(ModuleDependencyItem.InheritedSdkDependency) + newDependencies + else newDependencies + } + } + } + + private fun loadCompilerSettings(emlTag: Element) { + val javaSettings = module.javaSettings ?: builder.addJavaModuleSettingsEntity(true, true, null, null, module, module.entitySource) + builder.modifyEntity(ModifiableJavaModuleSettingsEntity::class.java, javaSettings) { + val testOutputElement = emlTag.getChild(IdeaXml.OUTPUT_TEST_TAG) + if (testOutputElement != null) { + compilerOutputForTests = testOutputElement.getAttributeValue(IdeaXml.URL_ATTR)?.let { virtualFileManager.fromUrl(it) } + } + + val inheritedOutput = emlTag.getAttributeValue(JpsJavaModelSerializerExtension.INHERIT_COMPILER_OUTPUT_ATTRIBUTE) + if (inheritedOutput.toBoolean()) { + inheritedCompilerOutput = true + } + + excludeOutput = emlTag.getChild(IdeaXml.EXCLUDE_OUTPUT_TAG) != null + } + } + + private fun loadLanguageLevel(emlTag: Element) { + val languageLevelAttribute = emlTag.getAttributeValue("LANGUAGE_LEVEL") + if (languageLevelAttribute != null) { + val languageLevelTag = JDOMUtil.write(Element("component").setAttribute("LANGUAGE_LEVEL", languageLevelAttribute)) + val customImlData = module.customImlData + if (customImlData != null) { + builder.modifyEntity(ModifiableModuleCustomImlDataEntity::class.java, customImlData) { + rootManagerTagCustomData = languageLevelTag + } + } + else { + builder.addModuleCustomImlDataEntity(languageLevelTag, emptyMap(), module, module.entitySource) + } + } + } + + private fun loadContentEntries(emlTag: Element, contentRoot: ContentRootEntity) { + val entriesElements = emlTag.getChildren(IdeaXml.CONTENT_ENTRY_TAG) + if (entriesElements.isNotEmpty()) { + entriesElements.forEach { + val url = virtualFileManager.fromUrl(it.getAttributeValue(IdeaXml.URL_ATTR)!!) + val contentRootEntity = contentRoot.module.contentRoots.firstOrNull { it.url == url } + ?: builder.addContentRootEntity(url, emptyList(), emptyList(), module) + loadContentEntry(it, contentRootEntity) + } + } + else { + loadContentEntry(emlTag, contentRoot) + } + } + + private fun loadContentEntry(contentEntryTag: Element, entity: ContentRootEntity) { + val testSourceFolders = contentEntryTag.getChildren(IdeaXml.TEST_FOLDER_TAG).mapTo(HashSet()) { + it.getAttributeValue(IdeaXml.URL_ATTR) + } + val packagePrefixes = contentEntryTag.getChildren(IdeaXml.PACKAGE_PREFIX_TAG).associateBy( + { it.getAttributeValue(IdeaXml.URL_ATTR)!! }, + { it.getAttributeValue(IdeaXml.PACKAGE_PREFIX_VALUE_ATTR)!! } + ) + for (sourceRoot in entity.sourceRoots) { + val url = sourceRoot.url.url + val isForTests = url in testSourceFolders + if (isForTests != sourceRoot.tests) { + builder.modifyEntity(ModifiableSourceRootEntity::class.java, sourceRoot) { + tests = isForTests + rootType = if (isForTests) JpsModuleRootModelSerializer.JAVA_TEST_ROOT_TYPE_ID else JpsModuleRootModelSerializer.JAVA_SOURCE_ROOT_TYPE_ID + } + } + + val packagePrefix = packagePrefixes[url] + if (packagePrefix != null) { + val javaRootProperties = sourceRoot.asJavaSourceRoot() + if (javaRootProperties != null) { + builder.modifyEntity(ModifiableJavaSourceRootEntity::class.java, javaRootProperties) { + this.packagePrefix = packagePrefix + } + } + else { + builder.addJavaSourceRootEntity(sourceRoot, false, packagePrefix) + } + } + } + + val excludedUrls = contentEntryTag.getChildren(IdeaXml.EXCLUDE_FOLDER_TAG) + .mapNotNull { it.getAttributeValue(IdeaXml.URL_ATTR) } + .filter { FileUtil.isAncestor(entity.url.file!!, JpsPathUtil.urlToFile(it), false) } + .map { virtualFileManager.fromPath(it) } + if (excludedUrls.isNotEmpty()) { + builder.modifyEntity(ModifiableContentRootEntity::class.java, entity) { + this.excludedUrls = this.excludedUrls + excludedUrls + } + } + } +} \ No newline at end of file diff --git a/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EmlFileSaver.kt b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EmlFileSaver.kt new file mode 100644 index 000000000000..9ee7126a79a5 --- /dev/null +++ b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/EmlFileSaver.kt @@ -0,0 +1,169 @@ +// 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.idea.eclipse.config + +import com.intellij.openapi.components.PathMacroMap +import com.intellij.openapi.roots.JavadocOrderRootType +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar +import com.intellij.openapi.util.JDOMUtil +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.vfs.JarFileSystem +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.workspaceModel.ide.impl.jps.serialization.getLegacyLibraryName +import com.intellij.workspaceModel.ide.impl.virtualFile +import com.intellij.workspaceModel.storage.WorkspaceEntity +import com.intellij.workspaceModel.storage.bridgeEntities.* +import org.jdom.Element +import org.jetbrains.annotations.NonNls +import org.jetbrains.idea.eclipse.IdeaXml.* +import org.jetbrains.idea.eclipse.conversion.EPathUtil +import org.jetbrains.idea.eclipse.conversion.IdeaSpecificSettings +import org.jetbrains.jps.model.serialization.java.JpsJavaModelSerializerExtension + +/** + * Saves additional module configuration from [ModuleEntity] to *.eml file + */ +internal class EmlFileSaver(private val module: ModuleEntity, + private val entities: Map, List>, + private val pathShortener: ModulePathShortener, + private val replacePathMacroMap: PathMacroMap) { + fun saveEml(): Element? { + val root = Element(COMPONENT_TAG) + saveCustomJavaSettings(root) + saveContentRoots(root) + + @Suppress("UNCHECKED_CAST") + val moduleLibraries = (entities[LibraryEntity::class.java] as List? ?: emptyList()).associateBy { it.name } + val libLevels = LinkedHashMap() + module.dependencies.forEach { dep -> + when (dep) { + is ModuleDependencyItem.Exportable.ModuleDependency -> { + if (dep.scope != ModuleDependencyItem.DependencyScope.COMPILE) { + root.addContent(Element("module").setAttribute("name", dep.module.name).setAttribute("scope", dep.scope.name)) + } + } + is ModuleDependencyItem.InheritedSdkDependency -> { + //todo don't set if sdk is unresolved + root.setAttribute(IdeaSpecificSettings.INHERIT_JDK, true.toString()) + } + is ModuleDependencyItem.SdkDependency -> { + //todo don't set if sdk is unresolved + root.setAttribute("jdk", dep.sdkName) + root.setAttribute("jdk_type", dep.sdkType) + } + is ModuleDependencyItem.Exportable.LibraryDependency -> { + val libTag = Element("lib") + val library = moduleLibraries[dep.library.name] + val libName = getLegacyLibraryName(dep.library) ?: generateLibName(library) + libTag.setAttribute("name", libName) + libTag.setAttribute("scope", dep.scope.name) + when (val tableId = dep.library.tableId) { + is LibraryTableId.ModuleLibraryTableId -> { + if (library != null) { + val srcRoots = library.roots.filter { it.type.name == OrderRootType.SOURCES.name() } + val eclipseUrl = srcRoots.firstOrNull()?.url?.url?.substringBefore(JarFileSystem.JAR_SEPARATOR) + srcRoots.forEach { + val url = it.url.url + val srcTag = Element(IdeaSpecificSettings.SRCROOT_ATTR).setAttribute("url", url) + if (!EPathUtil.areUrlsPointTheSame(url, eclipseUrl)) { + srcTag.setAttribute(IdeaSpecificSettings.SRCROOT_BIND_ATTR, false.toString()) + } + libTag.addContent(srcTag) + } + library.roots.filter { it.type.name == "JAVADOC" }.forEach { + libTag.addContent(Element(IdeaSpecificSettings.JAVADOCROOT_ATTR).setAttribute("url", it.url.url)) + } + saveModuleRelatedRoots(libTag, library, OrderRootType.SOURCES, IdeaSpecificSettings.RELATIVE_MODULE_SRC) + saveModuleRelatedRoots(libTag, library, OrderRootType.CLASSES, IdeaSpecificSettings.RELATIVE_MODULE_CLS) + saveModuleRelatedRoots(libTag, library, JavadocOrderRootType.getInstance(), IdeaSpecificSettings.RELATIVE_MODULE_JAVADOC) + } + } + is LibraryTableId.ProjectLibraryTableId -> libLevels[dep.library.name] = LibraryTablesRegistrar.PROJECT_LEVEL + is LibraryTableId.GlobalLibraryTableId -> { + if (tableId.level != LibraryTablesRegistrar.APPLICATION_LEVEL) { + libLevels[dep.library.name] = tableId.level + } + } + } + if (libTag.children.isNotEmpty() || dep.scope != ModuleDependencyItem.DependencyScope.COMPILE) { + root.addContent(libTag) + } + } + } + } + if (libLevels.isNotEmpty()) { + val levelsTag = Element("levels") + for ((name, level) in libLevels) { + levelsTag.addContent(Element("level").setAttribute("name", name).setAttribute("value", level)) + } + root.addContent(levelsTag) + } + + replacePathMacroMap.substitute(root, SystemInfo.isFileSystemCaseSensitive) + return if (JDOMUtil.isEmpty(root)) null else root + } + + private fun saveModuleRelatedRoots(libTag: Element, library: LibraryEntity, type: OrderRootType, tagName: @NonNls String) { + library.roots.filter { it.type.name == type.name() }.forEach { + if (pathShortener.isUnderContentRoots(it.url)) { + libTag.addContent(Element(tagName).setAttribute(IdeaSpecificSettings.PROJECT_RELATED, it.url.url)) + } + } + } + + private fun generateLibName(library: LibraryEntity?): String { + val firstRoot = library?.roots?.firstOrNull { it.type.name == OrderRootType.CLASSES.name() }?.url + val file = firstRoot?.virtualFile + val fileForJar = JarFileSystem.getInstance().getVirtualFileForJar(file) + if (fileForJar != null) return fileForJar.name + return file?.name ?: "Empty Library" + } + + private fun saveContentRoots(root: Element) { + module.contentRoots.forEach { contentRoot -> + val contentRootTag = Element(CONTENT_ENTRY_TAG).setAttribute(URL_ATTR, contentRoot.url.url) + contentRoot.sourceRoots.forEach { sourceRoot -> + if (sourceRoot.tests) { + contentRootTag.addContent(Element(TEST_FOLDER_TAG).setAttribute(URL_ATTR, sourceRoot.url.url)) + } + val packagePrefix = sourceRoot.asJavaSourceRoot()?.packagePrefix + if (!packagePrefix.isNullOrEmpty()) { + contentRootTag.addContent(Element(PACKAGE_PREFIX_TAG).setAttribute(URL_ATTR, sourceRoot.url.url) + .setAttribute(PACKAGE_PREFIX_VALUE_ATTR, packagePrefix)) + } + } + val rootFile = contentRoot.url.virtualFile + contentRoot.excludedUrls.forEach { excluded -> + val excludedFile = excluded.virtualFile + if (rootFile == null || excludedFile == null || VfsUtilCore.isAncestor(rootFile, excludedFile, false)) { + contentRootTag.addContent(Element(EXCLUDE_FOLDER_TAG).setAttribute(URL_ATTR, excluded.url)) + } + } + if (!JDOMUtil.isEmpty(contentRootTag)) { + root.addContent(contentRootTag) + } + } + } + + private fun saveCustomJavaSettings(root: Element) { + module.javaSettings?.let { javaSettings -> + javaSettings.compilerOutputForTests?.let { testOutput -> + root.addContent(Element(OUTPUT_TEST_TAG).setAttribute(URL_ATTR, testOutput.url)) + } + if (javaSettings.inheritedCompilerOutput) { + root.setAttribute(JpsJavaModelSerializerExtension.INHERIT_COMPILER_OUTPUT_ATTRIBUTE, true.toString()) + } + if (javaSettings.excludeOutput) { + root.addContent(Element(EXCLUDE_OUTPUT_TAG)) + } + } + + module.customImlData?.rootManagerTagCustomData?.let { languageLevelTagString -> + val tag = JDOMUtil.load(languageLevelTagString) + tag.getAttributeValue("LANGUAGE_LEVEL")?.let { + root.setAttribute("LANGUAGE_LEVEL", it) + } + } + } + +} \ No newline at end of file diff --git a/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/eclipseEntities.kt b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/eclipseEntities.kt new file mode 100644 index 000000000000..4b22f78db99b --- /dev/null +++ b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/eclipseEntities.kt @@ -0,0 +1,93 @@ +// 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.idea.eclipse.config + +import com.intellij.workspaceModel.ide.JpsFileDependentEntitySource +import com.intellij.workspaceModel.ide.JpsFileEntitySource +import com.intellij.workspaceModel.storage.EntitySource +import com.intellij.workspaceModel.storage.VirtualFileUrl +import com.intellij.workspaceModel.storage.WorkspaceEntityStorage +import com.intellij.workspaceModel.storage.WorkspaceEntityStorageDiffBuilder +import com.intellij.workspaceModel.storage.bridgeEntities.ModuleEntity +import com.intellij.workspaceModel.storage.impl.EntityDataDelegation +import com.intellij.workspaceModel.storage.impl.ModifiableWorkspaceEntityBase +import com.intellij.workspaceModel.storage.impl.WorkspaceEntityBase +import com.intellij.workspaceModel.storage.impl.WorkspaceEntityData +import com.intellij.workspaceModel.storage.impl.references.ManyToOne +import com.intellij.workspaceModel.storage.impl.references.MutableOneToOneChild +import com.intellij.workspaceModel.storage.impl.references.OneToOneParent + +data class EclipseProjectFile( + val classpathFile: VirtualFileUrl, + val internalSource: JpsFileEntitySource +) : EntitySource, JpsFileDependentEntitySource { + override val originalSource: JpsFileEntitySource + get() = internalSource +} + +/** + * Stores data from [EclipseModuleManagerImpl] in workspace model + */ +class EclipseProjectPropertiesEntity( + val variablePaths: Map, + val eclipseUrls: Set, + val unknownCons: Set, + val knownCons: Set, + val forceConfigureJdk: Boolean, + val expectedModuleSourcePlace: Int, + val srcPlace: Map, + val invalidJdk: String? + +) : WorkspaceEntityBase() { + val module: ModuleEntity by moduleDelegate + + companion object { + val moduleDelegate = ManyToOne.NotNull(ModuleEntity::class.java) + } +} + +@Suppress("unused") //created via reflection in ClassConversion.entityToEntityData +class EclipseProjectPropertiesEntityData : WorkspaceEntityData() { + lateinit var variablePaths: Map + lateinit var eclipseUrls: Set + lateinit var unknownCons: Set + lateinit var knownCons: Set + var forceConfigureJdk = false + var expectedModuleSourcePlace = 0 + lateinit var srcPlace: Map + var invalidJdk: String? = null + + override fun createEntity(snapshot: WorkspaceEntityStorage): EclipseProjectPropertiesEntity { + return EclipseProjectPropertiesEntity(variablePaths, eclipseUrls, unknownCons, knownCons, forceConfigureJdk, expectedModuleSourcePlace, srcPlace, invalidJdk).also { addMetaData(it, snapshot) } + } +} + +class ModifiableEclipseProjectPropertiesEntity : ModifiableWorkspaceEntityBase() { + var variablePaths: MutableMap by EntityDataDelegation() + var eclipseUrls: MutableSet by EntityDataDelegation() + var unknownCons: MutableSet by EntityDataDelegation() + var knownCons: MutableSet by EntityDataDelegation() + var forceConfigureJdk: Boolean by EntityDataDelegation() + var expectedModuleSourcePlace: Int by EntityDataDelegation() + var srcPlace: MutableMap by EntityDataDelegation() + var invalidJdk: String? by EntityDataDelegation() + var module: ModuleEntity by MutableOneToOneChild.NotNull(EclipseProjectPropertiesEntity::class.java, ModuleEntity::class.java, true) +} + +fun WorkspaceEntityStorageDiffBuilder.addEclipseProjectPropertiesEntity(module: ModuleEntity, source: EntitySource) + = addEntity(ModifiableEclipseProjectPropertiesEntity::class.java, source) { + this.module = module + variablePaths = LinkedHashMap() + eclipseUrls = LinkedHashSet() + unknownCons = LinkedHashSet() + knownCons = LinkedHashSet() + srcPlace = LinkedHashMap() +} + +private val eclipsePropertiesDelegate = OneToOneParent.Nullable(EclipseProjectPropertiesEntity::class.java, false) +val ModuleEntity.eclipseProperties: EclipseProjectPropertiesEntity? by eclipsePropertiesDelegate + +fun ModifiableEclipseProjectPropertiesEntity.setVariable(kind: String, name: String, path: String) { + variablePaths[kind + path] = name +} + +fun EclipseProjectPropertiesEntity.getVariable(kind: String, path: String): String? = variablePaths[kind + path] \ No newline at end of file diff --git a/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/pathConversion.kt b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/pathConversion.kt new file mode 100644 index 000000000000..f8940a847f18 --- /dev/null +++ b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/pathConversion.kt @@ -0,0 +1,275 @@ +// 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.idea.eclipse.config + +import com.intellij.openapi.application.PathMacros +import com.intellij.openapi.components.ExpandMacroToPathMap +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.roots.impl.ProjectRootManagerImpl +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.io.OSAgnosticPathUtil +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.* +import com.intellij.openapi.vfs.ex.http.HttpFileSystem +import com.intellij.workspaceModel.ide.JpsProjectConfigLocation +import com.intellij.workspaceModel.ide.impl.virtualFile +import com.intellij.workspaceModel.storage.VirtualFileUrl +import com.intellij.workspaceModel.storage.VirtualFileUrlManager +import com.intellij.workspaceModel.storage.append +import com.intellij.workspaceModel.storage.bridgeEntities.ContentRootEntity +import com.intellij.workspaceModel.storage.bridgeEntities.LibraryRoot +import com.intellij.workspaceModel.storage.bridgeEntities.ModuleEntity +import org.jetbrains.idea.eclipse.AbstractEclipseClasspathReader +import org.jetbrains.idea.eclipse.EPathCommonUtil +import org.jetbrains.idea.eclipse.EclipseXml +import org.jetbrains.idea.eclipse.conversion.EJavadocUtil +import org.jetbrains.jps.util.JpsPathUtil +import java.io.File + +private val LOG = logger() + +internal fun convertToJavadocUrl(originalPath: String, + moduleEntity: ModuleEntity, + relativePathResolver: ModuleRelativePathResolver, + virtualUrlManager: VirtualFileUrlManager): VirtualFileUrl { + val javadocPath = if (!SystemInfo.isWindows) { + originalPath.replaceFirst(EclipseXml.FILE_PROTOCOL, EclipseXml.FILE_PROTOCOL + "/") + } + else { + originalPath + } + + if (javadocPath.startsWith(EclipseXml.FILE_PROTOCOL)) { + val path = javadocPath.removePrefix(EclipseXml.FILE_PROTOCOL) + if (File(path).exists()) { + return virtualUrlManager.fromUrl(pathToUrl(path)) + } + } + else { + val protocol = VirtualFileManager.extractProtocol(javadocPath) + if (protocol == HttpFileSystem.getInstance().protocol) { + return virtualUrlManager.fromUrl(javadocPath) + } + else if (javadocPath.startsWith(EclipseXml.JAR_PREFIX)) { + val jarJavadocPath = javadocPath.removePrefix(EclipseXml.JAR_PREFIX) + if (jarJavadocPath.startsWith(EclipseXml.PLATFORM_PROTOCOL)) { + val relativeToPlatform = jarJavadocPath.substring(EclipseXml.PLATFORM_PROTOCOL.length + "resource".length) // starts with leading / + val currentRoot = moduleEntity.mainContentRoot?.url?.virtualFile + val basePath = currentRoot?.parent?.path ?: + JpsPathUtil.urlToPath((moduleEntity.entitySource as EclipseProjectFile).internalSource.projectLocation.baseDirectoryUrl.url) + val currentModulePath = basePath + relativeToPlatform + if (EJavadocUtil.isJarFileExist(currentModulePath)) { + return virtualUrlManager.fromUrl(VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, currentModulePath)) + } + else { + val moduleName = EPathCommonUtil.getRelativeModuleName(relativeToPlatform) + val relativeToModulePathWithJarSuffix = EPathCommonUtil.getRelativeToModulePath(relativeToPlatform) + val relativeToModulePath = EJavadocUtil.stripPathInsideJar(relativeToModulePathWithJarSuffix) + var url: String? = null + if (moduleName != moduleEntity.name) { + url = relativePathResolver.resolve(moduleName, relativeToModulePath) + } + if (url != null) { + assert(relativeToModulePathWithJarSuffix != null) + assert(relativeToModulePath != null) + if (relativeToModulePath!!.length < relativeToModulePathWithJarSuffix!!.length) { + url += relativeToModulePathWithJarSuffix.substring(relativeToModulePath.length) + } + return virtualUrlManager.fromUrl(VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, VfsUtil.urlToPath(url))) + } + } + } + else if (jarJavadocPath.startsWith(EclipseXml.FILE_PROTOCOL)) { + val localFile = jarJavadocPath.substring(EclipseXml.FILE_PROTOCOL.length) + if (EJavadocUtil.isJarFileExist(localFile)) { + return virtualUrlManager.fromUrl(VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, localFile)) + } + } + } + } + return virtualUrlManager.fromUrl(javadocPath) +} + +internal fun convertToEclipseJavadocPath(javadocRoot: VirtualFileUrl, + module: ModuleEntity, + projectLocation: JpsProjectConfigLocation, + pathShortener: ModulePathShortener): String? { + val javadocUrl = javadocRoot.url + val protocol = VirtualFileManager.extractProtocol(javadocUrl) + if (protocol != HttpFileSystem.getInstance().protocol) { + val path = VfsUtil.urlToPath(javadocUrl) + val contentRoot = module.mainContentRoot?.url?.virtualFile + val baseDir = if (contentRoot != null) contentRoot.parent else projectLocation.baseDirectoryUrl.virtualFile + if (protocol == JarFileSystem.getInstance().protocol) { + val javadocFile = JarFileSystem.getInstance().getVirtualFileForJar(javadocRoot.virtualFile) + if (javadocFile != null) { + val relativeUrl: String? = if (contentRoot != null && baseDir != null && VfsUtilCore.isAncestor(contentRoot, javadocFile, false)) { + "/" + VfsUtilCore.getRelativePath(javadocFile, baseDir, '/') + } + else { + pathShortener.shortenPath(javadocRoot) + } + if (relativeUrl != null) { + var javadocPath = javadocUrl + if (!javadocPath.contains(JarFileSystem.JAR_SEPARATOR)) { + javadocPath = StringUtil.trimEnd(javadocPath, "/") + JarFileSystem.JAR_SEPARATOR + } + return EclipseXml.JAR_PREFIX + EclipseXml.PLATFORM_PROTOCOL + "resource" + relativeUrl + + javadocPath.substring(javadocFile.url.length - 1) + } + else { + return EclipseXml.JAR_PREFIX + EclipseXml.FILE_PROTOCOL + path.removePrefix("/") + } + } + else { + return EclipseXml.JAR_PREFIX + EclipseXml.FILE_PROTOCOL + path.removePrefix("/") + } + } + else if (File(path).exists()) { + return EclipseXml.FILE_PROTOCOL + path.removePrefix("/") + } + } + return javadocUrl +} + + +internal fun convertToRootUrl(path: String, virtualUrlManager: VirtualFileUrlManager): VirtualFileUrl { + val url = pathToUrl(path) + val localFile = VirtualFileManager.getInstance().findFileByUrl(path) + if (localFile != null) { + val jarFile = JarFileSystem.getInstance().getJarRootForLocalFile(localFile) + if (jarFile != null) { + return virtualUrlManager.fromUrl(jarFile.url) + } + } + + return virtualUrlManager.fromUrl(url) +} + +internal fun pathToUrl(path: String): String = JpsPathUtil.pathToUrl(FileUtil.toSystemIndependentName(path)) + +internal fun convertVariablePathToUrl(pathMap: ExpandMacroToPathMap, + path: String?, + varStart: Int, + virtualUrlManager: VirtualFileUrlManager): VirtualFileUrl { + val variable = AbstractEclipseClasspathReader.createEPathVariable(path, varStart) + val url = pathToUrl(AbstractEclipseClasspathReader.getVariableRelatedPath(variable.variable, variable.relatedPath)) + return convertToRootUrl(pathMap.substitute(url, SystemInfo.isFileSystemCaseSensitive), virtualUrlManager) +} + +internal fun convertRelativePathToUrl(path: String, + contentRootEntity: ContentRootEntity, + pathResolver: ModuleRelativePathResolver, + virtualUrlManager: VirtualFileUrlManager): VirtualFileUrl { + if (!File(path).exists()) { + if (path.startsWith("/")) { + //relative to other project + val moduleName = EPathCommonUtil.getRelativeModuleName(path) + val relativeToRootPath = EPathCommonUtil.getRelativeToModulePath(path) + if (moduleName != contentRootEntity.module.name) { + val url = pathResolver.resolve(moduleName, relativeToRootPath) + if (url != null) { + return virtualUrlManager.fromUrl(url) + } + } + } + else { + val url = findFileUnderContentRoot(path, contentRootEntity, virtualUrlManager) + if (url != null) { + return url + } + } + } + return convertToRootUrl(path, virtualUrlManager) +} + +private fun findFileUnderContentRoot(path: String, + contentRootEntity: ContentRootEntity, + virtualUrlManager: VirtualFileUrlManager): VirtualFileUrl? { + val root = contentRootEntity.url.file + val mainRoot = if (root?.exists() == true) root + else contentRootEntity.module.mainContentRoot?.url?.file + val file = File(mainRoot, path) + return if (file.exists()) convertToRootUrl(file.absolutePath, virtualUrlManager) else null +} + +internal val ModuleEntity.mainContentRoot: ContentRootEntity? + get() = contentRoots.firstOrNull { + VirtualFileManager.getInstance().findFileByUrl(it.url.url)?.findChild(EclipseXml.PROJECT_FILE) != null + } + +internal fun getStorageRoot(imlFileUrl: VirtualFileUrl, customDir: String?, virtualFileManager: VirtualFileUrlManager): VirtualFileUrl { + val moduleRoot = imlFileUrl.parent!! + if (customDir == null) return moduleRoot + if (OSAgnosticPathUtil.isAbsolute(customDir)) { + return virtualFileManager.fromPath(customDir) + } + return moduleRoot.append(customDir) +} + +internal fun convertToEclipsePath(fileUrl: VirtualFileUrl, + moduleEntity: ModuleEntity, + entitySource: EclipseProjectFile, + pathShortener: ModulePathShortener): String? { + val contentRoot = moduleEntity.mainContentRoot?.let { VirtualFileManager.getInstance().findFileByUrl(it.url.url) } + var file = fileUrl.virtualFile + val url = fileUrl.url + if (file != null) { + LOG.assertTrue(file.isValid) + if (file.fileSystem is JarFileSystem) { + val jarFile = JarFileSystem.getInstance().getVirtualFileForJar(file) + if (jarFile == null) { + LOG.error("Url: '$url'; file: $file") + return ProjectRootManagerImpl.extractLocalPath(url) + } + file = jarFile + } + if (contentRoot != null && VfsUtilCore.isAncestor(contentRoot, file, false)) { //inside current project + return VfsUtilCore.getRelativePath(file, contentRoot, '/') + } + else { + val path = pathShortener.shortenPath(fileUrl) + if (path != null) { + return path + } + } + } + else { //try to avoid absolute path for deleted file + if (contentRoot != null) { + val rootUrl = contentRoot.url + if (url.startsWith(rootUrl) && url.length > rootUrl.length) { + return url.substring(rootUrl.length + 1) //without leading / + } + } + val projectBaseDir = if (contentRoot != null) contentRoot.parent else entitySource.internalSource.projectLocation.baseDirectoryUrl.virtualFile!! + val projectUrl = projectBaseDir.url + if (url.startsWith(projectUrl)) { + return url.substring(projectUrl.length) //leading / + } + val path = VfsUtilCore.urlToPath(url) + val projectPath = projectBaseDir.path + if (path.startsWith(projectPath)) { + return ProjectRootManagerImpl.extractLocalPath(path.substring(projectPath.length)) + } + } + return ProjectRootManagerImpl.extractLocalPath(url) //absolute path +} + +fun convertToEclipsePathWithVariable(roots: List): String? { + roots.forEach { root -> + var filePath = VirtualFileManager.extractPath(root.url.url) + val jarSeparatorIdx = filePath.indexOf(JarFileSystem.JAR_SEPARATOR) + if (jarSeparatorIdx > -1) { + filePath = filePath.substring(0, jarSeparatorIdx) + } + val pathMacros = PathMacros.getInstance().userMacros + for (name in pathMacros.keys) { + val path = FileUtil.toSystemIndependentName(pathMacros[name]!!) + if (filePath.startsWith("$path/")) { + val substr = filePath.substring(path.length) + return name + if (substr.startsWith("/") || substr.isEmpty()) substr else "/$substr" + } + } + } + return null +} diff --git a/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/relativeModulePathSupport.kt b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/relativeModulePathSupport.kt new file mode 100644 index 000000000000..a0d946cc4849 --- /dev/null +++ b/plugins/eclipse/src/org/jetbrains/idea/eclipse/config/relativeModulePathSupport.kt @@ -0,0 +1,58 @@ +// 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.idea.eclipse.config + +import com.intellij.openapi.module.impl.getModuleNameByFilePath +import com.intellij.workspaceModel.ide.impl.jps.serialization.JpsFileContentReader +import com.intellij.workspaceModel.ide.impl.jps.serialization.JpsModuleListSerializer +import com.intellij.workspaceModel.storage.VirtualFileUrl +import com.intellij.workspaceModel.storage.VirtualFileUrlManager +import com.intellij.workspaceModel.storage.WorkspaceEntityStorage +import com.intellij.workspaceModel.storage.bridgeEntities.ContentRootEntity +import org.jetbrains.jps.model.serialization.JpsProjectLoader +import org.jetbrains.jps.util.JpsPathUtil + +class ModuleRelativePathResolver(private val moduleListSerializer: JpsModuleListSerializer?, + private val reader: JpsFileContentReader, + private val virtualFileManager: VirtualFileUrlManager) { + private val moduleFileUrls by lazy { + (moduleListSerializer?.loadFileList(reader, virtualFileManager) ?: emptyList()).associateBy( + { getModuleNameByFilePath(JpsPathUtil.urlToPath(it.first.url)) }, + { it.first } + ) + } + + fun resolve(moduleName: String, relativePath: String?): String? { + val moduleFile = moduleFileUrls[moduleName] ?: return null + val component = reader.loadComponent(moduleFile.url, "DeprecatedModuleOptionManager", null) + val baseDir = component?.getChildren("option") + ?.firstOrNull { it.getAttributeValue("key") == JpsProjectLoader.CLASSPATH_DIR_ATTRIBUTE } + ?.getAttributeValue("value") + val storageRoot = getStorageRoot(moduleFile, baseDir, virtualFileManager) + if (relativePath.isNullOrEmpty()) return storageRoot.url + return "${storageRoot.url}/${relativePath.removePrefix("/")}" + } +} + +class ModulePathShortener(private val storage: WorkspaceEntityStorage) { + private val contentRootsToModule by lazy { + storage.entities(ContentRootEntity::class.java).associateBy({ it.url }, { it.module.name }) + } + + fun shortenPath(url: VirtualFileUrl): String? { + var current: VirtualFileUrl? = url + val map = contentRootsToModule + while (current != null) { + val moduleName = map[current] + if (moduleName != null) { + return "/$moduleName${url.url.substring(current.url.length)}" + } + current = current.parent + } + return null + } + + fun isUnderContentRoots(url: VirtualFileUrl): Boolean { + val map = contentRootsToModule + return generateSequence(url, {it.parent}).any { it in map } + } +} diff --git a/plugins/eclipse/src/org/jetbrains/idea/eclipse/conversion/EJavadocUtil.java b/plugins/eclipse/src/org/jetbrains/idea/eclipse/conversion/EJavadocUtil.java index a450f70dce27..55095bf2f876 100644 --- a/plugins/eclipse/src/org/jetbrains/idea/eclipse/conversion/EJavadocUtil.java +++ b/plugins/eclipse/src/org/jetbrains/idea/eclipse/conversion/EJavadocUtil.java @@ -117,7 +117,7 @@ public final class EJavadocUtil { } @Nullable - private static String stripPathInsideJar(@Nullable String relativeToModulePathWithJarSuffix) { + public static String stripPathInsideJar(@Nullable String relativeToModulePathWithJarSuffix) { String relativeToModulePath = relativeToModulePathWithJarSuffix; if (relativeToModulePath != null) { int jarSufIdx = relativeToModulePathWithJarSuffix.indexOf(JarFileSystem.JAR_SEPARATOR); @@ -128,7 +128,7 @@ public final class EJavadocUtil { return relativeToModulePath; } - static boolean isJarFileExist(String path) { + public static boolean isJarFileExist(String path) { final int jarSufIdx = path.indexOf(JarFileSystem.JAR_SEPARATOR); if (jarSufIdx != -1) { path = path.substring(0, jarSufIdx); @@ -188,10 +188,10 @@ public final class EJavadocUtil { setupAttributes(orderEntry, s -> toEclipseJavadocPath(model, s), JAVADOC_LOCATION, libraryOrderEntry.getRootUrls(JavadocOrderRootType.getInstance())); } - static void setupAttributes(Element orderEntry, - Function fun, - String attributeName, - String[] roots) { + public static void setupAttributes(Element orderEntry, + Function fun, + String attributeName, + T[] roots) { final List eclipseUrls = new ArrayList<>(); if (roots.length > 0) { eclipseUrls.add(fun.fun(roots[0])); diff --git a/plugins/eclipse/src/org/jetbrains/idea/eclipse/conversion/EclipseClasspathReader.java b/plugins/eclipse/src/org/jetbrains/idea/eclipse/conversion/EclipseClasspathReader.java index 4d4df43d70a3..03824769228b 100644 --- a/plugins/eclipse/src/org/jetbrains/idea/eclipse/conversion/EclipseClasspathReader.java +++ b/plugins/eclipse/src/org/jetbrains/idea/eclipse/conversion/EclipseClasspathReader.java @@ -271,7 +271,7 @@ public class EclipseClasspathReader extends AbstractEclipseClasspathReader oldEntries) { + return addOrderEntry(kind, path, classpathRoot, -1, oldEntries); + } + + public static Element addOrderEntry(@NotNull String kind, String path, Element classpathRoot, int index, Map oldEntries) { + Element element = getOldElement(kind, path, oldEntries); if (element != null) { Element clonedElement = element.clone(); if (index == -1 || index >= classpathRoot.getContentSize()) { @@ -252,19 +256,23 @@ public class EclipseClasspathWriter { return orderEntry; } - private Element getOldElement(@NotNull String kind, String path) { - return myOldEntries.get(kind + getJREKey(path)); + public static Element getOldElement(@NotNull String kind, String path, Map entries) { + return entries.get(kind + getJREKey(path)); } - private static String getJREKey(String path) { + public static String getJREKey(String path) { return path.startsWith(EclipseXml.JRE_CONTAINER) ? EclipseXml.JRE_CONTAINER : path; } private static void setExported(Element orderEntry, ExportableOrderEntry dependency) { - setOrRemoveAttribute(orderEntry, EclipseXml.EXPORTED_ATTR, dependency.isExported() ? EclipseXml.TRUE_VALUE : null); + setExported(orderEntry, dependency.isExported()); } - private static void setOrRemoveAttribute(@NotNull Element element, @NotNull String name, @Nullable String value) { + public static void setExported(Element orderEntry, boolean exported) { + setOrRemoveAttribute(orderEntry, EclipseXml.EXPORTED_ATTR, exported ? EclipseXml.TRUE_VALUE : null); + } + + public static void setOrRemoveAttribute(@NotNull Element element, @NotNull String name, @Nullable String value) { if (value == null) { element.removeAttribute(name); } @@ -273,7 +281,7 @@ public class EclipseClasspathWriter { } } - private static void setAttributeIfAbsent(@NotNull Element element, String name, String value) { + public static void setAttributeIfAbsent(@NotNull Element element, String name, String value) { if (element.getAttribute(name) == null) { element.setAttribute(name, value); } diff --git a/plugins/eclipse/src/org/jetbrains/idea/eclipse/conversion/IdeaSpecificSettings.java b/plugins/eclipse/src/org/jetbrains/idea/eclipse/conversion/IdeaSpecificSettings.java index 11fa90578902..769457e222c1 100644 --- a/plugins/eclipse/src/org/jetbrains/idea/eclipse/conversion/IdeaSpecificSettings.java +++ b/plugins/eclipse/src/org/jetbrains/idea/eclipse/conversion/IdeaSpecificSettings.java @@ -39,15 +39,15 @@ import static org.jetbrains.idea.eclipse.conversion.EPathUtil.areUrlsPointTheSam * Read/write .eml */ public class IdeaSpecificSettings extends AbstractIdeaSpecificSettings { - @NonNls private static final String RELATIVE_MODULE_SRC = "relative-module-src"; - @NonNls private static final String RELATIVE_MODULE_CLS = "relative-module-cls"; - @NonNls private static final String RELATIVE_MODULE_JAVADOC = "relative-module-javadoc"; - @NonNls private static final String PROJECT_RELATED = "project-related"; + @NonNls public static final String RELATIVE_MODULE_SRC = "relative-module-src"; + @NonNls public static final String RELATIVE_MODULE_CLS = "relative-module-cls"; + @NonNls public static final String RELATIVE_MODULE_JAVADOC = "relative-module-javadoc"; + @NonNls public static final String PROJECT_RELATED = "project-related"; - @NonNls private static final String SRCROOT_ATTR = "srcroot"; - @NonNls private static final String SRCROOT_BIND_ATTR = "bind"; + @NonNls public static final String SRCROOT_ATTR = "srcroot"; + @NonNls public static final String SRCROOT_BIND_ATTR = "bind"; private static final Logger LOG = Logger.getInstance(IdeaSpecificSettings.class); - @NonNls private static final String JAVADOCROOT_ATTR = "javadocroot_attr"; + @NonNls public static final String JAVADOCROOT_ATTR = "javadocroot_attr"; public static final String INHERIT_JDK = "inheritJdk"; @Override diff --git a/plugins/eclipse/testSources/org/jetbrains/idea/eclipse/EclipseClasspathTest.kt b/plugins/eclipse/testSources/org/jetbrains/idea/eclipse/EclipseClasspathTest.kt index 2a2c48692d97..c7a450f40514 100644 --- a/plugins/eclipse/testSources/org/jetbrains/idea/eclipse/EclipseClasspathTest.kt +++ b/plugins/eclipse/testSources/org/jetbrains/idea/eclipse/EclipseClasspathTest.kt @@ -23,13 +23,13 @@ import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.TemporaryDirectory import com.intellij.testFramework.assertions.Assertions import com.intellij.testFramework.loadProjectAndCheckResults -import com.intellij.testFramework.rules.ProjectModelRule import com.intellij.util.io.assertMatches import com.intellij.util.io.directoryContentOf +import com.intellij.workspaceModel.ide.WorkspaceModel +import com.intellij.workspaceModel.ide.impl.jps.serialization.JpsProjectModelSynchronizer import org.jetbrains.idea.eclipse.config.EclipseClasspathStorageProvider import org.jetbrains.idea.eclipse.conversion.EclipseClasspathReader import org.jetbrains.idea.eclipse.conversion.EclipseClasspathWriter -import org.junit.Assume import org.junit.ClassRule import org.junit.Rule import org.junit.Test @@ -164,8 +164,6 @@ class EclipseClasspathTest { } fun doTest(relativePath: String) { - //todo loading and saving in Eclipse format isn't implemented in workspace model yet (IDEA-246236) - Assume.assumeFalse(ProjectModelRule.isWorkspaceModelEnabled) val testDataRoot = PluginPathManager.getPluginHome("eclipse").toPath().resolve("testData/round") val testRoot = testDataRoot.resolve(testName.methodName.removePrefix("test").decapitalize()).resolve(relativePath) val commonRoot = testDataRoot.resolve("common") @@ -175,6 +173,9 @@ class EclipseClasspathTest { it.moduleFile!!.delete(this) it.stateStore.clearCaches() } + if (WorkspaceModel.isEnabled) { + JpsProjectModelSynchronizer.getInstance(project)!!.markAllEntitiesAsDirty() + } } project.stateStore.save(true) val baseDir = Paths.get(project.basePath!!)