[workspace model] refactoring: extract JpsFileContentWriter::getReplacePathMacroMap method

We need to access PathMacroMap to properly generate *.eml files for projects imported from
Eclipse (IDEA-246236).

This change also extracts additional constructors in (Module/Project)PathMacroManager classes to get rid of corresponding *Bridge implementations. (We cannot use (Module/Project)PathMacroManager directly while loading the workspace model because Module/Project instance aren't available at that time.) It simplifies code and allows to use these classes from ProjectStoreBridge.kt where *Bridge classes weren't accessible from.

GitOrigin-RevId: 39126ba8efe1da98650c2819ebd4354327ed61ac
This commit is contained in:
Nikolay Chashnikov
2020-09-28 15:18:54 +00:00
committed by intellij-monorepo-bot
parent 477410cd20
commit b80fd5473c
7 changed files with 137 additions and 120 deletions
@@ -2,6 +2,8 @@
package com.intellij.configurationStore
import com.intellij.openapi.components.*
import com.intellij.openapi.components.impl.ModulePathMacroManager
import com.intellij.openapi.components.impl.ProjectPathMacroManager
import com.intellij.openapi.components.impl.stores.FileStorageCoreUtil
import com.intellij.openapi.components.impl.stores.IProjectStore
import com.intellij.openapi.project.Project
@@ -16,6 +18,7 @@ import com.intellij.workspaceModel.ide.impl.jps.serialization.*
import org.jdom.Element
import org.jetbrains.jps.util.JpsPathUtil
import java.util.concurrent.ConcurrentHashMap
import java.util.function.Supplier
class ProjectStoreBridge(private val project: Project) : ModuleSavingCustomizer {
override fun createSaveSessionProducerManager(): ProjectSaveSessionProducerManager {
@@ -56,6 +59,16 @@ private class JpsStorageContentWriter(private val session: ProjectWithModulesSav
}
}
}
override fun getReplacePathMacroMap(fileUrl: String): PathMacroMap {
val filePath = JpsPathUtil.urlToPath(fileUrl)
return if (FileUtil.extensionEquals(filePath, "iml") || isExternalModuleFile(filePath)) {
ModulePathMacroManager.createInstance(Supplier { filePath }).replacePathMap
}
else {
ProjectPathMacroManager.getInstance(project).replacePathMap
}
}
}
private val MODULE_FILE_STORAGE_ANNOTATION = FileStorageAnnotation(StoragePathMacros.MODULE_FILE, false)
@@ -6,29 +6,42 @@ import com.intellij.openapi.application.PathMacros;
import com.intellij.openapi.components.ExpandMacroToPathMap;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.module.Module;
import com.intellij.serviceContainer.NonInjectable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.SystemIndependent;
import org.jetbrains.jps.model.serialization.PathMacroUtil;
import java.util.function.Supplier;
public final class ModulePathMacroManager extends PathMacroManager {
private final Module myModule;
private final Supplier<@SystemIndependent String> myModuleDirPointer;
public ModulePathMacroManager(@NotNull Module module) {
super(PathMacros.getInstance());
myModule = module;
myModuleDirPointer = module::getModuleFilePath;
}
@NonInjectable
private ModulePathMacroManager(Supplier<@SystemIndependent String> moduleDirPointer) {
super(PathMacros.getInstance());
myModuleDirPointer = moduleDirPointer;
}
@Override
public @NotNull ExpandMacroToPathMap getExpandMacroMap() {
ExpandMacroToPathMap result = super.getExpandMacroMap();
addFileHierarchyReplacements(result, PathMacroUtil.MODULE_DIR_MACRO_NAME, PathMacroUtil.getModuleDir(myModule.getModuleFilePath()));
addFileHierarchyReplacements(result, PathMacroUtil.MODULE_DIR_MACRO_NAME, PathMacroUtil.getModuleDir(myModuleDirPointer.get()));
return result;
}
@Override
public @NotNull ReplacePathToMacroMap computeReplacePathMap() {
ReplacePathToMacroMap result = super.computeReplacePathMap();
String modulePath = PathMacroUtil.getModuleDir(myModule.getModuleFilePath());
addFileHierarchyReplacements(result, PathMacroUtil.MODULE_DIR_MACRO_NAME, modulePath, PathMacroUtil.getUserHomePath());
addFileHierarchyReplacements(result, PathMacroUtil.MODULE_DIR_MACRO_NAME, PathMacroUtil.getModuleDir(myModuleDirPointer.get()), PathMacroUtil.getUserHomePath());
return result;
}
public static ModulePathMacroManager createInstance(Supplier<@SystemIndependent String> moduleDirPointer) {
return new ModulePathMacroManager(moduleDirPointer);
}
}
@@ -6,25 +6,39 @@ import com.intellij.openapi.application.PathMacros;
import com.intellij.openapi.components.ExpandMacroToPathMap;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.project.Project;
import com.intellij.serviceContainer.NonInjectable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.SystemIndependent;
import org.jetbrains.jps.model.serialization.PathMacroUtil;
import java.util.function.Supplier;
public class ProjectPathMacroManager extends PathMacroManager {
private final Project myProject;
private final Supplier<@SystemIndependent String> myBasePathPointer;
private final @Nullable Supplier<@SystemIndependent String> myNamePointer;
public ProjectPathMacroManager(@NotNull Project project) {
super(PathMacros.getInstance());
myBasePathPointer = project::getBasePath;
myNamePointer = !project.isDefault() ? project::getName : null;
}
myProject = project;
@NonInjectable
private ProjectPathMacroManager(Supplier<@SystemIndependent String> basePathPointer,
@Nullable Supplier<@SystemIndependent String> namePointer) {
super(PathMacros.getInstance());
myBasePathPointer = basePathPointer;
myNamePointer = namePointer;
}
@NotNull
@Override
public ExpandMacroToPathMap getExpandMacroMap() {
ExpandMacroToPathMap result = super.getExpandMacroMap();
addFileHierarchyReplacements(result, PathMacroUtil.PROJECT_DIR_MACRO_NAME, myProject.getBasePath());
if (!myProject.isDefault()) {
result.addMacroExpand(PathMacroUtil.PROJECT_NAME_MACRO_NAME, myProject.getName());
addFileHierarchyReplacements(result, PathMacroUtil.PROJECT_DIR_MACRO_NAME, myBasePathPointer.get());
if (myNamePointer != null) {
result.addMacroExpand(PathMacroUtil.PROJECT_NAME_MACRO_NAME, myNamePointer.get());
}
return result;
}
@@ -33,7 +47,12 @@ public class ProjectPathMacroManager extends PathMacroManager {
@Override
protected ReplacePathToMacroMap computeReplacePathMap() {
ReplacePathToMacroMap result = super.computeReplacePathMap();
addFileHierarchyReplacements(result, PathMacroUtil.PROJECT_DIR_MACRO_NAME, myProject.getBasePath(), null);
addFileHierarchyReplacements(result, PathMacroUtil.PROJECT_DIR_MACRO_NAME, myBasePathPointer.get(), null);
return result;
}
public static ProjectPathMacroManager createInstance(Supplier<@SystemIndependent String> basePathPointer,
@Nullable Supplier<@SystemIndependent String> namePointer) {
return new ProjectPathMacroManager(basePathPointer, namePointer);
}
}
@@ -1,10 +1,10 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.workspaceModel.ide.impl.jps.serialization
import com.intellij.application.options.ReplacePathToMacroMap
import com.intellij.openapi.application.PathMacros
import com.intellij.openapi.components.ExpandMacroToPathMap
import com.intellij.openapi.components.PathMacroManager
import com.intellij.openapi.components.impl.ModulePathMacroManager
import com.intellij.openapi.components.impl.ProjectPathMacroManager
import com.intellij.openapi.module.impl.ModulePath
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtilCore
@@ -25,7 +25,6 @@ import com.intellij.workspaceModel.storage.bridgeEntities.ModuleEntity
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import org.jdom.Element
import org.jetbrains.annotations.TestOnly
import org.jetbrains.jps.model.serialization.PathMacroUtil
import org.jetbrains.jps.util.JpsPathUtil
import java.nio.file.Files
import java.nio.file.Path
@@ -457,7 +456,7 @@ class JpsProjectSerializersImpl(directorySerializersFactories: List<JpsDirectory
}
class CachingJpsFileContentReader(projectBaseDirUrl: String) : JpsFileContentReader {
private val projectPathMacroManager = ProjectPathMacroManagerBridge(JpsPathUtil.urlToPath(projectBaseDirUrl))
private val projectPathMacroManager = ProjectPathMacroManager.createInstance({ JpsPathUtil.urlToPath(projectBaseDirUrl) }, null)
private val fileContentCache = ConcurrentHashMap<String, Map<String, Element>>()
override fun loadComponent(fileUrl: String, componentName: String, customModuleFilePath: String?): Element? {
@@ -482,46 +481,12 @@ class CachingJpsFileContentReader(projectBaseDirUrl: String) : JpsFileContentRea
customModuleFilePath: String?): PathMacroManager {
val path = JpsPathUtil.urlToPath(fileUrl)
return if (FileUtil.extensionEquals(fileUrl, "iml") || isExternalModuleFile(path)) {
ModulePathMacroManagerBridge(PathMacros.getInstance(), customModuleFilePath ?: path)
ModulePathMacroManager.createInstance { customModuleFilePath ?: path }
}
else {
projectPathMacroManager
}
}
internal class ModulePathMacroManagerBridge(pathMacros: PathMacros, private val moduleFilePath: String) : PathMacroManager(
pathMacros) {
override fun getExpandMacroMap(): ExpandMacroToPathMap {
val result = super.getExpandMacroMap()
addFileHierarchyReplacements(
result, PathMacroUtil.MODULE_DIR_MACRO_NAME,
PathMacroUtil.getModuleDir(moduleFilePath))
return result
}
public override fun computeReplacePathMap(): ReplacePathToMacroMap {
val result = super.computeReplacePathMap()
val modulePath = PathMacroUtil.getModuleDir(moduleFilePath)
addFileHierarchyReplacements(
result, PathMacroUtil.MODULE_DIR_MACRO_NAME, modulePath,
PathMacroUtil.getUserHomePath())
return result
}
}
internal class ProjectPathMacroManagerBridge(private val projectDirPath: String) : PathMacroManager(PathMacros.getInstance()) {
override fun getExpandMacroMap(): ExpandMacroToPathMap {
val result = super.getExpandMacroMap()
addFileHierarchyReplacements(result, PathMacroUtil.PROJECT_DIR_MACRO_NAME, projectDirPath)
return result
}
override fun computeReplacePathMap(): ReplacePathToMacroMap {
val result = super.computeReplacePathMap()
addFileHierarchyReplacements(result, PathMacroUtil.PROJECT_DIR_MACRO_NAME, projectDirPath, null)
return result
}
}
}
// TODO Add more diagnostics: file path, line etc
@@ -2,6 +2,7 @@
package com.intellij.workspaceModel.ide.impl.jps.serialization
import com.intellij.openapi.components.ExpandMacroToPathMap
import com.intellij.openapi.components.PathMacroMap
import com.intellij.openapi.module.impl.ModulePath
import com.intellij.workspaceModel.storage.bridgeEntities.ModuleEntity
import com.intellij.workspaceModel.ide.JpsFileEntitySource
@@ -21,6 +22,7 @@ interface JpsFileContentReader {
interface JpsFileContentWriter {
fun saveComponent(fileUrl: String, componentName: String, componentTag: Element?)
fun getReplacePathMacroMap(fileUrl: String): PathMacroMap
}
/**
@@ -157,9 +157,9 @@ class JpsProjectSaveAfterChangesTest {
is EntityChange.Replaced -> listOf(change.oldEntity, change.newEntity)
}
}.map { it.entitySource }}
val writer = JpsFileContentWriterImpl()
val writer = JpsFileContentWriterImpl(projectData.projectDir)
projectData.serializers.saveEntities(builder.toStorage(), changedSources, writer)
writer.writeFiles(projectData.projectDir)
writer.writeFiles()
projectData.serializers.checkConsistency(projectData.projectDirUrl, builder.toStorage(), virtualFileManager)
val expectedDir = FileUtil.createTempDirectory("jpsProjectTest", "expected")
@@ -3,6 +3,9 @@ package com.intellij.workspaceModel.ide.impl.jps.serialization
import com.intellij.openapi.application.PathMacros
import com.intellij.openapi.application.ex.PathManagerEx
import com.intellij.openapi.components.PathMacroMap
import com.intellij.openapi.components.impl.ModulePathMacroManager
import com.intellij.openapi.components.impl.ProjectPathMacroManager
import com.intellij.openapi.util.JDOMUtil
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.systemIndependentPath
@@ -62,74 +65,9 @@ internal fun loadProject(configLocation: JpsProjectConfigLocation, originalBui
}
internal fun JpsProjectSerializersImpl.saveAllEntities(storage: WorkspaceEntityStorage, projectDir: File) {
val writer = JpsFileContentWriterImpl()
val writer = JpsFileContentWriterImpl(projectDir)
saveAllEntities(storage, writer)
writer.writeFiles(projectDir)
}
internal fun JpsFileContentWriterImpl.writeFiles(baseProjectDir: File) {
urlToComponents.forEach { (url, newComponents) ->
val components = HashMap(newComponents)
val file = JpsPathUtil.urlToFile(url)
val isModuleFile = FileUtil.extensionEquals(file.absolutePath, "iml")
|| file.parentFile.name == "modules" && file.parentFile.parentFile.name != ".idea"
val replaceMacroMap = if (isModuleFile)
CachingJpsFileContentReader.ModulePathMacroManagerBridge(PathMacros.getInstance(), JpsPathUtil.urlToOsPath(url)).replacePathMap
else
CachingJpsFileContentReader.ProjectPathMacroManagerBridge(baseProjectDir.systemIndependentPath).replacePathMap
val newRootElement = when {
isModuleFile -> Element("module")
FileUtil.filesEqual(File(baseProjectDir, ".idea"), file.parentFile.parentFile) -> null
else -> Element("project")
}
fun isEmptyComponentTag(componentTag: Element) = componentTag.contentSize == 0 && componentTag.attributes.all { it.name == "name" }
val rootElement: Element?
if (newRootElement != null) {
if (file.exists()) {
val oldElement = JDOMUtil.load(file)
oldElement.getChildren("component")
.filterNot { it.getAttributeValue("name") in components }
.map { it.clone() }
.associateByTo(components) { it.getAttributeValue("name") }
}
components.entries.sortedBy { it.key }.forEach { (name, element) ->
if (element != null && !isEmptyComponentTag(element)) {
if (name == "DeprecatedModuleOptionManager") {
element.getChildren("option").forEach {
newRootElement.setAttribute(it.getAttributeValue("key")!!, it.getAttributeValue("value")!!)
}
}
else {
newRootElement.addContent(element)
}
}
}
if (!JDOMUtil.isEmpty(newRootElement)) {
newRootElement.setAttribute("version", "4")
rootElement = newRootElement
}
else {
rootElement = null
}
}
else {
val singleComponent = components.values.single()
rootElement = if (singleComponent != null && !isEmptyComponentTag(singleComponent)) singleComponent else null
}
if (rootElement != null) {
replaceMacroMap.substitute(rootElement, true, true)
FileUtil.createParentDirs(file)
JDOMUtil.write(rootElement, file)
}
else {
FileUtil.delete(file)
}
}
writer.writeFiles()
}
internal fun assertDirectoryMatches(actualDir: File, expectedDir: File, filesToIgnore: Set<String>, componentsToIgnore: List<String>) {
@@ -228,12 +166,79 @@ internal fun toConfigLocation(file: Path, virtualFileManager: VirtualFileUrlMana
}
}
internal class JpsFileContentWriterImpl : JpsFileContentWriter {
internal class JpsFileContentWriterImpl(private val baseProjectDir: File) : JpsFileContentWriter {
val urlToComponents = LinkedHashMap<String, LinkedHashMap<String, Element?>>()
override fun saveComponent(fileUrl: String, componentName: String, componentTag: Element?) {
urlToComponents.computeIfAbsent(fileUrl) { LinkedHashMap() }[componentName] = componentTag
}
override fun getReplacePathMacroMap(fileUrl: String): PathMacroMap {
return if (isModuleFile(JpsPathUtil.urlToFile(fileUrl)))
ModulePathMacroManager.createInstance { JpsPathUtil.urlToOsPath(fileUrl) }.replacePathMap
else
ProjectPathMacroManager.createInstance({ baseProjectDir.systemIndependentPath }, null).replacePathMap
}
internal fun writeFiles() {
urlToComponents.forEach { (url, newComponents) ->
val components = HashMap(newComponents)
val file = JpsPathUtil.urlToFile(url)
val replaceMacroMap = getReplacePathMacroMap(url)
val newRootElement = when {
isModuleFile(file) -> Element("module")
FileUtil.filesEqual(File(baseProjectDir, ".idea"), file.parentFile.parentFile) -> null
else -> Element("project")
}
fun isEmptyComponentTag(componentTag: Element) = componentTag.contentSize == 0 && componentTag.attributes.all { it.name == "name" }
val rootElement: Element?
if (newRootElement != null) {
if (file.exists()) {
val oldElement = JDOMUtil.load(file)
oldElement.getChildren("component")
.filterNot { it.getAttributeValue("name") in components }
.map { it.clone() }
.associateByTo(components) { it.getAttributeValue("name") }
}
components.entries.sortedBy { it.key }.forEach { (name, element) ->
if (element != null && !isEmptyComponentTag(element)) {
if (name == "DeprecatedModuleOptionManager") {
element.getChildren("option").forEach {
newRootElement.setAttribute(it.getAttributeValue("key")!!, it.getAttributeValue("value")!!)
}
}
else {
newRootElement.addContent(element)
}
}
}
if (!JDOMUtil.isEmpty(newRootElement)) {
newRootElement.setAttribute("version", "4")
rootElement = newRootElement
}
else {
rootElement = null
}
}
else {
val singleComponent = components.values.single()
rootElement = if (singleComponent != null && !isEmptyComponentTag(singleComponent)) singleComponent else null
}
if (rootElement != null) {
replaceMacroMap.substitute(rootElement, true, true)
FileUtil.createParentDirs(file)
JDOMUtil.write(rootElement, file)
}
else {
FileUtil.delete(file)
}
}
}
private fun isModuleFile(file: File) = (FileUtil.extensionEquals(file.absolutePath, "iml")
|| file.parentFile.name == "modules" && file.parentFile.parentFile.name != ".idea")
}
internal object TestErrorReporter : ErrorReporter {