From 31a99d200e779050d41a4708b177f83a15211ae8 Mon Sep 17 00:00:00 2001 From: Mikhail Mazurkevich Date: Fri, 24 Apr 2020 13:14:33 +0300 Subject: [PATCH] [workspace-model] Move VirtualFileUrlManager to the core module GitOrigin-RevId: c269b8cca27da288af1de53ee6bf5de3f64fb759 --- ...lij.platform.workspaceModel.core.tests.iml | 1 - .../SerializationInProxyBasedStorageTest.kt | 0 ...SimplePropertiesInProxyBasedStorageTest.kt | 5 +- .../api}/VirtualFileNameStoreTest.kt | 3 +- .../api}/VirtualFileUrlManagerTest.kt | 7 +- .../api/pstorage/ReferencesInPStorageTest.kt | 3 +- .../SimplePropertiesInPStorageTest.kt | 5 +- .../api/pstorage/VirtualFileIndexTest.kt | 3 +- .../src}/VirtualFileNameStore.kt | 6 +- .../workspaceModel-core/src/VirtualFileUrl.kt | 8 - .../src/VirtualFileUrlManager.kt | 242 ++++++++++++++++++ .../ide/VirtualFileUrlManagerImpl.kt | 237 ----------------- 12 files changed, 255 insertions(+), 265 deletions(-) create mode 100644 platform/workspaceModel-core-tests/test/com/intellij/workspace/api/SerializationInProxyBasedStorageTest.kt rename platform/{workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps => workspaceModel-core-tests/test/com/intellij/workspace/api}/VirtualFileNameStoreTest.kt (92%) rename platform/{workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps => workspaceModel-core-tests/test/com/intellij/workspace/api}/VirtualFileUrlManagerTest.kt (97%) rename platform/{workspaceModel-ide/src/com/intellij/workspace/ide => workspaceModel-core/src}/VirtualFileNameStore.kt (93%) create mode 100644 platform/workspaceModel-core/src/VirtualFileUrlManager.kt diff --git a/platform/workspaceModel-core-tests/intellij.platform.workspaceModel.core.tests.iml b/platform/workspaceModel-core-tests/intellij.platform.workspaceModel.core.tests.iml index eb2d7a422a7a..9ba4d5938980 100644 --- a/platform/workspaceModel-core-tests/intellij.platform.workspaceModel.core.tests.iml +++ b/platform/workspaceModel-core-tests/intellij.platform.workspaceModel.core.tests.iml @@ -8,7 +8,6 @@ - diff --git a/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/SerializationInProxyBasedStorageTest.kt b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/SerializationInProxyBasedStorageTest.kt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/SimplePropertiesInProxyBasedStorageTest.kt b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/SimplePropertiesInProxyBasedStorageTest.kt index 7a9ede933970..ea1bd7181f32 100644 --- a/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/SimplePropertiesInProxyBasedStorageTest.kt +++ b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/SimplePropertiesInProxyBasedStorageTest.kt @@ -1,6 +1,5 @@ package com.intellij.workspace.api -import com.intellij.workspace.ide.VirtualFileUrlManagerImpl import org.junit.Assert.* import org.junit.Before import org.junit.Test @@ -27,7 +26,7 @@ internal fun TypedEntityStorageBuilder.addSampleEntity(stringProperty: String, source: EntitySource = SampleEntitySource("test"), booleanProperty: Boolean = false, stringListProperty: MutableList = ArrayList(), - virtualFileManager: VirtualFileUrlManager = VirtualFileUrlManagerImpl(), + virtualFileManager: VirtualFileUrlManager = VirtualFileUrlManager(), fileProperty: VirtualFileUrl = virtualFileManager.fromUrl("file:///tmp")): SampleEntity { return addEntity(source) { this.booleanProperty = booleanProperty @@ -45,7 +44,7 @@ class SimplePropertiesInProxyBasedStorageTest { private lateinit var virtualFileManager: VirtualFileUrlManager @Before fun setUp() { - virtualFileManager = VirtualFileUrlManagerImpl() + virtualFileManager = VirtualFileUrlManager() } @Test diff --git a/platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/VirtualFileNameStoreTest.kt b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/VirtualFileNameStoreTest.kt similarity index 92% rename from platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/VirtualFileNameStoreTest.kt rename to platform/workspaceModel-core-tests/test/com/intellij/workspace/api/VirtualFileNameStoreTest.kt index 9b38a14262d6..f845307e8548 100644 --- a/platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/VirtualFileNameStoreTest.kt +++ b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/VirtualFileNameStoreTest.kt @@ -1,7 +1,6 @@ // 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.workspace.jps +package com.intellij.workspace.api -import com.intellij.workspace.ide.VirtualFileNameStore import org.junit.Assert import org.junit.Test diff --git a/platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/VirtualFileUrlManagerTest.kt b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/VirtualFileUrlManagerTest.kt similarity index 97% rename from platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/VirtualFileUrlManagerTest.kt rename to platform/workspaceModel-core-tests/test/com/intellij/workspace/api/VirtualFileUrlManagerTest.kt index 02eac107bb0d..12c24b152518 100644 --- a/platform/workspaceModel-ide-tests/testSrc/com/intellij/workspace/jps/VirtualFileUrlManagerTest.kt +++ b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/VirtualFileUrlManagerTest.kt @@ -1,17 +1,16 @@ // 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.workspace.jps +package com.intellij.workspace.api import com.intellij.openapi.vfs.VfsUtil -import com.intellij.workspace.ide.VirtualFileUrlManagerImpl import org.junit.Assert import org.junit.Before import org.junit.Test class VirtualFileUrlManagerTest { - private lateinit var virtualFileManager: VirtualFileUrlManagerImpl + private lateinit var virtualFileManager: VirtualFileUrlManager @Before fun setUp() { - virtualFileManager = VirtualFileUrlManagerImpl() + virtualFileManager = VirtualFileUrlManager() } @Test diff --git a/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/pstorage/ReferencesInPStorageTest.kt b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/pstorage/ReferencesInPStorageTest.kt index 4e73f156d1eb..201bb09963dc 100644 --- a/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/pstorage/ReferencesInPStorageTest.kt +++ b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/pstorage/ReferencesInPStorageTest.kt @@ -5,7 +5,6 @@ import com.intellij.workspace.api.* import com.intellij.workspace.api.pstorage.references.ManyToOne import com.intellij.workspace.api.pstorage.references.MutableManyToOne import com.intellij.workspace.api.pstorage.references.OneToMany -import com.intellij.workspace.ide.VirtualFileUrlManagerImpl import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Before @@ -154,7 +153,7 @@ class ReferencesInPStorageTest { private lateinit var virtualFileManager: VirtualFileUrlManager @Before fun setUp() { - virtualFileManager = VirtualFileUrlManagerImpl() + virtualFileManager = VirtualFileUrlManager() } @Test diff --git a/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/pstorage/SimplePropertiesInPStorageTest.kt b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/pstorage/SimplePropertiesInPStorageTest.kt index 67e0d9b4591d..8762acf21338 100644 --- a/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/pstorage/SimplePropertiesInPStorageTest.kt +++ b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/pstorage/SimplePropertiesInPStorageTest.kt @@ -3,7 +3,6 @@ package com.intellij.workspace.api.pstorage import com.intellij.openapi.util.Ref import com.intellij.workspace.api.* -import com.intellij.workspace.ide.VirtualFileUrlManagerImpl import org.junit.Assert.* import org.junit.Before import org.junit.Test @@ -36,7 +35,7 @@ internal fun TypedEntityStorageBuilder.addPSampleEntity(stringProperty: String, source: EntitySource = PSampleEntitySource("test"), booleanProperty: Boolean = false, stringListProperty: MutableList = ArrayList(), - virtualFileManager: VirtualFileUrlManager = VirtualFileUrlManagerImpl(), + virtualFileManager: VirtualFileUrlManager = VirtualFileUrlManager(), fileProperty: VirtualFileUrl = virtualFileManager.fromUrl( "file:///tmp")): PSampleEntity { return addEntity(ModifiablePSampleEntity::class.java, source) { @@ -55,7 +54,7 @@ class PSimplePropertiesInProxyBasedStorageTest { private lateinit var virtualFileManager: VirtualFileUrlManager @Before fun setUp() { - virtualFileManager = VirtualFileUrlManagerImpl() + virtualFileManager = VirtualFileUrlManager() } @Test diff --git a/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/pstorage/VirtualFileIndexTest.kt b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/pstorage/VirtualFileIndexTest.kt index 4beda2ac89f9..7c95f4351182 100644 --- a/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/pstorage/VirtualFileIndexTest.kt +++ b/platform/workspaceModel-core-tests/test/com/intellij/workspace/api/pstorage/VirtualFileIndexTest.kt @@ -9,7 +9,6 @@ import com.intellij.workspace.api.* import com.intellij.workspace.api.pstorage.indices.VirtualFileUrlListProperty import com.intellij.workspace.api.pstorage.indices.VirtualFileUrlNullableProperty import com.intellij.workspace.api.pstorage.indices.VirtualFileUrlProperty -import com.intellij.workspace.ide.VirtualFileUrlManagerImpl import org.junit.Assert.* import org.junit.Before import org.junit.Test @@ -91,7 +90,7 @@ class VirtualFileIndexTest { private lateinit var virtualFileManager: VirtualFileUrlManager @Before fun setUp() { - virtualFileManager = VirtualFileUrlManagerImpl() + virtualFileManager = VirtualFileUrlManager() } @Test diff --git a/platform/workspaceModel-ide/src/com/intellij/workspace/ide/VirtualFileNameStore.kt b/platform/workspaceModel-core/src/VirtualFileNameStore.kt similarity index 93% rename from platform/workspaceModel-ide/src/com/intellij/workspace/ide/VirtualFileNameStore.kt rename to platform/workspaceModel-core/src/VirtualFileNameStore.kt index 70a0382eafa8..134dd52578ff 100644 --- a/platform/workspaceModel-ide/src/com/intellij/workspace/ide/VirtualFileNameStore.kt +++ b/platform/workspaceModel-core/src/VirtualFileNameStore.kt @@ -1,11 +1,11 @@ // 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.workspace.ide +package com.intellij.workspace.api import gnu.trove.THashMap import org.jetbrains.annotations.TestOnly internal class VirtualFileNameStore { - private val generator = IdGenerator() + private val generator = IntIdGenerator() private val name2IdStore = THashMap() private val id2NameStore = THashMap() @@ -58,7 +58,7 @@ private data class IdPerCount(val id: Int, var usageCount: Long) { override fun hashCode() = 31 * id.hashCode() } -internal class IdGenerator { +internal class IntIdGenerator { private var generator: Int = 0 fun generateId() = ++generator diff --git a/platform/workspaceModel-core/src/VirtualFileUrl.kt b/platform/workspaceModel-core/src/VirtualFileUrl.kt index 58ca049fef2a..d074ffae5386 100644 --- a/platform/workspaceModel-core/src/VirtualFileUrl.kt +++ b/platform/workspaceModel-core/src/VirtualFileUrl.kt @@ -3,14 +3,6 @@ package com.intellij.workspace.api import java.io.File import java.nio.file.Path -abstract class VirtualFileUrlManager { - abstract fun fromUrl(url: String): VirtualFileUrl - abstract fun fromPath(path: String): VirtualFileUrl - abstract fun getParentVirtualUrlById(id: Int): VirtualFileUrl? - abstract fun getUrlById(id: Int): String - abstract fun isEqualOrParentOf(parentNodeId: Int, childNodeId: Int): Boolean -} - // TODO Do we want to make it inline? data class VirtualFileUrl(private val id: Int, internal val manager: VirtualFileUrlManager) { diff --git a/platform/workspaceModel-core/src/VirtualFileUrlManager.kt b/platform/workspaceModel-core/src/VirtualFileUrlManager.kt new file mode 100644 index 000000000000..308b5fb13335 --- /dev/null +++ b/platform/workspaceModel-core/src/VirtualFileUrlManager.kt @@ -0,0 +1,242 @@ +// 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.workspace.api + +import com.intellij.openapi.util.io.FileUtil +import com.intellij.util.SmartList +import com.intellij.util.containers.IntArrayList +import gnu.trove.THashMap +import kotlin.collections.set + +open class VirtualFileUrlManager { + private val idGenerator= IntIdGenerator() + private val EMPTY_URL = VirtualFileUrl(0, this) + private val fileNameStore = VirtualFileNameStore() + private val id2NodeMapping = THashMap() + private var segmentId2RootNodeMapping = THashMap() + + fun fromUrl(url: String): VirtualFileUrl { + if (url.isEmpty()) return EMPTY_URL + return add(url) + } + + fun fromPath(path: String): VirtualFileUrl { + if (path.isEmpty()) return EMPTY_URL + return fromUrl("file://${FileUtil.toSystemIndependentName(path)}") + } + + fun getParentVirtualUrlById(id: Int): VirtualFileUrl? = id2NodeMapping[id]?.parent?.let { VirtualFileUrl(it.nodeId, this) } + + fun getUrlById(id: Int): String { + if (id <= 0) return "" + + var node = id2NodeMapping[id] + val contentIds = IntArrayList() + while (node != null) { + contentIds.add(node.contentId) + node = node.parent + } + + if (contentIds.size() == 1) { + return fileNameStore.getNameForId(contentIds[0])?.let { + return@let if (it.isEmpty()) "/" else it + } ?: "" + } + val builder = StringBuilder() + for (index in contentIds.size() - 1 downTo 0) { + builder.append(fileNameStore.getNameForId(contentIds[index])) + if (index != 0) builder.append("/") + } + return builder.toString() + } + + internal fun add(path: String): VirtualFileUrl { + val segments = splitNames(path) + var latestNode: FilePathNode? = findRootNode(segments.first()) + val latestElement = segments.size - 1 + for (index in segments.indices) { + val nameId = fileNameStore.generateIdForName(segments[index]) + // Latest node can be NULL only if it's root node + if (latestNode == null) { + val nodeId = idGenerator.generateId() + val newNode = FilePathNode(nodeId, nameId) + id2NodeMapping[nodeId] = newNode + // If it's the latest name of folder or files, save entity Id as node value + if (index == latestElement) { + segmentId2RootNodeMapping[nameId] = newNode + return VirtualFileUrl(nodeId, this) + } + latestNode = newNode + segmentId2RootNodeMapping[nameId] = newNode + continue + } + + if (latestNode === findRootNode(latestNode.contentId)) { + if (latestNode.contentId == nameId) { + if (index == latestElement) return VirtualFileUrl(latestNode.nodeId, this) + continue + } + } + + val node = latestNode.findChild(nameId) + if (node == null) { + val nodeId = idGenerator.generateId() + val newNode = FilePathNode(nodeId, nameId, latestNode) + id2NodeMapping[nodeId] = newNode + latestNode.addChild(newNode) + latestNode = newNode + // If it's the latest name of folder or files, save entity Id as node value + if (index == latestElement) return VirtualFileUrl(nodeId, this) + } else { + // If it's the latest name of folder or files, save entity Id as node value + if (index == latestElement) return VirtualFileUrl(node.nodeId, this) + latestNode = node + } + } + return EMPTY_URL + } + + internal fun remove(path: String) { + val node = findLatestFilePathNode(path) + if (node == null) { + println("File not found") + return + } + if (!node.isEmpty()) return + + var currentNode: FilePathNode = node + do { + val parent = currentNode.parent + if (parent == null) { + if (currentNode === findRootNode(currentNode.contentId) && currentNode.isEmpty()) { + removeNameUsage(currentNode.contentId) + id2NodeMapping.remove(currentNode.nodeId) + segmentId2RootNodeMapping.remove(currentNode.contentId) + } + return + } + + parent.removeChild(currentNode) + removeNameUsage(currentNode.contentId) + id2NodeMapping.remove(currentNode.nodeId) + currentNode = parent + } while (currentNode.isEmpty()) + } + + internal fun update(oldPath: String, newPath: String) { + val latestPathNode = findLatestFilePathNode(oldPath) + if (latestPathNode == null) return + remove(oldPath) + add(newPath) + } + + private fun removeNameUsage(contentId: Int) { + val name = fileNameStore.getNameForId(contentId) + assert(name != null) + fileNameStore.removeName(name!!) + } + + private fun findLatestFilePathNode(path: String): FilePathNode? { + val segments = splitNames(path) + var latestNode: FilePathNode? = findRootNode(segments.first()) + val latestElement = segments.size - 1 + for (index in segments.indices) { + val nameId = fileNameStore.getIdForName(segments[index]) ?: return null + // Latest node can be NULL only if it's root node + if (latestNode == null) return null + + if (latestNode === findRootNode(latestNode.contentId)) { + if (latestNode.contentId == nameId) { + if (index == latestElement) return latestNode else continue + } + } + + latestNode.findChild(nameId)?.let { + if (index == latestElement) return it + latestNode = it + } ?: return null + } + return null + } + + private fun findRootNode(segment: String): FilePathNode? { + val segmentId = fileNameStore.getIdForName(segment) ?: return null + return segmentId2RootNodeMapping[segmentId] + } + + private fun findRootNode(contentId: Int): FilePathNode? = segmentId2RootNodeMapping[contentId] + + private fun splitNames(path: String): List = path.split('/', '\\') + + override fun toString() = segmentId2RootNodeMapping.values.joinToString(separator = "\n") { it.toString() } + + fun isEqualOrParentOf(parentNodeId: Int, childNodeId: Int): Boolean { + if (parentNodeId == 0 && childNodeId == 0) return true + + var current = childNodeId + while (current > 0) { + if (parentNodeId == current) return true + current = id2NodeMapping[current]?.parent?.nodeId ?: return false + } + /* + TODO It may look like this + caching + invalidating + val segmentName = getSegmentName(id).toString() + + val parent = id2parent.getValue(id) + val parentParent = id2parent.getValue(parent) + return if (parentParent <= 0) { + val fileSystem = VirtualFileManager.getInstance().getFileSystem(getSegmentName(parent).toString()) + fileSystem?.findFileByPath(segmentName) + } else { + getVirtualFileById(parent)?.findChild(segmentName) + } + } + */ + return false + } + + private inner class FilePathNode(val nodeId: Int, val contentId: Int, val parent: FilePathNode? = null) { + private var children: MutableList? = null + + internal fun findChild(nameId: Int): FilePathNode? { + // If search of child node will be slow, replace SmartList to THashSet + // For now SmartList reduce 500Kb memory on IDEA project + return children?.find { it.contentId == nameId } + } + + internal fun addChild(newNode: FilePathNode) { + createChildrenList() + children!!.add(newNode) + } + + internal fun removeChild(node: FilePathNode) { + children?.remove(node) + } + + internal fun isEmpty() = children == null || children!!.isEmpty() + + private fun createChildrenList() { + if (children == null) children = SmartList() + } + + override fun toString(): String { + val buffer = StringBuilder() + print(buffer, "", "") + return buffer.toString() + } + + private fun print(buffer: StringBuilder, prefix: String, childrenPrefix: String) { + val name = this@VirtualFileUrlManager.fileNameStore.getNameForId(contentId) + buffer.append("$prefix $name\n") + val iterator = children?.iterator() ?: return + while (iterator.hasNext()) { + val next = iterator.next() + if (iterator.hasNext()) { + next.print(buffer, "$childrenPrefix |- ", "$childrenPrefix | ") + } + else { + next.print(buffer, "$childrenPrefix '- ", "$childrenPrefix ") + } + } + } + } +} \ No newline at end of file diff --git a/platform/workspaceModel-ide/src/com/intellij/workspace/ide/VirtualFileUrlManagerImpl.kt b/platform/workspaceModel-ide/src/com/intellij/workspace/ide/VirtualFileUrlManagerImpl.kt index 1bd5d900434d..5b74f350bd1c 100644 --- a/platform/workspaceModel-ide/src/com/intellij/workspace/ide/VirtualFileUrlManagerImpl.kt +++ b/platform/workspaceModel-ide/src/com/intellij/workspace/ide/VirtualFileUrlManagerImpl.kt @@ -3,247 +3,10 @@ package com.intellij.workspace.ide import com.intellij.openapi.components.service import com.intellij.openapi.project.Project -import com.intellij.openapi.util.io.FileUtil -import com.intellij.util.SmartList -import com.intellij.workspace.api.VirtualFileUrl import com.intellij.workspace.api.VirtualFileUrlManager -import gnu.trove.THashMap -import kotlin.collections.set class VirtualFileUrlManagerImpl: VirtualFileUrlManager() { - private val idGenerator= IdGenerator() - private val EMPTY_URL = VirtualFileUrl(0, this) - private val fileNameStore = VirtualFileNameStore() - private val id2NodeMapping = THashMap() - private var segmentId2RootNodeMapping = THashMap() - companion object { fun getInstance(project: Project): VirtualFileUrlManager = project.service() } - - override fun fromUrl(url: String): VirtualFileUrl { - if (url.isEmpty()) return EMPTY_URL - return add(url) - } - - override fun fromPath(path: String): VirtualFileUrl { - if (path.isEmpty()) return EMPTY_URL - return fromUrl("file://${FileUtil.toSystemIndependentName(path)}") - } - - override fun getParentVirtualUrlById(id: Int): VirtualFileUrl? = id2NodeMapping[id]?.parent?.let { VirtualFileUrl(it.nodeId, this) } - - override fun getUrlById(id: Int): String { - if (id <= 0) return "" - - var node = id2NodeMapping[id] - val contentIds = mutableListOf() - while (node != null) { - contentIds.add(node.contentId) - node = node.parent - } - - if (contentIds.size == 1) { - return fileNameStore.getNameForId(contentIds[0])?.let { - return@let if (it.isEmpty()) "/" else it - } ?: "" - } - val builder = StringBuilder() - for (index in contentIds.indices.reversed()) { - builder.append(fileNameStore.getNameForId(contentIds[index])) - if (index != 0) builder.append("/") - } - return builder.toString() - } - - internal fun add(path: String): VirtualFileUrl { - val segments = splitNames(path) - var latestNode: FilePathNode? = findRootNode(segments.first()) - val latestElement = segments.size - 1 - for (index in segments.indices) { - val nameId = fileNameStore.generateIdForName(segments[index]) - // Latest node can be NULL only if it's root node - if (latestNode == null) { - val nodeId = idGenerator.generateId() - val newNode = FilePathNode(nodeId, nameId) - id2NodeMapping[nodeId] = newNode - // If it's the latest name of folder or files, save entity Id as node value - if (index == latestElement) { - segmentId2RootNodeMapping[nameId] = newNode - return VirtualFileUrl(nodeId, this) - } - latestNode = newNode - segmentId2RootNodeMapping[nameId] = newNode - continue - } - - if (latestNode === findRootNode(latestNode.contentId)) { - if (latestNode.contentId == nameId) { - if (index == latestElement) return VirtualFileUrl(latestNode.nodeId, this) - continue - } - } - - val node = latestNode.findChild(nameId) - if (node == null) { - val nodeId = idGenerator.generateId() - val newNode = FilePathNode(nodeId, nameId, latestNode) - id2NodeMapping[nodeId] = newNode - latestNode.addChild(newNode) - latestNode = newNode - // If it's the latest name of folder or files, save entity Id as node value - if (index == latestElement) return VirtualFileUrl(nodeId, this) - } else { - // If it's the latest name of folder or files, save entity Id as node value - if (index == latestElement) return VirtualFileUrl(node.nodeId, this) - latestNode = node - } - } - return EMPTY_URL - } - - internal fun remove(path: String) { - val node = findLatestFilePathNode(path) - if (node == null) { - println("File not found") - return - } - if (!node.isEmpty()) return - - var currentNode: FilePathNode = node - do { - val parent = currentNode.parent - if (parent == null) { - if (currentNode === findRootNode(currentNode.contentId) && currentNode.isEmpty()) { - removeNameUsage(currentNode.contentId) - id2NodeMapping.remove(currentNode.nodeId) - segmentId2RootNodeMapping.remove(currentNode.contentId) - } - return - } - - parent.removeChild(currentNode) - removeNameUsage(currentNode.contentId) - id2NodeMapping.remove(currentNode.nodeId) - currentNode = parent - } while (currentNode.isEmpty()) - } - - internal fun update(oldPath: String, newPath: String) { - val latestPathNode = findLatestFilePathNode(oldPath) - if (latestPathNode == null) return - remove(oldPath) - add(newPath) - } - - private fun removeNameUsage(contentId: Int) { - val name = fileNameStore.getNameForId(contentId) - assert(name != null) - fileNameStore.removeName(name!!) - } - - private fun findLatestFilePathNode(path: String): FilePathNode? { - val segments = splitNames(path) - var latestNode: FilePathNode? = findRootNode(segments.first()) - val latestElement = segments.size - 1 - for (index in segments.indices) { - val nameId = fileNameStore.getIdForName(segments[index]) ?: return null - // Latest node can be NULL only if it's root node - if (latestNode == null) return null - - if (latestNode === findRootNode(latestNode.contentId)) { - if (latestNode.contentId == nameId) { - if (index == latestElement) return latestNode else continue - } - } - - latestNode.findChild(nameId)?.let { - if (index == latestElement) return it - latestNode = it - } ?: return null - } - return null - } - - private fun findRootNode(segment: String): FilePathNode? { - val segmentId = fileNameStore.getIdForName(segment) ?: return null - return segmentId2RootNodeMapping[segmentId] - } - - private fun findRootNode(contentId: Int): FilePathNode? = segmentId2RootNodeMapping[contentId] - - private fun splitNames(path: String): List = path.split('/', '\\') - - override fun toString() = segmentId2RootNodeMapping.values.joinToString(separator = "\n") { it.toString() } - - override fun isEqualOrParentOf(parentNodeId: Int, childNodeId: Int): Boolean { - if (parentNodeId == 0 && childNodeId == 0) return true - - var current = childNodeId - while (current > 0) { - if (parentNodeId == current) return true - current = id2NodeMapping[current]?.parent?.nodeId ?: return false - } - /* - TODO It may look like this + caching + invalidating - val segmentName = getSegmentName(id).toString() - - val parent = id2parent.getValue(id) - val parentParent = id2parent.getValue(parent) - return if (parentParent <= 0) { - val fileSystem = VirtualFileManager.getInstance().getFileSystem(getSegmentName(parent).toString()) - fileSystem?.findFileByPath(segmentName) - } else { - getVirtualFileById(parent)?.findChild(segmentName) - } - } - */ - return false - } - - private inner class FilePathNode(val nodeId: Int, val contentId: Int, val parent: FilePathNode? = null) { - private var children: MutableList? = null - - internal fun findChild(nameId: Int): FilePathNode? { - // If search of child node will be slow, replace SmartList to THashSet - // For now SmartList reduce 500Kb memory on IDEA project - return children?.find { it.contentId == nameId } - } - - internal fun addChild(newNode: FilePathNode) { - createChildrenList() - children!!.add(newNode) - } - - internal fun removeChild(node: FilePathNode) { - children?.remove(node) - } - - internal fun isEmpty() = children == null || children!!.isEmpty() - - private fun createChildrenList() { - if (children == null) children = SmartList() - } - - override fun toString(): String { - val buffer = StringBuilder() - print(buffer, "", "") - return buffer.toString() - } - - private fun print(buffer: StringBuilder, prefix: String, childrenPrefix: String) { - val name = this@VirtualFileUrlManagerImpl.fileNameStore.getNameForId(contentId) - buffer.append("$prefix $name\n") - val iterator = children?.iterator() ?: return - while (iterator.hasNext()) { - val next = iterator.next() - if (iterator.hasNext()) { - next.print(buffer, "$childrenPrefix |- ", "$childrenPrefix | ") - } - else { - next.print(buffer, "$childrenPrefix '- ", "$childrenPrefix ") - } - } - } - } } \ No newline at end of file