diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/ex/ProjectInspectionManagerTest.kt b/java/java-tests/testSrc/com/intellij/codeInspection/ex/ProjectInspectionManagerTest.kt index 6d88faf7a30e9..c962c8abab528 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/ex/ProjectInspectionManagerTest.kt +++ b/java/java-tests/testSrc/com/intellij/codeInspection/ex/ProjectInspectionManagerTest.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2019 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. +// 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.codeInspection.ex import com.intellij.codeHighlighting.HighlightDisplayLevel @@ -63,7 +63,7 @@ class ProjectInspectionManagerTest { """.trimIndent() assertThat(projectInspectionProfileManager.state).isEqualTo(doNotUseProjectProfileState) - val inspectionDir = Paths.get(project.stateStore.projectConfigDir!!, PROFILE_DIR) + val inspectionDir = project.stateStore.projectConfigDir!!.resolve(PROFILE_DIR) val file = inspectionDir.resolve("profiles_settings.xml") project.stateStore.save() assertThat(file).exists() @@ -93,7 +93,7 @@ class ProjectInspectionManagerTest { @Test fun `do not save default project profile`() { doTest { project -> - val inspectionDir = Paths.get(project.stateStore.projectConfigDir!!, PROFILE_DIR) + val inspectionDir = project.stateStore.projectConfigDir!!.resolve(PROFILE_DIR) val profileFile = inspectionDir.resolve("Project_Default.xml") assertThat(profileFile).doesNotExist() @@ -127,7 +127,7 @@ class ProjectInspectionManagerTest { project.stateStore.save() - val inspectionDir = Paths.get(project.stateStore.projectConfigDir!!, PROFILE_DIR) + val inspectionDir = project.stateStore.projectConfigDir!!.resolve(PROFILE_DIR) val file = inspectionDir.resolve("profiles_settings.xml") assertThat(file).doesNotExist() @@ -166,7 +166,7 @@ class ProjectInspectionManagerTest { assertThat(profileManager.currentProfile.isProjectLevel).isTrue() assertThat(profileManager.currentProfile.name).isEqualTo("Project Default") - val projectConfigDir = Paths.get(project.stateStore.projectConfigDir!!) + val projectConfigDir = project.stateStore.projectConfigDir!! // test creation of .idea/inspectionProfiles dir, not .idea itself projectConfigDir.createDirectories() diff --git a/platform/analysis-api/src/com/intellij/openapi/vfs/VfsUtil.java b/platform/analysis-api/src/com/intellij/openapi/vfs/VfsUtil.java index c432c53571b2a..dadabef343db7 100644 --- a/platform/analysis-api/src/com/intellij/openapi/vfs/VfsUtil.java +++ b/platform/analysis-api/src/com/intellij/openapi/vfs/VfsUtil.java @@ -194,11 +194,11 @@ public static VirtualFile copy(Object requestor, @NotNull VirtualFile file, @Not } public static @Nullable VirtualFile findFile(@NotNull Path file, boolean refreshIfNeeded) { - return findFile(FileUtil.toSystemIndependentName(file.toAbsolutePath().toString()), refreshIfNeeded); + return findFile(file.toAbsolutePath().toString().replace(File.separatorChar, '/'), refreshIfNeeded); } public static @Nullable VirtualFile findFileByIoFile(@NotNull File file, boolean refreshIfNeeded) { - return findFile(FileUtil.toSystemIndependentName(file.getAbsolutePath()), refreshIfNeeded); + return findFile(file.getAbsolutePath().replace(File.separatorChar, '/'), refreshIfNeeded); } private static @Nullable VirtualFile findFile(@NotNull String filePath, boolean refreshIfNeeded) { diff --git a/platform/configuration-store-impl/src/ApplicationStoreImpl.kt b/platform/configuration-store-impl/src/ApplicationStoreImpl.kt index 94607443a1e5e..9fccee2b6423b 100644 --- a/platform/configuration-store-impl/src/ApplicationStoreImpl.kt +++ b/platform/configuration-store-impl/src/ApplicationStoreImpl.kt @@ -7,11 +7,9 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.PathManager import com.intellij.openapi.application.appSystemDir import com.intellij.openapi.components.* -import com.intellij.openapi.components.impl.stores.FileStorageCoreUtil import com.intellij.openapi.diagnostic.runAndLogException import com.intellij.openapi.project.ex.ProjectManagerEx import com.intellij.openapi.util.NamedJDOMExternalizable -import com.intellij.util.io.systemIndependentPath import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import org.jetbrains.jps.model.serialization.JpsGlobalLoader @@ -22,20 +20,19 @@ internal class ApplicationPathMacroManager : PathMacroManager(null) const val APP_CONFIG = "\$APP_CONFIG$" class ApplicationStoreImpl : ComponentStoreWithExtraComponents() { - private val application = ApplicationManager.getApplication() - - override val storageManager = ApplicationStorageManager(application, PathMacroManager.getInstance(ApplicationManager.getApplication())) + override val storageManager = ApplicationStorageManager(ApplicationManager.getApplication(), PathMacroManager.getInstance(ApplicationManager.getApplication())) // number of app components require some state, so, we load default state in test mode override val loadPolicy: StateLoadPolicy - get() = if (application.isUnitTestMode) StateLoadPolicy.LOAD_ONLY_DEFAULT else StateLoadPolicy.LOAD + get() = if (ApplicationManager.getApplication().isUnitTestMode) StateLoadPolicy.LOAD_ONLY_DEFAULT else StateLoadPolicy.LOAD override fun setPath(path: Path) { - val systemIndependentPath = path.systemIndependentPath - // app config must be first, because collapseMacros collapse from fist to last, so, at first we must replace APP_CONFIG because it overlaps ROOT_CONFIG value - storageManager.addMacro(APP_CONFIG, "$systemIndependentPath/${PathManager.OPTIONS_DIRECTORY}") - storageManager.addMacro(ROOT_CONFIG, systemIndependentPath) - storageManager.addMacro(StoragePathMacros.CACHE_FILE, appSystemDir.resolve("workspace").resolve("app.xml").systemIndependentPath) + storageManager.setMacros(listOf( + // app config must be first, because collapseMacros collapse from fist to last, so, at first we must replace APP_CONFIG because it overlaps ROOT_CONFIG value + Macro(APP_CONFIG, path.resolve(PathManager.OPTIONS_DIRECTORY)), + Macro(ROOT_CONFIG, path), + Macro(StoragePathMacros.CACHE_FILE, appSystemDir.resolve("workspace").resolve("app.xml")) + )) } override suspend fun doSave(result: SaveResult, forceSavingAllSettings: Boolean) { @@ -71,12 +68,12 @@ internal val appFileBasedStorageConfiguration = object: FileBasedStorageConfigur } class ApplicationStorageManager(application: Application?, pathMacroManager: PathMacroManager? = null) - : StateStorageManagerImpl("application", pathMacroManager?.createTrackingSubstitutor(), application) { + : StateStorageManagerImpl("application", pathMacroManager?.createTrackingSubstitutor (), application) { override fun getFileBasedStorageConfiguration(fileSpec: String) = appFileBasedStorageConfiguration override fun getOldStorageSpec(component: Any, componentName: String, operation: StateStorageOperation): String? { return when (component) { - is NamedJDOMExternalizable -> "${component.externalFileName}${FileStorageCoreUtil.DEFAULT_EXT}" + is NamedJDOMExternalizable -> "${component.externalFileName}${PathManager.DEFAULT_EXT}" else -> PathManager.DEFAULT_OPTIONS_FILE } } @@ -102,5 +99,8 @@ class ApplicationStorageManager(application: Application?, pathMacroManager: Pat override fun normalizeFileSpec(fileSpec: String) = removeMacroIfStartsWith(super.normalizeFileSpec(fileSpec), APP_CONFIG) - override fun expandMacros(path: String) = if (path[0] == '$') super.expandMacros(path) else "${expandMacro(APP_CONFIG)}/$path" + override fun expandMacro(collapsedPath: String): Path { + // APP_CONFIG is the first macro + return if (collapsedPath[0] == '$') super.expandMacro(collapsedPath) else macros.get(0).value.resolve(collapsedPath) + } } \ No newline at end of file diff --git a/platform/configuration-store-impl/src/ComponentStoreImpl.kt b/platform/configuration-store-impl/src/ComponentStoreImpl.kt index e4e0c3dfe57f6..2935b232e69d5 100644 --- a/platform/configuration-store-impl/src/ComponentStoreImpl.kt +++ b/platform/configuration-store-impl/src/ComponentStoreImpl.kt @@ -37,7 +37,6 @@ import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.CalledInAwt import org.jetbrains.annotations.TestOnly import java.io.IOException -import java.nio.file.Paths import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit @@ -246,7 +245,7 @@ abstract class ComponentStoreImpl : IComponentStore { LOG.debug { "saveComponent is called for ${stateSpec.name}" } val saveManager = createSaveSessionProducerManager() commitComponent(saveManager, ComponentInfoImpl(component, stateSpec), null, false) - val absolutePath = Paths.get(storageManager.expandMacros(findNonDeprecated(getStorageSpecs(component, stateSpec, StateStorageOperation.WRITE)).path)).toAbsolutePath().toString() + val absolutePath = storageManager.expandMacro(findNonDeprecated(getStorageSpecs(component, stateSpec, StateStorageOperation.WRITE)).path).toString() Disposer.newDisposable().use { VfsRootAccess.allowRootAccess(it, absolutePath) runBlocking { diff --git a/platform/configuration-store-impl/src/DefaultProjectStoreImpl.kt b/platform/configuration-store-impl/src/DefaultProjectStoreImpl.kt index 58df1c55e80f5..96f2a459fc31e 100644 --- a/platform/configuration-store-impl/src/DefaultProjectStoreImpl.kt +++ b/platform/configuration-store-impl/src/DefaultProjectStoreImpl.kt @@ -9,7 +9,6 @@ import org.jdom.Element import org.jetbrains.annotations.ApiStatus import java.io.Writer import java.nio.file.Path -import java.nio.file.Paths private const val FILE_SPEC = "${APP_CONFIG}/project.default.xml" @@ -64,7 +63,7 @@ class DefaultProjectStoreImpl(override val project: Project) : ChildlessComponen get() = if (ApplicationManager.getApplication().isUnitTestMode) StateLoadPolicy.NOT_LOAD else StateLoadPolicy.LOAD private val storage by lazy { - DefaultProjectStorage(Paths.get(ApplicationManager.getApplication().stateStore.storageManager.expandMacros(FILE_SPEC)), FILE_SPEC, PathMacroManager.getInstance(project)) + DefaultProjectStorage(ApplicationManager.getApplication().stateStore.storageManager.expandMacro(FILE_SPEC), FILE_SPEC, PathMacroManager.getInstance(project)) } override val storageManager = object : StateStorageManager { @@ -77,12 +76,9 @@ class DefaultProjectStoreImpl(override val project: Project) : ChildlessComponen override fun removeStreamProvider(clazz: Class) { } - override fun rename(path: String, newName: String) { - } - override fun getStateStorage(storageSpec: Storage) = storage - override fun expandMacros(path: String) = throw UnsupportedOperationException() + override fun expandMacro(path: String) = throw UnsupportedOperationException() override fun getOldStorage(component: Any, componentName: String, operation: StateStorageOperation) = storage } diff --git a/platform/configuration-store-impl/src/ExportSettingsAction.kt b/platform/configuration-store-impl/src/ExportSettingsAction.kt index 20f5330a193d8..bf37a986e41e9 100644 --- a/platform/configuration-store-impl/src/ExportSettingsAction.kt +++ b/platform/configuration-store-impl/src/ExportSettingsAction.kt @@ -21,11 +21,11 @@ import com.intellij.openapi.options.SchemeManagerFactory import com.intellij.openapi.project.DumbAware import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.showOkCancelDialog -import com.intellij.openapi.util.io.FileUtil import com.intellij.serviceContainer.ComponentManagerImpl import com.intellij.serviceContainer.processAllImplementationClasses import com.intellij.util.ArrayUtil import com.intellij.util.ReflectionUtil +import com.intellij.util.SmartList import com.intellij.util.containers.CollectionFactory import com.intellij.util.containers.putValue import com.intellij.util.io.* @@ -33,7 +33,6 @@ import java.io.IOException import java.io.OutputStream import java.io.StringWriter import java.nio.file.Path -import java.nio.file.Paths import java.util.* internal fun isImportExportActionApplicable(): Boolean { @@ -49,7 +48,7 @@ open class ExportSettingsAction : AnAction(), DumbAware { protected open fun exportSettings(saveFile: Path, markedComponents: Set) { val exportFiles = markedComponents.mapTo(CollectionFactory.createSmallMemoryFootprintSet()) { it.file } saveFile.outputStream().use { - exportSettings(exportFiles, it, FileUtil.toSystemIndependentName(PathManager.getConfigPath())) + exportSettings(exportFiles, it, PathManager.getConfigDir()) } } @@ -92,24 +91,26 @@ open class ExportSettingsAction : AnAction(), DumbAware { } } -fun exportSettings(exportFiles: Set, out: OutputStream, configPath: String) { - val filter = CollectionFactory.createSmallMemoryFootprintSet() - Compressor.Zip(out).filter { entryName, _ -> filter.add(entryName) }.use { zip -> - for (file in exportFiles) { - val fileInfo = file.basicAttributesIfExists() ?: continue - val relativePath = FileUtil.getRelativePath(configPath, file.toAbsolutePath().systemIndependentPath, '/')!! - if (fileInfo.isDirectory) { - zip.addDirectory(relativePath, file.toFile()) - } - else { - zip.addFile(relativePath, file.inputStream()) +fun exportSettings(exportFiles: Set, out: OutputStream, configPath: Path) { + val filter = HashSet() + Compressor.Zip(out) + .nioFilter { entryName, _ -> filter.add(entryName) } + .use { zip -> + for (file in exportFiles) { + val fileInfo = file.basicAttributesIfExists() ?: continue + val relativePath = configPath.relativize(file).toString() + if (fileInfo.isDirectory) { + zip.addDirectory(relativePath, file) + } + else { + zip.addFile(relativePath, file) + } } - } - exportInstalledPlugins(zip) + exportInstalledPlugins(zip) - zip.addFile(ImportSettingsFilenameFilter.SETTINGS_JAR_MARKER, ArrayUtil.EMPTY_BYTE_ARRAY) - } + zip.addFile(ImportSettingsFilenameFilter.SETTINGS_JAR_MARKER, ArrayUtil.EMPTY_BYTE_ARRAY) + } } data class ExportableItem(val file: Path, val presentableName: String, val roamingType: RoamingType = RoamingType.DEFAULT) @@ -133,7 +134,7 @@ fun getExportableComponentsMap(isOnlyExisting: Boolean, val processor = { component: ExportableComponent -> for (file in component.exportFiles) { val item = ExportableItem(file.toPath(), component.presentableName, RoamingType.DEFAULT) - result.putValue(item.file, item) + result.computeIfAbsent(item.file) { SmartList() }.add(item) } } @@ -144,11 +145,16 @@ fun getExportableComponentsMap(isOnlyExisting: Boolean, @Suppress("DEPRECATION") ServiceBean.loadServicesFromBeans(ExportableComponent.EXTENSION_POINT, ExportableComponent::class.java).forEach(processor) - val configPath = storageManager.expandMacros(ROOT_CONFIG) + val configPath = storageManager.expandMacro(ROOT_CONFIG) fun isSkipFile(file: Path): Boolean { if (onlyPaths != null) { - var relativePath = FileUtil.getRelativePath(configPath, file.systemIndependentPath, '/')!! + // maybe in tests where in memory fs is used + if (configPath.fileSystem != file.fileSystem) { + return true + } + + var relativePath = configPath.relativize(file).systemIndependentPath if (!file.fileName.toString().contains('.') && !file.isFile()) { relativePath += '/' } @@ -184,7 +190,7 @@ fun getExportableComponentsMap(isOnlyExisting: Boolean, try { additionalExportFile = getAdditionalExportFile(stateAnnotation, storageManager, ::isSkipFile) - file = Paths.get(storageManager.expandMacros(storage.path)) + file = storageManager.expandMacro(storage.path) } catch (e: UnknownMacroException) { LOG.error("Cannot expand macro for component \"${stateAnnotation.name}\"", e) @@ -214,7 +220,7 @@ fun getExportableComponentsMap(isOnlyExisting: Boolean, // must be in the end - because most of SchemeManager clients specify additionalExportFile in the State spec (SchemeManagerFactory.getInstance() as SchemeManagerFactoryBase).process { if (it.roamingType != RoamingType.DISABLED && it.fileSpec.getOrNull(0) != '$') { - val file = Paths.get(storageManager.expandMacros(ROOT_CONFIG), it.fileSpec) + val file = storageManager.expandMacro(ROOT_CONFIG).resolve(it.fileSpec) if (!result.containsKey(file) && !isSkipFile(file)) { result.putValue(file, ExportableItem(file, it.presentableName ?: "", it.roamingType)) } @@ -232,10 +238,11 @@ private inline fun getAdditionalExportFile(stateAnnotation: State, storageManage val additionalExportFile: Path? // backward compatibility - path can contain macro if (additionalExportPath[0] == '$') { - additionalExportFile = Paths.get(storageManager.expandMacros(additionalExportPath)) + additionalExportFile = storageManager.expandMacro(additionalExportPath) } else { - additionalExportFile = Paths.get(storageManager.expandMacros(ROOT_CONFIG), additionalExportPath) + @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") + additionalExportFile = storageManager.expandMacro(ROOT_CONFIG).resolve(additionalExportPath)!! } return if (isSkipFile(additionalExportFile)) null else additionalExportFile } diff --git a/platform/configuration-store-impl/src/ModuleStateStorageManager.kt b/platform/configuration-store-impl/src/ModuleStateStorageManager.kt index 793403c34e7ba..0c36a4f44bdd4 100644 --- a/platform/configuration-store-impl/src/ModuleStateStorageManager.kt +++ b/platform/configuration-store-impl/src/ModuleStateStorageManager.kt @@ -1,34 +1,64 @@ -// Copyright 2000-2019 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. +// 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.configurationStore +import com.intellij.ide.highlighter.ModuleFileType import com.intellij.openapi.components.* import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.module.impl.ModuleEx import com.intellij.openapi.module.impl.ModuleManagerImpl -import com.intellij.openapi.module.impl.getModuleNameByFilePath import com.intellij.openapi.project.isExternalStorageEnabled import com.intellij.openapi.vfs.newvfs.events.VFileEvent import org.jdom.Element import org.jetbrains.annotations.ApiStatus import java.io.FileNotFoundException +import java.io.IOException import java.nio.file.Path -import java.nio.file.Paths +import kotlin.concurrent.write @ApiStatus.Internal -open class ModuleStateStorageManager(macroSubstitutor: TrackingPathMacroSubstitutor, module: Module) : StateStorageManagerImpl("module", macroSubstitutor, module) { +open class ModuleStateStorageManager(macroSubstitutor: TrackingPathMacroSubstitutor, module: Module) : StateStorageManagerImpl("module", macroSubstitutor, module), RenameableStateStorageManager { override fun getOldStorageSpec(component: Any, componentName: String, operation: StateStorageOperation) = StoragePathMacros.MODULE_FILE - override fun pathRenamed(oldPath: String, newPath: String, event: VFileEvent?) { + // the only macro is supported by ModuleStateStorageManager + final override fun expandMacro(collapsedPath: String): Path { + if (collapsedPath != StoragePathMacros.MODULE_FILE) { + throw IllegalStateException("Cannot resolve $collapsedPath in $macros") + } + return macros.get(0).value + } + + final override fun rename(newName: String) { + storageLock.write { + val storage = getOrCreateStorage(StoragePathMacros.MODULE_FILE, RoamingType.DEFAULT) as FileBasedStorage + val file = storage.getVirtualFile(StateStorageOperation.WRITE) + try { + if (file != null) { + file.rename(storage, newName) + } + else if (storage.file.fileName.toString() != newName) { + // old file didn't exist or renaming failed + val newFile = storage.file.parent.resolve(newName) + storage.setFile(null, newFile) + pathRenamed(newFile, null) + } + } + catch (e: IOException) { + LOG.debug(e) + } + } + } + + override fun pathRenamed(newPath: Path, event: VFileEvent?) { try { - super.pathRenamed(oldPath, newPath, event) + setMacros(listOf(Macro(StoragePathMacros.MODULE_FILE, newPath))) } finally { val requestor = event?.requestor if (requestor == null || requestor !is StateStorage /* not renamed as result of explicit rename */) { val module = componentManager as ModuleEx val oldName = module.name - module.rename(getModuleNameByFilePath(newPath), false) + module.rename(newPath.fileName.toString().removeSuffix(ModuleFileType.DOT_DEFAULT_EXTENSION), false) (ModuleManager.getInstance(module.project) as? ModuleManagerImpl)?.fireModuleRenamedByVfsEvent(module, oldName) } } @@ -66,8 +96,8 @@ open class ModuleStateStorageManager(macroSubstitutor: TrackingPathMacroSubstitu override val isExternalSystemStorageEnabled: Boolean get() = (componentManager as Module?)?.project?.isExternalStorageEnabled ?: false - override fun createFileBasedStorage(path: String, collapsedPath: String, roamingType: RoamingType, rootTagName: String?): StateStorage { - return ModuleFileStorage(this, Paths.get(path), collapsedPath, rootTagName, roamingType, getMacroSubstitutor(collapsedPath), if (roamingType == RoamingType.DISABLED) null else compoundStreamProvider) + override fun createFileBasedStorage(path: Path, collapsedPath: String, roamingType: RoamingType, rootTagName: String?): StateStorage { + return ModuleFileStorage(this, path, collapsedPath, rootTagName, roamingType, getMacroSubstitutor(collapsedPath), if (roamingType == RoamingType.DISABLED) null else compoundStreamProvider) } override fun getFileBasedStorageConfiguration(fileSpec: String) = moduleFileBasedStorageConfiguration diff --git a/platform/configuration-store-impl/src/ModuleStoreImpl.kt b/platform/configuration-store-impl/src/ModuleStoreImpl.kt index 1a0be182dd713..66ec35c1bf503 100644 --- a/platform/configuration-store-impl/src/ModuleStoreImpl.kt +++ b/platform/configuration-store-impl/src/ModuleStoreImpl.kt @@ -8,10 +8,8 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.vfs.VirtualFile import com.intellij.project.isDirectoryBased import com.intellij.util.io.exists -import com.intellij.util.io.systemIndependentPath import org.jetbrains.annotations.ApiStatus import java.nio.file.Path -import kotlin.streams.asSequence private val MODULE_FILE_STORAGE_ANNOTATION = FileStorageAnnotation(StoragePathMacros.MODULE_FILE, false) @@ -25,15 +23,20 @@ internal open class ModuleStoreImpl(module: Module) : ModuleStoreBase() { final override fun getPathMacroManagerForDefaults() = pathMacroManager - // todo what about Upsource? For now this implemented not in the ModuleStoreBase because `project` and `module` are available only in this class (ModuleStoreImpl) override fun getStorageSpecs(component: PersistentStateComponent, stateSpec: State, operation: StateStorageOperation): List { val result = super.getStorageSpecs(component, stateSpec, operation) if (!project.isDirectoryBased) { return result } - return StreamProviderFactory.EP_NAME.extensions(project).asSequence() - .map { LOG.runAndLogException { it.customizeStorageSpecs(component, storageManager, stateSpec, result, operation) } } - .find { it != null } ?: result + + for (provider in StreamProviderFactory.EP_NAME.getExtensionList(project)) { + LOG.runAndLogException { + provider.customizeStorageSpecs(component, storageManager, stateSpec, result, operation)?.let { + return it + } + } + } + return result } } @@ -57,14 +60,19 @@ abstract class ModuleStoreBase : ChildlessComponentStore(), ModuleStore { abstract override val storageManager: StateStorageManagerImpl - override fun getStorageSpecs(component: PersistentStateComponent, stateSpec: State, operation: StateStorageOperation): List = - if (stateSpec.storages.isEmpty()) listOf(MODULE_FILE_STORAGE_ANNOTATION) - else super.getStorageSpecs(component, stateSpec, operation) + override fun getStorageSpecs(component: PersistentStateComponent, stateSpec: State, operation: StateStorageOperation): List { + if (stateSpec.storages.isEmpty()) { + return listOf(MODULE_FILE_STORAGE_ANNOTATION) + } + else { + return super.getStorageSpecs(component, stateSpec, operation) + } + } final override fun setPath(path: Path) = setPath(path, null, false) override fun setPath(path: Path, virtualFile: VirtualFile?, isNew: Boolean) { - val isMacroAdded = storageManager.addMacro(StoragePathMacros.MODULE_FILE, path.systemIndependentPath) + val isMacroAdded = storageManager.setMacros(listOf(Macro(StoragePathMacros.MODULE_FILE, path))).isEmpty() // if file not null - update storage storageManager.getOrCreateStorage(StoragePathMacros.MODULE_FILE, storageCustomizer = { if (this !is FileBasedStorage) { @@ -82,7 +90,7 @@ abstract class ModuleStoreBase : ChildlessComponentStore(), ModuleStore { preloadStorageData(isNew) } else { - storageManager.updatePath(StoragePathMacros.MODULE_FILE, path.systemIndependentPath) + storageManager.updatePath(StoragePathMacros.MODULE_FILE, path) } }) } diff --git a/platform/configuration-store-impl/src/ProjectStateStorageManager.kt b/platform/configuration-store-impl/src/ProjectStateStorageManager.kt index 1e1f4c6f1187c..fd56d91aa6be6 100644 --- a/platform/configuration-store-impl/src/ProjectStateStorageManager.kt +++ b/platform/configuration-store-impl/src/ProjectStateStorageManager.kt @@ -11,6 +11,7 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.serviceContainer.isWorkspaceComponent import org.jdom.Element import org.jetbrains.annotations.ApiStatus +import java.nio.file.Path // extended in upsource open class ProjectStateStorageManager(macroSubstitutor: PathMacroSubstitutor, @@ -45,12 +46,13 @@ open class ProjectStateStorageManager(macroSubstitutor: PathMacroSubstitutor, override fun normalizeFileSpec(fileSpec: String) = removeMacroIfStartsWith(super.normalizeFileSpec(fileSpec), PROJECT_CONFIG_DIR) - override fun expandMacros(path: String): String { - if (path[0] == '$') { - return super.expandMacros(path) + override fun expandMacro(collapsedPath: String): Path { + if (collapsedPath[0] == '$') { + return super.expandMacro(collapsedPath) } else { - return "${expandMacro(PROJECT_CONFIG_DIR)}/$path" + // PROJECT_CONFIG_DIR is the first macro + return macros.get(0).value.resolve(collapsedPath) } } diff --git a/platform/configuration-store-impl/src/ProjectStoreBase.kt b/platform/configuration-store-impl/src/ProjectStoreBase.kt index 02317f495106a..df6b3b826c5df 100644 --- a/platform/configuration-store-impl/src/ProjectStoreBase.kt +++ b/platform/configuration-store-impl/src/ProjectStoreBase.kt @@ -11,7 +11,7 @@ import com.intellij.openapi.components.impl.stores.IProjectStore import com.intellij.openapi.diagnostic.runAndLogException import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectCoreUtil -import com.intellij.openapi.project.getProjectCacheFileName +import com.intellij.openapi.project.doGetProjectFileName import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.vfs.LocalFileSystem @@ -22,12 +22,13 @@ import com.intellij.util.SmartList import com.intellij.util.containers.isNullOrEmpty import com.intellij.util.io.Ksuid import com.intellij.util.io.exists -import com.intellij.util.io.move import com.intellij.util.io.systemIndependentPath import com.intellij.util.text.nullize import kotlinx.coroutines.runBlocking import java.nio.file.Path import java.nio.file.Paths +import java.util.* +import kotlin.collections.ArrayList import kotlin.streams.asSequence internal const val PROJECT_FILE = "\$PROJECT_FILE$" @@ -39,21 +40,18 @@ private val DEPRECATED_PROJECT_FILE_STORAGE_ANNOTATION = FileStorageAnnotation(P // cannot be `internal`, used in Upsource abstract class ProjectStoreBase(final override val project: Project) : ComponentStoreWithExtraComponents(), IProjectStore { private var dirOrFile: Path? = null - - // the protected setter used in Upsource - // Zelix KlassMaster - ERROR: Could not find method 'getScheme()' - var scheme = StorageScheme.DEFAULT + private var dotIdea: Path? = null final override var loadPolicy = StateLoadPolicy.LOAD final override fun isOptimiseTestLoadSpeed() = loadPolicy != StateLoadPolicy.LOAD - final override fun getStorageScheme() = scheme + final override fun getStorageScheme() = if (dotIdea == null) StorageScheme.DEFAULT else StorageScheme.DIRECTORY_BASED abstract override val storageManager: StateStorageManagerImpl protected val isDirectoryBased: Boolean - get() = scheme == StorageScheme.DIRECTORY_BASED + get() = dotIdea != null final override fun setOptimiseTestLoadSpeed(value: Boolean) { // we don't load default state in tests as app store does because @@ -62,14 +60,14 @@ abstract class ProjectStoreBase(final override val project: Project) : Component loadPolicy = if (value) StateLoadPolicy.NOT_LOAD else StateLoadPolicy.LOAD } - final override fun getProjectFilePath() = storageManager.expandMacro(PROJECT_FILE) + final override fun getProjectFilePath() = storageManager.expandMacro(PROJECT_FILE).systemIndependentPath /** * `null` for default or non-directory based project. */ - override fun getProjectConfigDir() = if (isDirectoryBased) storageManager.expandMacro(PROJECT_CONFIG_DIR) else null + override fun getProjectConfigDir() = dotIdea - final override fun getWorkspaceFilePath() = storageManager.expandMacro(StoragePathMacros.WORKSPACE_FILE) + final override fun getWorkspaceFilePath() = storageManager.expandMacro(StoragePathMacros.WORKSPACE_FILE).systemIndependentPath final override fun clearStorages() = storageManager.clearStorages() @@ -78,13 +76,19 @@ abstract class ProjectStoreBase(final override val project: Project) : Component runBlocking { stateStore.save() } val element = stateStore.getStateCopy() ?: return LOG.runAndLogException { - if (isDirectoryBased) { - normalizeDefaultProjectElement(defaultProject, element, Paths.get(storageManager.expandMacro(PROJECT_CONFIG_DIR))) + val dotIdea = dotIdea + if (dotIdea != null) { + normalizeDefaultProjectElement(defaultProject, element, dotIdea) } else { - moveComponentConfiguration(defaultProject, element, { /* doesn't matter, any path will be resolved as projectFilePath (see fileResolver below) */ PROJECT_FILE }) { - if (it == "workspace.xml") Paths.get(workspaceFilePath) - else Paths.get(projectFilePath) + moveComponentConfiguration(defaultProject, element, + storagePathResolver = { /* doesn't matter, any path will be resolved as projectFilePath (see fileResolver below) */ PROJECT_FILE }) { + if (it == "workspace.xml") { + Paths.get(workspaceFilePath) + } + else { + dirOrFile!! + } } } } @@ -110,76 +114,69 @@ abstract class ProjectStoreBase(final override val project: Project) : Component val fs = LocalFileSystem.getInstance() val isUnitTestMode = ApplicationManager.getApplication().isUnitTestMode val filePath = file.systemIndependentPath + val macros = ArrayList(5) if (filePath.endsWith(ProjectFileType.DOT_DEFAULT_EXTENSION)) { - scheme = StorageScheme.DEFAULT - - storageManager.addMacro(PROJECT_FILE, filePath) + macros.add(Macro(PROJECT_FILE, file)) - val workspacePath = composeFileBasedProjectWorkSpacePath(filePath) - storageManager.addMacro(StoragePathMacros.WORKSPACE_FILE, workspacePath) + val workspacePath = file.parent.resolve("${file.fileName.toString().removeSuffix(ProjectFileType.DOT_DEFAULT_EXTENSION)}${WorkspaceFileType.DOT_DEFAULT_EXTENSION}") + macros.add(Macro(StoragePathMacros.WORKSPACE_FILE, workspacePath)) if (isRefreshVfsNeeded) { - VfsUtil.markDirtyAndRefresh(false, true, false, fs.refreshAndFindFileByPath(filePath), fs.refreshAndFindFileByPath(workspacePath)) + VfsUtil.markDirtyAndRefresh(false, true, false, fs.refreshAndFindFileByPath(filePath), fs.refreshAndFindFileByNioFile(workspacePath)) } if (isUnitTestMode) { // load state only if there are existing files isOptimiseTestLoadSpeed = !file.exists() - - storageManager.addMacro(StoragePathMacros.PRODUCT_WORKSPACE_FILE, workspacePath) + macros.add(Macro(StoragePathMacros.PRODUCT_WORKSPACE_FILE, workspacePath)) } } else { - scheme = StorageScheme.DIRECTORY_BASED + val dotIdea = file.resolve(Project.DIRECTORY_STORE_FOLDER) + this.dotIdea = dotIdea - val configDir = "$filePath/${Project.DIRECTORY_STORE_FOLDER}" - storageManager.addMacro(PROJECT_CONFIG_DIR, configDir) - storageManager.addMacro(PROJECT_FILE, "$configDir/misc.xml") - storageManager.addMacro(StoragePathMacros.WORKSPACE_FILE, "$configDir/workspace.xml") + // PROJECT_CONFIG_DIR must be first macro + macros.add(Macro(PROJECT_CONFIG_DIR, dotIdea)) + macros.add(Macro(StoragePathMacros.WORKSPACE_FILE, dotIdea.resolve("workspace.xml"))) + macros.add(Macro(PROJECT_FILE, dotIdea.resolve("misc.xml"))) if (isUnitTestMode) { // load state only if there are existing files isOptimiseTestLoadSpeed = !file.exists() - storageManager.addMacro(StoragePathMacros.PRODUCT_WORKSPACE_FILE, "$configDir/product-workspace.xml") + macros.add(Macro(StoragePathMacros.PRODUCT_WORKSPACE_FILE, dotIdea.resolve("product-workspace.xml"))) } if (isRefreshVfsNeeded) { - VfsUtil.markDirtyAndRefresh(false, true, true, fs.refreshAndFindFileByPath(configDir)) + VfsUtil.markDirtyAndRefresh(false, true, true, fs.refreshAndFindFileByNioFile(dotIdea)) } } + val presentableUrl = (if (dotIdea == null) file else projectBasePath) + val cacheFileName = doGetProjectFileName(presentableUrl.systemIndependentPath, presentableUrl.fileName.toString().toLowerCase(Locale.US).removeSuffix(ProjectFileType.DOT_DEFAULT_EXTENSION), ".", ".xml") + macros.add(Macro(StoragePathMacros.CACHE_FILE, appSystemDir.resolve("workspace").resolve(cacheFileName))) + + storageManager.setMacros(macros) + if (template != null) { loadProjectFromTemplate(template) } - val cacheFileName = project.getProjectCacheFileName(extensionWithDot = ".xml") - storageManager.addMacro(StoragePathMacros.CACHE_FILE, appSystemDir.resolve("workspace").resolve(cacheFileName).systemIndependentPath) - if (isUnitTestMode) { return } - val productSpecificWorkspaceParentDir = "${FileUtil.toSystemIndependentName(PathManager.getConfigPath())}/workspace" + val productSpecificWorkspaceParentDir = PathManager.getConfigDir().resolve("workspace") val projectIdManager = ProjectIdManager.getInstance(project) var projectId = projectIdManager.state.id if (projectId == null) { // do not use project name as part of id, to ensure that project dir renaming also will not cause data loss projectId = Ksuid.generate() projectIdManager.state.id = projectId - - try { - val oldFile = Paths.get("$productSpecificWorkspaceParentDir/$cacheFileName") - if (oldFile.exists()) { - oldFile.move(Paths.get("$productSpecificWorkspaceParentDir/$projectId.xml")) - } - } - catch (e: Exception) { - LOG.error(e) - } } - storageManager.addMacro(StoragePathMacros.PRODUCT_WORKSPACE_FILE, "$productSpecificWorkspaceParentDir/$projectId.xml") + macros.add(Macro(StoragePathMacros.PRODUCT_WORKSPACE_FILE, productSpecificWorkspaceParentDir.resolve("$projectId.xml"))) + storageManager.setMacros(macros) } override fun getStorageSpecs(component: PersistentStateComponent, stateSpec: State, operation: StateStorageOperation): List { @@ -282,8 +279,6 @@ abstract class ProjectStoreBase(final override val project: Project) : Component override suspend fun doSave(result: SaveResult, forceSavingAllSettings: Boolean) { } } -private fun composeFileBasedProjectWorkSpacePath(filePath: String) = "${FileUtil.getNameWithoutExtension(filePath)}${WorkspaceFileType.DOT_DEFAULT_EXTENSION}" - private fun isSpecialStorage(storage: Storage) = isSpecialStorage(storage.path) internal fun isSpecialStorage(collapsedPath: String): Boolean = diff --git a/platform/configuration-store-impl/src/StateStorageManagerImpl.kt b/platform/configuration-store-impl/src/StateStorageManagerImpl.kt index 9f627e6b455d9..e404518554f55 100644 --- a/platform/configuration-store-impl/src/StateStorageManagerImpl.kt +++ b/platform/configuration-store-impl/src/StateStorageManagerImpl.kt @@ -9,26 +9,17 @@ import com.intellij.openapi.components.StateStorageChooserEx.Resolution import com.intellij.openapi.roots.ProjectModelElement import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.io.FileUtilRt -import com.intellij.openapi.util.text.StringUtil -import com.intellij.openapi.vfs.newvfs.events.VFileEvent -import com.intellij.util.PathUtilRt import com.intellij.util.ReflectionUtil import com.intellij.util.SmartList import com.intellij.util.ThreeState -import com.intellij.util.containers.ContainerUtil import com.intellij.util.io.systemIndependentPath import org.jdom.Element import org.jetbrains.annotations.TestOnly -import java.io.IOException import java.nio.file.Path -import java.nio.file.Paths import java.util.concurrent.locks.ReentrantReadWriteLock -import java.util.regex.Pattern import kotlin.concurrent.read import kotlin.concurrent.write -private val MACRO_PATTERN = Pattern.compile("(\\$[^$]*\\$)") - /** * If componentManager not specified, storage will not add file tracker */ @@ -36,8 +27,10 @@ open class StateStorageManagerImpl(private val rootTagName: String, final override val macroSubstitutor: PathMacroSubstitutor? = null, override val componentManager: ComponentManager? = null, private val virtualFileTracker: StorageVirtualFileTracker? = createDefaultVirtualTracker(componentManager)) : StateStorageManager { - private val macros: MutableList = ContainerUtil.createLockFreeCopyOnWriteList() - private val storageLock = ReentrantReadWriteLock() + @Volatile + protected var macros: List = emptyList() + + protected val storageLock = ReentrantReadWriteLock() private val storages = HashMap() val compoundStreamProvider: CompoundStreamProvider = CompoundStreamProvider() @@ -91,46 +84,16 @@ open class StateStorageManagerImpl(private val rootTagName: String, } } - private data class Macro(val key: String, var value: String) - @TestOnly fun getVirtualFileTracker() = virtualFileTracker /** - * @param expansion System-independent - * @return `false` if the [key] was updated, `true` if the new key was attached + * Returns old map. */ - fun addMacro(key: String, expansion: String): Boolean { - LOG.assertTrue(key.isNotEmpty()) - - val value: String - if (expansion.contains('\\')) { - LOG.error("Macro $key set to system-dependent expansion $expansion") - value = FileUtilRt.toSystemIndependentName(expansion) - } - else { - value = expansion - } - - // e.g ModuleImpl.setModuleFilePath update macro value - for (macro in macros) { - if (key == macro.key) { - macro.value = value - return false - } - } - - macros.add(Macro(key, value)) - return true - } - - // system-independent paths - open fun pathRenamed(oldPath: String, newPath: String, event: VFileEvent?) { - for (macro in macros) { - if (oldPath == macro.value) { - macro.value = newPath - } - } + fun setMacros(map: List): List { + val oldValue = macros + macros = map + return oldValue } @Suppress("CAST_NEVER_SUCCEEDS") @@ -194,15 +157,15 @@ open class StateStorageManagerImpl(private val rootTagName: String, Pair(getCachedFileStorages(changed, pathNormalizer), getCachedFileStorages(deleted, pathNormalizer)) } - fun updatePath(spec: String, newPath: String) { + fun updatePath(spec: String, newPath: Path) { val storage = getCachedFileStorages(listOf(spec)).firstOrNull() ?: return if (storage is StorageVirtualFileTracker.TrackedStorage) { virtualFileTracker?.let { tracker -> tracker.remove(storage.file.systemIndependentPath) - tracker.put(newPath, storage) + tracker.put(newPath.systemIndependentPath, storage) } } - storage.setFile(null, resolvePath(newPath)) + storage.setFile(null, newPath) } fun getCachedFileStorages(fileSpecs: Collection, pathNormalizer: ((String) -> String)? = null): Collection { @@ -248,30 +211,30 @@ open class StateStorageManagerImpl(private val rootTagName: String, isUseVfsListener = ThreeState.fromBoolean(!compoundStreamProvider.isApplicable(collapsedPath, effectiveRoamingType)) } - val filePath = expandMacros(collapsedPath) + val filePath = expandMacro(collapsedPath) @Suppress("DEPRECATION") if (stateSplitter != StateSplitter::class.java && stateSplitter != StateSplitterEx::class.java) { val storage = createDirectoryBasedStorage(filePath, collapsedPath, ReflectionUtil.newInstance(stateSplitter)) if (storage is StorageVirtualFileTracker.TrackedStorage) { - virtualFileTracker?.put(filePath, storage) + virtualFileTracker?.put(filePath.systemIndependentPath, storage) } return storage } val app = ApplicationManager.getApplication() - if (app != null && !app.isHeadlessEnvironment && PathUtilRt.getFileName(filePath).lastIndexOf('.') < 0) { + if (app != null && !app.isHeadlessEnvironment && !filePath.fileName.toString().contains('.')) { throw IllegalArgumentException("Extension is missing for storage file: $filePath") } val storage = createFileBasedStorage(filePath, collapsedPath, effectiveRoamingType, if (exclusive) null else rootTagName) if (isUseVfsListener == ThreeState.YES && storage is StorageVirtualFileTracker.TrackedStorage) { - virtualFileTracker?.put(filePath, storage) + virtualFileTracker?.put(filePath.systemIndependentPath, storage) } return storage } // open for upsource - protected open fun createFileBasedStorage(path: String, + protected open fun createFileBasedStorage(path: Path, collapsedPath: String, roamingType: RoamingType, rootTagName: String?): StateStorage { @@ -283,17 +246,15 @@ open class StateStorageManagerImpl(private val rootTagName: String, else { compoundStreamProvider } - return MyFileStorage(this, resolvePath(path), collapsedPath, rootTagName, roamingType, getMacroSubstitutor(collapsedPath), provider) + return MyFileStorage(this, path, collapsedPath, rootTagName, roamingType, getMacroSubstitutor(collapsedPath), provider) } // open for upsource - protected open fun createDirectoryBasedStorage(path: String, collapsedPath: String, @Suppress( - "DEPRECATION") splitter: StateSplitter): StateStorage { - return MyDirectoryStorage(this, resolvePath(path), splitter) + protected open fun createDirectoryBasedStorage(path: Path, collapsedPath: String, @Suppress("DEPRECATION") splitter: StateSplitter): StateStorage { + return MyDirectoryStorage(this, path, splitter) } - private class MyDirectoryStorage(override val storageManager: StateStorageManagerImpl, file: Path, @Suppress( - "DEPRECATION") splitter: StateSplitter) : + private class MyDirectoryStorage(override val storageManager: StateStorageManagerImpl, file: Path, @Suppress("DEPRECATION") splitter: StateSplitter) : DirectoryBasedStorage(file, splitter, storageManager.macroSubstitutor), StorageVirtualFileTracker.TrackedStorage protected open class MyFileStorage(override val storageManager: StateStorageManagerImpl, @@ -349,35 +310,12 @@ open class StateStorageManagerImpl(private val rootTagName: String, protected open fun beforeElementLoaded(element: Element) { } - final override fun rename(path: String, newName: String) { - storageLock.write { - val storage = getOrCreateStorage(collapseMacros(path), RoamingType.DEFAULT) as FileBasedStorage - - val file = storage.getVirtualFile(StateStorageOperation.WRITE) - try { - if (file != null) { - file.rename(storage, newName) - } - else if (storage.file.fileName.toString() != newName) { - // old file didn't exist or renaming failed - val expandedPath = expandMacros(path) - val parentPath = PathUtilRt.getParentPath(expandedPath) - storage.setFile(null, resolvePath(parentPath).resolve(newName)) - pathRenamed(expandedPath, "$parentPath/$newName", null) - } - } - catch (e: IOException) { - LOG.debug(e) - } - } - } - fun clearStorages() { storageLock.write { try { if (virtualFileTracker != null) { for (collapsedPath in storages.keys) { - virtualFileTracker.remove(expandMacros(collapsedPath)) + virtualFileTracker.remove(expandMacro(collapsedPath).systemIndependentPath) } } } @@ -389,41 +327,24 @@ open class StateStorageManagerImpl(private val rootTagName: String, protected open fun getMacroSubstitutor(fileSpec: String): PathMacroSubstitutor? = macroSubstitutor - override fun expandMacros(path: String): String { - // replacement can contains $ (php tests), so, this check must be performed before expand - val matcher = MACRO_PATTERN.matcher(path) - matcherLoop@ - while (matcher.find()) { - val m = matcher.group(1) - for ((key) in macros) { - if (key == m) { - continue@matcherLoop - } - } - throw UnknownMacroException("Unknown macro: $m in storage file spec: $path") - } - - var expanded = path - for ((key, value) in macros) { - expanded = StringUtil.replace(expanded, key, value) - } - return expanded - } - - fun expandMacro(macro: String): String { + override fun expandMacro(collapsedPath: String): Path { for ((key, value) in macros) { - if (key == macro) { + if (key == collapsedPath) { return value } + + if (collapsedPath.length > (key.length + 2) && collapsedPath[key.length] == '/' && collapsedPath.startsWith(key)) { + return value.resolve(collapsedPath.substring(key.length + 1)) + } } - throw UnknownMacroException("Unknown macro $macro") + throw IllegalStateException("Cannot resolve $collapsedPath in $macros") } fun collapseMacros(path: String): String { var result = path for ((key, value) in macros) { - result = result.replace(value, key) + result = result.replace(value.systemIndependentPath, key) } return normalizeFileSpec(result) } @@ -434,8 +355,6 @@ open class StateStorageManagerImpl(private val rootTagName: String, } protected open fun getOldStorageSpec(component: Any, componentName: String, operation: StateStorageOperation): String? = null - - protected open fun resolvePath(path: String): Path = Paths.get(path) } private fun String.startsWithMacro(macro: String): Boolean { @@ -458,4 +377,6 @@ internal fun getEffectiveRoamingType(roamingType: RoamingType, collapsedPath: St } } +data class Macro(val key: String, var value: Path) + class UnknownMacroException(message: String) : RuntimeException(message) \ No newline at end of file diff --git a/platform/configuration-store-impl/src/StorageVirtualFileTracker.kt b/platform/configuration-store-impl/src/StorageVirtualFileTracker.kt index 768ed7f94a251..2d87bc333ba60 100644 --- a/platform/configuration-store-impl/src/StorageVirtualFileTracker.kt +++ b/platform/configuration-store-impl/src/StorageVirtualFileTracker.kt @@ -67,7 +67,7 @@ class StorageVirtualFileTracker(private val messageBus: MessageBus) { // we don't support DirectoryBasedStorage renaming // StoragePathMacros.MODULE_FILE -> old path, we must update value - storage.storageManager.pathRenamed(oldPath, event.path, event) + (storage.storageManager as? RenameableStateStorageManager)?.pathRenamed(Paths.get(event.path), event) } } else { diff --git a/platform/configuration-store-impl/src/defaultProjectElementNormalizer.kt b/platform/configuration-store-impl/src/defaultProjectElementNormalizer.kt index d37b36ee06ce9..bedfa2c393236 100644 --- a/platform/configuration-store-impl/src/defaultProjectElementNormalizer.kt +++ b/platform/configuration-store-impl/src/defaultProjectElementNormalizer.kt @@ -11,7 +11,6 @@ import com.intellij.serviceContainer.processAllImplementationClasses import com.intellij.serviceContainer.processComponentInstancesOfType import com.intellij.util.LineSeparator import com.intellij.util.SmartList -import com.intellij.util.containers.CollectionFactory import com.intellij.util.io.exists import com.intellij.util.io.outputStream import com.intellij.util.isEmpty @@ -109,7 +108,7 @@ internal fun moveComponentConfiguration(defaultProject: Project, // ignore - this data should be not copied ignoredComponentNames.add(stateAnnotation.name) } - else -> storageNameToComponentNames.getOrPut(storagePathResolver(storagePath)) { CollectionFactory.createSmallMemoryFootprintSet() }.add(stateAnnotation.name) + else -> storageNameToComponentNames.getOrPut(storagePathResolver(storagePath)) { HashSet() }.add(stateAnnotation.name) } } diff --git a/platform/configuration-store-impl/src/schemeManager/SchemeManagerFactoryImpl.kt b/platform/configuration-store-impl/src/schemeManager/SchemeManagerFactoryImpl.kt index 88ffc620dc37c..a64a4d93ce756 100644 --- a/platform/configuration-store-impl/src/schemeManager/SchemeManagerFactoryImpl.kt +++ b/platform/configuration-store-impl/src/schemeManager/SchemeManagerFactoryImpl.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2019 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. +// 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.configurationStore.schemeManager import com.intellij.configurationStore.* @@ -117,7 +117,7 @@ sealed class SchemeManagerFactoryBase : SchemeManagerFactory(), com.intellij.ope } override fun pathToFile(path: String): Path { - return Paths.get(ApplicationManager.getApplication().stateStore.storageManager.expandMacros(ROOT_CONFIG), path) + return ApplicationManager.getApplication().stateStore.storageManager.expandMacro(ROOT_CONFIG).resolve(path) } } @@ -144,7 +144,7 @@ sealed class SchemeManagerFactoryBase : SchemeManagerFactory(), com.intellij.ope override fun pathToFile(path: String): Path { if (project.isDefault) { - // not idea how to solve this issue (run SingleInspectionProfilePanelTest) in a quick and safe way + // no idea how to solve this issue (run SingleInspectionProfilePanelTest) in a quick and safe way return Paths.get("__not_existent_path__") } @@ -153,7 +153,7 @@ sealed class SchemeManagerFactoryBase : SchemeManagerFactory(), com.intellij.ope return Paths.get(project.basePath!!, ".$path") } else { - return Paths.get(projectFileDir, path) + return projectFileDir.resolve(path) } } } diff --git a/platform/configuration-store-impl/testSrc/ApplicationStoreTest.kt b/platform/configuration-store-impl/testSrc/ApplicationStoreTest.kt index 3308e7c5c0628..eee485838f8e0 100644 --- a/platform/configuration-store-impl/testSrc/ApplicationStoreTest.kt +++ b/platform/configuration-store-impl/testSrc/ApplicationStoreTest.kt @@ -6,10 +6,13 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.* import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream -import com.intellij.testFramework.* +import com.intellij.testFramework.ApplicationRule +import com.intellij.testFramework.DisposableRule +import com.intellij.testFramework.ExtensionTestUtil import com.intellij.testFramework.assertions.Assertions.assertThat +import com.intellij.testFramework.refreshVfs +import com.intellij.testFramework.rules.InMemoryFsRule import com.intellij.util.io.lastModified -import com.intellij.util.io.systemIndependentPath import com.intellij.util.io.write import com.intellij.util.io.writeChild import com.intellij.util.pico.DefaultPicoContainer @@ -28,7 +31,6 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.InputStream import java.nio.file.Path -import java.nio.file.Paths import java.util.* import kotlin.collections.HashMap import kotlin.properties.Delegates @@ -42,7 +44,7 @@ internal class ApplicationStoreTest { @JvmField @Rule - val tempDirManager = TemporaryDirectory() + val fsRule = InMemoryFsRule() @JvmField @Rule @@ -53,7 +55,7 @@ internal class ApplicationStoreTest { @Before fun setUp() { - testAppConfig = tempDirManager.newPath() + testAppConfig = fsRule.fs.getPath("/app-config") componentStore = MyComponentStore(testAppConfig) } @@ -87,7 +89,7 @@ internal class ApplicationStoreTest { componentStore.initComponent(component, null, null) assertThat(component.foo).isEqualTo("newValue") - assertThat(Paths.get(storageManager.expandMacros(fileSpec))).doesNotExist() + assertThat(storageManager.expandMacro(fileSpec)).doesNotExist() } @Test @@ -126,48 +128,47 @@ internal class ApplicationStoreTest { testAppConfig.refreshVfs() val storageManager = ApplicationManager.getApplication().stateStore.storageManager - val optionsPath = storageManager.expandMacros(APP_CONFIG) - val rootConfigPath = storageManager.expandMacros(ROOT_CONFIG) + val optionsPath = storageManager.expandMacro(APP_CONFIG) + val rootConfigPath = storageManager.expandMacro(ROOT_CONFIG) val map = getExportableComponentsMap(false, true, storageManager) assertThat(map).isNotEmpty fun test(item: ExportableItem) { val file = item.file - assertThat(map[file]).containsExactly(item) + assertThat(map.get(file)).containsExactly(item) assertThat(file).doesNotExist() } - test(ExportableItem(Paths.get(optionsPath, "filetypes.xml"), "File types", RoamingType.DEFAULT)) - test(ExportableItem(Paths.get(rootConfigPath, "filetypes"), "File types (schemes)", RoamingType.DEFAULT)) - test(ExportableItem(Paths.get(optionsPath, "customization.xml"), "Menus and toolbars customization", RoamingType.DEFAULT)) - test(ExportableItem(Paths.get(optionsPath, "templates.xml"), "Live templates", RoamingType.DEFAULT)) - test(ExportableItem(Paths.get(rootConfigPath, "templates"), "Live templates (schemes)", RoamingType.DEFAULT)) + test(ExportableItem(optionsPath.resolve("filetypes.xml"), "File types", RoamingType.DEFAULT)) + test(ExportableItem(rootConfigPath.resolve("filetypes"), "File types (schemes)", RoamingType.DEFAULT)) + test(ExportableItem(optionsPath.resolve("customization.xml"), "Menus and toolbars customization", RoamingType.DEFAULT)) + test(ExportableItem(optionsPath.resolve("templates.xml"), "Live templates", RoamingType.DEFAULT)) + test(ExportableItem(rootConfigPath.resolve("templates"), "Live templates (schemes)", RoamingType.DEFAULT)) } @Test - fun `import settings`() = runBlocking { - testAppConfig.refreshVfs() - + fun `import settings`() { val component = A() componentStore.initComponent(component, null, null) component.options.foo = "new" - componentStore.save() + runBlocking { + componentStore.save() + } val storageManager = componentStore.storageManager - val configPath = storageManager.expandMacros(ROOT_CONFIG) - val configDir = Paths.get(configPath) + val configDir = storageManager.expandMacro(ROOT_CONFIG) val componentPath = configDir.resolve("a.xml") - assertThat(componentPath).isRegularFile + assertThat(componentPath).isRegularFile() // additional export path val additionalPath = configDir.resolve("foo") additionalPath.writeChild("bar.icls", "") val exportedData = BufferExposingByteArrayOutputStream() - exportSettings(setOf(componentPath, additionalPath), exportedData, configPath) + exportSettings(setOf(componentPath, additionalPath), exportedData, configDir) val relativePaths = getPaths(exportedData.toInputStream()) assertThat(relativePaths).containsOnly("a.xml", "foo/", "foo/bar.icls", "IntelliJ IDEA Global Settings") @@ -178,8 +179,8 @@ internal class ApplicationStoreTest { val componentKey = A::class.java.name picoContainer.registerComponent(DefaultPicoContainer.InstanceComponentAdapter(componentKey, component)) try { - assertThat(getExportableComponentsMap(false, false, storageManager, relativePaths)).containsOnly( - componentPath.to(listOf(ExportableItem(componentPath, ""))), additionalPath.to(listOf(ExportableItem(additionalPath, " (schemes)")))) + assertThat(getExportableComponentsMap(isOnlyExisting = false, isComputePresentableNames = false, storageManager = storageManager, onlyPaths = relativePaths)) + .containsOnly(componentPath.to(listOf(ExportableItem(componentPath, ""))), additionalPath.to(listOf(ExportableItem(additionalPath, " (schemes)")))) } finally { picoContainer.unregisterComponent(componentKey) @@ -411,9 +412,8 @@ internal class ApplicationStoreTest { } override fun setPath(path: Path) { - storageManager.addMacro(APP_CONFIG, path.systemIndependentPath) // yes, in tests APP_CONFIG equals to ROOT_CONFIG (as ICS does) - storageManager.addMacro(ROOT_CONFIG, path.systemIndependentPath) + storageManager.setMacros(listOf(Macro(APP_CONFIG, path), Macro(ROOT_CONFIG, path))) } override suspend fun doSave(result: SaveResult, forceSavingAllSettings: Boolean) { diff --git a/platform/configuration-store-impl/testSrc/ComponentStoreModificationTrackerTest.kt b/platform/configuration-store-impl/testSrc/ComponentStoreModificationTrackerTest.kt index 18268498bbf80..1d92cd6665efb 100644 --- a/platform/configuration-store-impl/testSrc/ComponentStoreModificationTrackerTest.kt +++ b/platform/configuration-store-impl/testSrc/ComponentStoreModificationTrackerTest.kt @@ -12,7 +12,6 @@ import com.intellij.testFramework.RunsInEdt import com.intellij.testFramework.assertions.Assertions.assertThat import com.intellij.testFramework.rules.InMemoryFsRule import com.intellij.util.ExceptionUtil -import com.intellij.util.io.systemIndependentPath import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Rule @@ -164,32 +163,27 @@ internal class ComponentStoreModificationTrackerTest { """.trimIndent()) } - } private class MyComponentStore(testAppConfigPath: Path) : ChildlessComponentStore() { - private class MyStorageManager(private val rootDir: Path) : StateStorageManagerImpl("application") { + private class MyStorageManager : StateStorageManagerImpl("application") { override fun getFileBasedStorageConfiguration(fileSpec: String) = appFileBasedStorageConfiguration override val isUseXmlProlog = false override fun normalizeFileSpec(fileSpec: String) = removeMacroIfStartsWith(super.normalizeFileSpec(fileSpec), APP_CONFIG) - override fun expandMacros(path: String) = if (path[0] == '$') super.expandMacros(path) else "${expandMacro(APP_CONFIG)}/$path" - - override fun resolvePath(path: String): Path = rootDir.resolve(path) + override fun expandMacro(collapsedPath: String): Path = if (collapsedPath[0] == '$') super.expandMacro(collapsedPath) else macros.get(0).value.resolve(collapsedPath) } - override val storageManager = MyStorageManager(testAppConfigPath) + override val storageManager = MyStorageManager() init { setPath(testAppConfigPath) } override fun setPath(path: Path) { - val systemIndependentPath = path.systemIndependentPath - storageManager.addMacro(APP_CONFIG, systemIndependentPath) // yes, in tests APP_CONFIG equals to ROOT_CONFIG (as ICS does) - storageManager.addMacro(ROOT_CONFIG, systemIndependentPath) + storageManager.setMacros(listOf(Macro(APP_CONFIG, path), Macro(ROOT_CONFIG, path))) } } \ No newline at end of file diff --git a/platform/configuration-store-impl/testSrc/DefaultProjectStoreTest.kt b/platform/configuration-store-impl/testSrc/DefaultProjectStoreTest.kt index 4fe333416d852..1f4d38a953792 100644 --- a/platform/configuration-store-impl/testSrc/DefaultProjectStoreTest.kt +++ b/platform/configuration-store-impl/testSrc/DefaultProjectStoreTest.kt @@ -1,11 +1,9 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.configurationStore -import com.intellij.externalDependencies.DependencyOnPlugin -import com.intellij.externalDependencies.ExternalDependenciesManager -import com.intellij.externalDependencies.ProjectExternalDependency +import com.intellij.ide.highlighter.ProjectFileType import com.intellij.openapi.application.ex.PathManagerEx -import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.project.ex.ProjectManagerEx import com.intellij.openapi.util.JDOMUtil @@ -19,6 +17,7 @@ import com.intellij.util.isEmpty import org.junit.ClassRule import org.junit.Rule import org.junit.Test +import java.nio.file.Path import java.nio.file.Paths @Suppress("UsePropertyAccessSyntax") @@ -34,7 +33,6 @@ internal class DefaultProjectStoreTest { val fsRule = InMemoryFsRule() private val tempDirManager = TemporaryDirectory() - private val requiredPlugins = listOf(DependencyOnPlugin("fake", "0", "1")) @JvmField @Rule @@ -42,29 +40,29 @@ internal class DefaultProjectStoreTest { @Test fun `new project from default - file-based storage`() { - val externalDependenciesManager = ProjectManager.getInstance().defaultProject.service() - externalDependenciesManager.allDependencies = requiredPlugins - try { - createProjectAndUseInLoadComponentStateMode(tempDirManager) { - assertThat(it.service().allDependencies).isEqualTo(requiredPlugins) + checkDefaultProjectAsTemplate { checkTask -> + val project = openAsNewProjectAndUseDefaultSettings(fsRule.fs.getPath("/test${ProjectFileType.DOT_DEFAULT_EXTENSION}")) + project.use { + checkTask(project, true) } } - finally { - externalDependenciesManager.allDependencies = emptyList() - } } @Test fun `new project from default - directory-based storage`() { checkDefaultProjectAsTemplate { checkTask -> // obviously, project must be directory-based also - val project = ProjectManagerEx.getInstanceEx().openProject(tempDirManager.newPath("test"), createTestOpenProjectOptions().copy(isNewProject = true, useDefaultProjectAsTemplate = true))!! + val project = openAsNewProjectAndUseDefaultSettings(fsRule.fs.getPath("/test")) project.use { checkTask(project, true) } } } + private fun openAsNewProjectAndUseDefaultSettings(file: Path): Project { + return ProjectManagerEx.getInstanceEx().openProject(file, createTestOpenProjectOptions().copy(isNewProject = true, useDefaultProjectAsTemplate = true))!! + } + @Test fun `new project from default - remove workspace component configuration`() { val testData = Paths.get(PathManagerEx.getCommunityHomePath(), "platform/configuration-store-impl/testData") diff --git a/platform/configuration-store-impl/testSrc/DoNotSaveDefaultsTest.kt b/platform/configuration-store-impl/testSrc/DoNotSaveDefaultsTest.kt index 64d8b1108014a..2653b3510b764 100644 --- a/platform/configuration-store-impl/testSrc/DoNotSaveDefaultsTest.kt +++ b/platform/configuration-store-impl/testSrc/DoNotSaveDefaultsTest.kt @@ -94,7 +94,7 @@ internal class DoNotSaveDefaultsTest { return } - val directoryTree = Paths.get(componentManager.stateStore.storageManager.expandMacros(APP_CONFIG)).getDirectoryTree(hashSetOf( + val directoryTree = componentManager.stateStore.storageManager.expandMacro(APP_CONFIG).getDirectoryTree(hashSetOf( "path.macros.xml" /* todo EP to register (provide) macro dynamically */, "stubIndex.xml" /* low-level non-roamable stuff */, "usage.statistics.xml" /* SHOW_NOTIFICATION_ATTR in internal mode */, diff --git a/platform/configuration-store-impl/testSrc/ModuleStoreRenameTest.kt b/platform/configuration-store-impl/testSrc/ModuleStoreRenameTest.kt index cf164d8501159..179964109c7da 100644 --- a/platform/configuration-store-impl/testSrc/ModuleStoreRenameTest.kt +++ b/platform/configuration-store-impl/testSrc/ModuleStoreRenameTest.kt @@ -7,8 +7,6 @@ import com.intellij.openapi.application.AppUIExecutor import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.impl.coroutineDispatchingContext import com.intellij.openapi.application.runWriteAction -import com.intellij.openapi.command.impl.UndoManagerImpl -import com.intellij.openapi.command.undo.UndoManager import com.intellij.openapi.components.StateStorageOperation import com.intellij.openapi.components.StoragePathMacros import com.intellij.openapi.components.stateStore @@ -29,7 +27,6 @@ import com.intellij.util.io.readText import com.intellij.util.io.systemIndependentPath import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -import org.junit.After import org.junit.ClassRule import org.junit.Rule import org.junit.Test @@ -40,6 +37,7 @@ import kotlin.properties.Delegates private val Module.storage: FileBasedStorage get() = (stateStore.storageManager as StateStorageManagerImpl).getCachedFileStorages(listOf(StoragePathMacros.MODULE_FILE)).first() +@RunsInActiveStoreMode internal class ModuleStoreRenameTest { companion object { @JvmField @@ -59,9 +57,10 @@ internal class ModuleStoreRenameTest { @JvmField val ruleChain = RuleChain( tempDirManager, + ActiveStoreRule(projectRule), object : ExternalResource() { override fun before() { - runInEdtAndWait { + ApplicationManager.getApplication().invokeAndWait { val moduleFileParent = tempDirManager.newPath() module = projectRule.createModule(moduleFileParent.resolve("m.iml")) dependentModule = projectRule.createModule(moduleFileParent.resolve("dependent-module.iml")) @@ -89,15 +88,6 @@ internal class ModuleStoreRenameTest { DisposeModulesRule(projectRule) ) - @After - fun tearDown() { - ApplicationManager.getApplication().invokeAndWait { - val undoManager = UndoManager.getInstance(projectRule.project) as UndoManagerImpl - undoManager.dropHistoryInTests() - undoManager.flushCurrentCommandMerger() - } - } - // project structure @Test fun `rename module using model`() = runBlocking { @@ -152,7 +142,7 @@ internal class ModuleStoreRenameTest { assertThat(newFile).isRegularFile // ensure that macro value updated - assertThat(module.stateStore.storageManager.expandMacros(StoragePathMacros.MODULE_FILE)).isEqualTo(newFile.systemIndependentPath) + assertThat(module.stateStore.storageManager.expandMacro(StoragePathMacros.MODULE_FILE)).isEqualTo(newFile) saveProjectState() assertThat(dependentModule.storage.file.readText()).contains("""""") diff --git a/platform/configuration-store-impl/testSrc/ModuleStoreTest.kt b/platform/configuration-store-impl/testSrc/ModuleStoreTest.kt index d26009c77e3ab..6a43031c558eb 100644 --- a/platform/configuration-store-impl/testSrc/ModuleStoreTest.kt +++ b/platform/configuration-store-impl/testSrc/ModuleStoreTest.kt @@ -128,11 +128,10 @@ class ModuleStoreTest { } fun removeContentRoot(module: Module) { - val modulePath = module.stateStore.storageManager.expandMacros(StoragePathMacros.MODULE_FILE) - val moduleFile = Paths.get(modulePath) + val moduleFile = module.stateStore.storageManager.expandMacro(StoragePathMacros.MODULE_FILE) assertThat(moduleFile).isRegularFile - val virtualFile = LocalFileSystem.getInstance().findFileByPath(modulePath)!! + val virtualFile = LocalFileSystem.getInstance().findFileByNioFile(moduleFile)!! val oldText = moduleFile.readText() val newText = oldText.replace("\n", "") assertThat(oldText).isNotEqualTo(newText) diff --git a/platform/configuration-store-impl/testSrc/ProjectStoreTest.kt b/platform/configuration-store-impl/testSrc/ProjectStoreTest.kt index 619c25d1d465b..54976562ed8d2 100644 --- a/platform/configuration-store-impl/testSrc/ProjectStoreTest.kt +++ b/platform/configuration-store-impl/testSrc/ProjectStoreTest.kt @@ -51,7 +51,7 @@ internal class ProjectStoreTest { """.trimIndent() - @State(name = "AATestComponent") + @State(name = "AATestComponent", allowLoadInTests = true) private class TestComponent : PersistentStateComponent { private var state: TestState? = null @@ -75,7 +75,7 @@ internal class ProjectStoreTest { assertThat(project.basePath).isEqualTo(PathUtil.getParentPath((PathUtil.getParentPath(project.projectFilePath!!)))) // test reload on external change - val file = Paths.get(project.stateStore.storageManager.expandMacros(PROJECT_FILE)) + val file = project.stateStore.storageManager.expandMacro(PROJECT_FILE) file.write(file.readText().replace("""