[workspace-model] Move VirtualFileUrlManager to the core module

GitOrigin-RevId: c269b8cca27da288af1de53ee6bf5de3f64fb759
This commit is contained in:
Mikhail Mazurkevich
2020-04-24 13:02:19 +00:00
committed by intellij-monorepo-bot
parent 25af0077d0
commit 31a99d200e
12 changed files with 255 additions and 265 deletions
@@ -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>
@@ -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,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 ")
}
}
}
}
}
@@ -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 ")
}
}
}
}
}