[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
This commit is contained in:
Nikolay Chashnikov
2020-09-28 15:18:55 +00:00
committed by intellij-monorepo-bot
parent d8cf763564
commit d7136f71ee
18 changed files with 1592 additions and 58 deletions
@@ -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
@@ -112,7 +112,7 @@ public abstract class AbstractEclipseClasspathReader<T> {
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<T> {
}
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<T> {
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<T> {
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<T> {
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<T> {
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<? super String> names) {
public static String getPresentableName(@NotNull String path, Set<? super String> names) {
String pathComponent = getLastPathComponent(path);
if (pathComponent != null && names != null && !names.add(pathComponent)) {
return path;
@@ -287,7 +287,7 @@ public abstract class AbstractEclipseClasspathReader<T> {
}
@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<T> {
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<T> {
}
@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<T> {
}
}
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<T> {
myVariable = variable;
myRelatedPath = relatedPath;
}
public @NotNull String getVariable() {
return myVariable;
}
public String getRelatedPath() {
return myRelatedPath;
}
}
}
+1
View File
@@ -23,5 +23,6 @@
<orderEntry type="module" module-name="intellij.eclipse.common" />
<orderEntry type="module" module-name="intellij.platform.testExtensions" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="module" module-name="intellij.platform.workspaceModel.ide" />
</component>
</module>
@@ -20,6 +20,8 @@
<depends>com.intellij.modules.java</depends>
<extensions defaultExtensionNs="com.intellij">
<classpathStorageProvider implementation="org.jetbrains.idea.eclipse.config.EclipseClasspathStorageProvider"/>
<workspaceModel.customModuleRootsSerializer implementation="org.jetbrains.idea.eclipse.config.EclipseModuleRootsSerializer"/>
<workspaceModel.customModuleComponentSerializer implementation="org.jetbrains.idea.eclipse.config.EclipseModuleManagerSerializer"/>
<compileServer.plugin classpath="eclipse-jps.jar;eclipse-common.jar"/>
<projectImportProvider implementation="org.jetbrains.idea.eclipse.importWizard.EclipseProjectImportProvider"/>
<projectOpenProcessor id="EclipseProjectOpenProcessor" implementation="org.jetbrains.idea.eclipse.importWizard.EclipseProjectOpenProcessor"/>
@@ -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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
writer.write(lineSeparator);
createOutputter(lineSeparator).output(element, writer);
@@ -21,25 +21,25 @@ import java.util.Set;
@State(name = "EclipseModuleManager")
public class EclipseModuleManagerImpl implements EclipseModuleManager, PersistentStateComponent<Element>, 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<String, String> myEclipseVariablePaths = new LinkedHashMap<>();
private final Set<String> myEclipseUrls = new LinkedHashSet<>();
private final Set<String> 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<String, Integer> mySrcPlace = new LinkedHashMap<>();
private String myInvalidJdk;
@@ -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)
}
}
@@ -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<EclipseModuleRootsSerializer>()
}
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<String>()
val expandMacroMap = reader.getExpandMacroMap(imlFileUrl.url)
val dependencies = ArrayList<ModuleDependencyItem>()
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<LibraryRoot> {
val roots = ArrayList<LibraryRoot>()
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<Class<out WorkspaceEntity>, List<WorkspaceEntity>>,
writer: JpsFileContentWriter,
customDir: String?,
imlFileUrl: VirtualFileUrl,
storage: WorkspaceEntityStorage) {
fun saveXmlFile(path: Path, root: Element) {
//todo get rid of WriteAction here
WriteAction.runAndWait<RuntimeException> {
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<ContentRootEntity>? ?: emptyList()
val entitySource = contentRoots.asSequence().map { it.entitySource }.filterIsInstance<EclipseProjectFile>().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<Class<out WorkspaceEntity>, List<WorkspaceEntity>>,
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<SourceRootEntity>? ?: emptyList()
@Suppress("UNCHECKED_CAST")
val moduleLibraries = (entities[LibraryEntity::class.java] as List<LibraryEntity>? ?: 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
}
}
@@ -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<ModuleDependencyItem.Exportable.LibraryDependency>()
.mapNotNull { it.library.resolve(builder) }
.filter { it.tableId is LibraryTableId.ModuleLibraryTableId }
.associateBy { it.name }
val libraryScopes = HashMap<String, ModuleDependencyItem.DependencyScope>()
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<LibraryRoot>()
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<LibraryRoot>()
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
}
}
}
}
@@ -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<Class<out WorkspaceEntity>, List<WorkspaceEntity>>,
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<LibraryEntity>? ?: emptyList()).associateBy { it.name }
val libLevels = LinkedHashMap<String, String>()
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)
}
}
}
}
@@ -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<String, String>,
val eclipseUrls: Set<VirtualFileUrl>,
val unknownCons: Set<String>,
val knownCons: Set<String>,
val forceConfigureJdk: Boolean,
val expectedModuleSourcePlace: Int,
val srcPlace: Map<String, Int>,
val invalidJdk: String?
) : WorkspaceEntityBase() {
val module: ModuleEntity by moduleDelegate
companion object {
val moduleDelegate = ManyToOne.NotNull<ModuleEntity, EclipseProjectPropertiesEntity>(ModuleEntity::class.java)
}
}
@Suppress("unused") //created via reflection in ClassConversion.entityToEntityData
class EclipseProjectPropertiesEntityData : WorkspaceEntityData<EclipseProjectPropertiesEntity>() {
lateinit var variablePaths: Map<String, String>
lateinit var eclipseUrls: Set<VirtualFileUrl>
lateinit var unknownCons: Set<String>
lateinit var knownCons: Set<String>
var forceConfigureJdk = false
var expectedModuleSourcePlace = 0
lateinit var srcPlace: Map<String, Int>
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<EclipseProjectPropertiesEntity>() {
var variablePaths: MutableMap<String, String> by EntityDataDelegation()
var eclipseUrls: MutableSet<VirtualFileUrl> by EntityDataDelegation()
var unknownCons: MutableSet<String> by EntityDataDelegation()
var knownCons: MutableSet<String> by EntityDataDelegation()
var forceConfigureJdk: Boolean by EntityDataDelegation()
var expectedModuleSourcePlace: Int by EntityDataDelegation()
var srcPlace: MutableMap<String, Int> 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<ModuleEntity, EclipseProjectPropertiesEntity>(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]
@@ -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<EclipseModuleRootsSerializer>()
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<LibraryRoot>): 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
}
@@ -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 }
}
}
@@ -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<? super String, String> fun,
String attributeName,
String[] roots) {
public static <T> void setupAttributes(Element orderEntry,
Function<? super T, String> fun,
String attributeName,
T[] roots) {
final List<String> eclipseUrls = new ArrayList<>();
if (roots.length > 0) {
eclipseUrls.add(fun.fun(roots[0]));
@@ -271,7 +271,7 @@ public class EclipseClasspathReader extends AbstractEclipseClasspathReader<Modif
return lib;
}
static String getJunitClsUrl(final boolean version4) {
public static String getJunitClsUrl(final boolean version4) {
String url = version4 ? JavaSdkUtil.getJunit4JarPath() : JavaSdkUtil.getJunit3JarPath();
final VirtualFile localFile = VirtualFileManager.getInstance().findFileByUrl(pathToUrl(url));
if (localFile != null) {
@@ -61,7 +61,7 @@ public class EclipseClasspathWriter {
}
}
for (String support : eclipseModuleManager.getUsedCons()) {
addOrderEntry(EclipseXml.CON_KIND, support, classpathElement, eclipseModuleManager.getSrcPlace(support));
addOrderEntry(EclipseXml.CON_KIND, support, classpathElement, eclipseModuleManager.getSrcPlace(support), myOldEntries);
}
setAttributeIfAbsent(addOrderEntry(EclipseXml.OUTPUT_KIND, outputPath, classpathElement), EclipseXml.PATH_ATTR, EclipseXml.BIN_DIR);
@@ -84,13 +84,13 @@ public class EclipseClasspathWriter {
}
}
int index = eclipseModuleManager.getSrcPlace(srcUrl);
addOrderEntry(EclipseXml.SRC_KIND, relativePath, classpathRoot, shouldPlaceSeparately && index != -1 ? index : -1);
addOrderEntry(EclipseXml.SRC_KIND, relativePath, classpathRoot, shouldPlaceSeparately && index != -1 ? index : -1, myOldEntries);
}
}
}
else if (entry instanceof ModuleOrderEntry) {
final String path = '/' + ((ModuleOrderEntry)entry).getModuleName();
final Element oldElement = getOldElement(EclipseXml.SRC_KIND, path);
final Element oldElement = getOldElement(EclipseXml.SRC_KIND, path, myOldEntries);
Element orderEntry = addOrderEntry(EclipseXml.SRC_KIND, path, classpathRoot);
if (oldElement == null) {
setAttributeIfAbsent(orderEntry, EclipseXml.COMBINEACCESSRULES_ATTR, EclipseXml.FALSE_VALUE);
@@ -222,11 +222,15 @@ public class EclipseClasspathWriter {
}
private Element addOrderEntry(String kind, String path, Element classpathRoot) {
return addOrderEntry(kind, path, classpathRoot, -1);
return addOrderEntry(kind, path, classpathRoot, myOldEntries);
}
private Element addOrderEntry(@NotNull String kind, String path, Element classpathRoot, int index) {
Element element = getOldElement(kind, path);
public static Element addOrderEntry(String kind, String path, Element classpathRoot, Map<String, Element> oldEntries) {
return addOrderEntry(kind, path, classpathRoot, -1, oldEntries);
}
public static Element addOrderEntry(@NotNull String kind, String path, Element classpathRoot, int index, Map<String, Element> 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<String, Element> 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);
}
@@ -39,15 +39,15 @@ import static org.jetbrains.idea.eclipse.conversion.EPathUtil.areUrlsPointTheSam
* Read/write .eml
*/
public class IdeaSpecificSettings extends AbstractIdeaSpecificSettings<ModifiableRootModel, ContentEntry, Sdk> {
@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
@@ -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!!)