diff --git a/platform/configuration-store-impl/src/schemeManager/SchemeListManager.kt b/platform/configuration-store-impl/src/schemeManager/SchemeListManager.kt index ecdf1c3ba43a..488d79e329cc 100644 --- a/platform/configuration-store-impl/src/schemeManager/SchemeListManager.kt +++ b/platform/configuration-store-impl/src/schemeManager/SchemeListManager.kt @@ -118,26 +118,4 @@ internal class SchemeListManager(private val schemeManager: SchemeManag schemes.mapTo(result) { schemeManager.processor.getSchemeKey(it) } return result } - - fun removeFirstScheme(schemes: MutableList, scheduleDelete: Boolean = true, condition: (T) -> Boolean): T? { - val iterator = schemes.iterator() - for (scheme in iterator) { - if (!condition(scheme)) { - continue - } - - if (schemeManager.activeScheme === scheme) { - schemeManager.activeScheme = null - } - - iterator.remove() - - if (scheduleDelete && schemeManager.processor.isExternalizable(scheme)) { - schemeManager.schemeToInfo.remove(scheme)?.let(schemeManager::scheduleDelete) - } - return scheme - } - - return null - } } \ No newline at end of file diff --git a/platform/configuration-store-impl/src/schemeManager/SchemeManagerImpl.kt b/platform/configuration-store-impl/src/schemeManager/SchemeManagerImpl.kt index 12fc53b043ad..e1c26cf07b1d 100644 --- a/platform/configuration-store-impl/src/schemeManager/SchemeManagerImpl.kt +++ b/platform/configuration-store-impl/src/schemeManager/SchemeManagerImpl.kt @@ -132,8 +132,7 @@ class SchemeManagerImpl(val fileSpec: String, externalInfo.schemeKey = schemeKey - val scheme = (processor as LazySchemeProcessor).createScheme( - SchemeDataHolderImpl(processor, bytes, externalInfo), schemeKey, attributeProvider, true) + val scheme = (processor as LazySchemeProcessor).createScheme(SchemeDataHolderImpl(processor, bytes, externalInfo), schemeKey, attributeProvider, true) val oldInfo = schemeToInfo.put(scheme, externalInfo) LOG.assertTrue(oldInfo == null) val oldScheme = schemeListManager.readOnlyExternalizableSchemes.put(schemeKey, scheme) @@ -559,15 +558,38 @@ class SchemeManagerImpl(val fileSpec: String, override fun findSchemeByName(schemeName: String) = schemes.firstOrNull { processor.getSchemeKey(it) == schemeName } - override fun removeScheme(name: String) = schemeListManager.removeFirstScheme(schemes) {processor.getSchemeKey(it) == name } + override fun removeScheme(name: String) = removeFirstScheme(schemes, this) {processor.getSchemeKey(it) == name } - override fun removeScheme(scheme: T) = schemeListManager.removeFirstScheme(schemes) { it == scheme } != null + override fun removeScheme(scheme: T) = removeFirstScheme(schemes, this) { it == scheme } != null override fun isMetadataEditable(scheme: T) = !schemeListManager.readOnlyExternalizableSchemes.containsKey(processor.getSchemeKey(scheme)) override fun toString() = fileSpec } +// static method to ensure that receiver state is not used +private fun removeFirstScheme(schemes: MutableList, schemeManager: SchemeManagerImpl, condition: (T) -> Boolean): T? { + val iterator = schemes.iterator() + for (scheme in iterator) { + if (!condition(scheme)) { + continue + } + + if (schemeManager.activeScheme === scheme) { + schemeManager.activeScheme = null + } + + iterator.remove() + + if (schemeManager.processor.isExternalizable(scheme)) { + schemeManager.schemeToInfo.remove(scheme)?.let(schemeManager::scheduleDelete) + } + return scheme + } + + return null +} + internal fun nameIsMissed(bytes: ByteArray): RuntimeException { return RuntimeException("Name is missed:\n${bytes.toString(Charsets.UTF_8)}") } diff --git a/platform/configuration-store-impl/src/schemeManager/schemeLoader.kt b/platform/configuration-store-impl/src/schemeManager/schemeLoader.kt index 2a67f300599a..be3c6a74fcbe 100644 --- a/platform/configuration-store-impl/src/schemeManager/schemeLoader.kt +++ b/platform/configuration-store-impl/src/schemeManager/schemeLoader.kt @@ -10,6 +10,7 @@ import com.intellij.openapi.util.Ref import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.containers.ConcurrentList +import com.intellij.util.containers.ContainerUtil import com.intellij.util.io.createDirectories import com.intellij.util.io.systemIndependentPath import gnu.trove.THashSet @@ -31,20 +32,28 @@ internal class SchemeLoader(private val schemeManag private val filesToDelete = THashSet() private val schemes = oldSchemes.toMutableList() - private val newSchemesOffset = schemes.size + private var newSchemesOffset = schemes.size + + // scheme could be changed - so, hashcode will be changed - we must use identity hashing strategy + private val schemeToInfo = ContainerUtil.newIdentityTroveMap() private val isApplied = AtomicBoolean() private var digest: MessageDigest? = null + // or from current session, or from current state + private fun getInfoForExistingScheme(existingScheme: T): ExternalInfo? { + return schemeToInfo.get(existingScheme) ?: schemeManager.schemeToInfo.get(existingScheme) + } + private fun isFromFileWithOldExtension(existingScheme: T): Boolean { - val info = schemeManager.schemeToInfo.get(existingScheme) + val info = getInfoForExistingScheme(existingScheme) // scheme from file with old extension, so, we must ignore it return info != null && schemeManager.schemeExtension != info.fileExtension } private fun isFromFileWithNewExtension(existingScheme: T, fileNameWithoutExtension: String): Boolean { - return schemeManager.schemeToInfo.get(existingScheme)?.fileNameWithoutExtension == fileNameWithoutExtension + return getInfoForExistingScheme(existingScheme)?.fileNameWithoutExtension == fileNameWithoutExtension } /** @@ -52,10 +61,11 @@ internal class SchemeLoader(private val schemeManag */ fun apply(): List { LOG.assertTrue(isApplied.compareAndSet(false, true)) - schemeManager.filesToDelete.addAll(filesToDelete) schemeManager.filesToDelete.addAll(preScheduledFilesToDelete) + schemeManager.schemeToInfo.putAll(schemeToInfo) + val result = schemes.subList(newSchemesOffset, schemes.size) schemeManager.schemeListManager.replaceSchemeList(oldSchemes, schemes) if (!isDuringLoad) { @@ -79,6 +89,44 @@ internal class SchemeLoader(private val schemeManag return result } + private fun checkExisting(schemeKey: String, fileName: String, fileNameWithoutExtension: String, extension: String): Boolean { + val processor = schemeManager.processor + // schemes load session doesn't care about any scheme that added after session creation, + // e.g. for now, on apply, simply current manager list replaced atomically to the new one + // if later it will lead to some issues, this check should be done as merge operation (again, currently on apply old list is replaced and not merged) + val existingSchemeIndex = schemes.indexOfFirst { processor.getSchemeKey(it) == schemeKey } + val existingScheme = (if (existingSchemeIndex == -1) null else schemes.get(existingSchemeIndex)) ?: return true + if (schemeManager.schemeListManager.readOnlyExternalizableSchemes.get(processor.getSchemeKey(existingScheme)) === existingScheme) { + // so, bundled scheme is shadowed + schemes.removeAt(existingSchemeIndex) + if (existingSchemeIndex < newSchemesOffset) { + newSchemesOffset-- + } + // not added to filesToDelete because it is only shadowed + return true + } + else if (processor.isExternalizable(existingScheme) && isFromFileWithOldExtension(existingScheme)) { + schemes.removeAt(existingSchemeIndex) + if (existingSchemeIndex < newSchemesOffset) { + newSchemesOffset-- + } + filesToDelete.add(fileName) + } + else { + if (schemeManager.schemeExtension != extension && isFromFileWithNewExtension(existingScheme, fileNameWithoutExtension)) { + // 1.oldExt is loading after 1.newExt - we should delete 1.oldExt + filesToDelete.add(fileName) + } + else { + // We don't load scheme with duplicated name - if we generate unique name for it, it will be saved then with new name. + // It is not what all can expect. Such situation in most cases indicates error on previous level, so, we just warn about it. + LOG.warn("Scheme file \"$fileName\" is not loaded because defines duplicated name \"$schemeKey\"") + } + return false + } + return true + } + fun loadScheme(fileName: String, input: InputStream): MUTABLE_SCHEME? { val extension = schemeManager.getFileExtension(fileName, isAllowAny = false) if (isFileScheduledForDeleteInThisLoadSession(fileName)) { @@ -87,37 +135,8 @@ internal class SchemeLoader(private val schemeManag } val processor = schemeManager.processor - val schemeListManager = schemeManager.schemeListManager - val fileNameWithoutExtension = fileName.substring(0, fileName.length - extension.length) - fun checkExisting(schemeName: String): Boolean { - schemes.firstOrNull { processor.getSchemeKey(it) == schemeName}?.let { existingScheme -> - if (schemeListManager.readOnlyExternalizableSchemes.get(processor.getSchemeKey(existingScheme)) === existingScheme) { - // so, bundled scheme is shadowed - schemeListManager.removeFirstScheme(schemes, scheduleDelete = false) { it === existingScheme } - return true - } - else if (processor.isExternalizable(existingScheme) && isFromFileWithOldExtension(existingScheme)) { - schemeListManager.removeFirstScheme(schemes) { it === existingScheme } - } - else { - if (schemeManager.schemeExtension != extension && isFromFileWithNewExtension(existingScheme, fileNameWithoutExtension)) { - // 1.oldExt is loading after 1.newExt - we should delete 1.oldExt - filesToDelete.add(fileName) - } - else { - // We don't load scheme with duplicated name - if we generate unique name for it, it will be saved then with new name. - // It is not what all can expect. Such situation in most cases indicates error on previous level, so, we just warn about it. - LOG.warn("Scheme file \"$fileName\" is not loaded because defines duplicated name \"$schemeName\"") - } - return false - } - } - - return true - } - fun createInfo(schemeName: String, element: Element?): ExternalInfo { val info = ExternalInfo(fileNameWithoutExtension, extension) if (element != null) { @@ -141,16 +160,16 @@ internal class SchemeLoader(private val schemeManag null } } - val schemeName = name - ?: processor.getSchemeKey(attributeProvider, fileNameWithoutExtension) - ?: throw nameIsMissed(bytes) - if (!checkExisting(schemeName)) { + val schemeKey = name + ?: processor.getSchemeKey(attributeProvider, fileNameWithoutExtension) + ?: throw nameIsMissed(bytes) + if (!checkExisting(schemeKey, fileName, fileNameWithoutExtension, extension)) { return null } - val externalInfo = createInfo(schemeName, null) - scheme = processor.createScheme(SchemeDataHolderImpl(processor, bytes, externalInfo), schemeName, attributeProvider) - schemeManager.schemeToInfo.put(scheme, externalInfo) + val externalInfo = createInfo(schemeKey, null) + scheme = processor.createScheme(SchemeDataHolderImpl(processor, bytes, externalInfo), schemeKey, attributeProvider) + schemeToInfo.put(scheme, externalInfo) retainProbablyScheduledForDeleteFile(fileName) } } @@ -158,11 +177,11 @@ internal class SchemeLoader(private val schemeManag val element = JDOMUtil.load(input.bufferedReader()) scheme = (processor as NonLazySchemeProcessor).readScheme(element, isDuringLoad) ?: return null val schemeKey = processor.getSchemeKey(scheme!!) - if (!checkExisting(schemeKey)) { + if (!checkExisting(schemeKey, fileName, fileNameWithoutExtension, extension)) { return null } - schemeManager.schemeToInfo.put(scheme, createInfo(schemeKey, element)) + schemeToInfo.put(scheme, createInfo(schemeKey, element)) retainProbablyScheduledForDeleteFile(fileName) }