mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[workspace-model] Move VirtualFileUrlManager to the core module
GitOrigin-RevId: c269b8cca27da288af1de53ee6bf5de3f64fb759
This commit is contained in:
committed by
intellij-monorepo-bot
parent
25af0077d0
commit
31a99d200e
@@ -8,7 +8,6 @@
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="intellij.platform.workspaceModel.core" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.workspaceModel.ide" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
|
||||
<orderEntry type="library" scope="TEST" name="Guava" level="project" />
|
||||
</component>
|
||||
|
||||
+2
-3
@@ -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<String> = ArrayList(),
|
||||
virtualFileManager: VirtualFileUrlManager = VirtualFileUrlManagerImpl(),
|
||||
virtualFileManager: VirtualFileUrlManager = VirtualFileUrlManager(),
|
||||
fileProperty: VirtualFileUrl = virtualFileManager.fromUrl("file:///tmp")): SampleEntity {
|
||||
return addEntity<ModifiableSampleEntity, SampleEntity>(source) {
|
||||
this.booleanProperty = booleanProperty
|
||||
@@ -45,7 +44,7 @@ class SimplePropertiesInProxyBasedStorageTest {
|
||||
private lateinit var virtualFileManager: VirtualFileUrlManager
|
||||
@Before
|
||||
fun setUp() {
|
||||
virtualFileManager = VirtualFileUrlManagerImpl()
|
||||
virtualFileManager = VirtualFileUrlManager()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
+3
-4
@@ -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
|
||||
+1
-2
@@ -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
|
||||
|
||||
+2
-3
@@ -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<String> = 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
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
+3
-3
@@ -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<String, IdPerCount>()
|
||||
private val id2NameStore = THashMap<Int, String>()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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<Int, FilePathNode>()
|
||||
private var segmentId2RootNodeMapping = THashMap<Int, FilePathNode>()
|
||||
|
||||
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<String> = 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<FilePathNode>? = 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 ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-237
@@ -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<Int, FilePathNode>()
|
||||
private var segmentId2RootNodeMapping = THashMap<Int, FilePathNode>()
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project): VirtualFileUrlManager = project.service<VirtualFileUrlManagerImpl>()
|
||||
}
|
||||
|
||||
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<Int>()
|
||||
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<String> = 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<FilePathNode>? = 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 ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user