aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/com/replaymod/gradle/remap/ShortNameIndex.kt
blob: c78256ab5683d473ea9f32f661c2dceb265c43d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.replaymod.gradle.remap

import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.javaSourceRoots
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.com.intellij.lang.jvm.JvmModifier
import org.jetbrains.kotlin.com.intellij.openapi.vfs.StandardFileSystems
import org.jetbrains.kotlin.com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.com.intellij.openapi.vfs.VirtualFileManager
import org.jetbrains.kotlin.com.intellij.psi.PsiClass
import org.jetbrains.kotlin.com.intellij.psi.PsiJavaFile
import org.jetbrains.kotlin.com.intellij.psi.PsiManager
import org.jetbrains.kotlin.config.JVMConfigurationKeys

class ShortNameIndex(private val environment: KotlinCoreEnvironment) {
    private val psiManager = PsiManager.getInstance(environment.project)

    private val entries: Map<String, ShortNameEntry> = mutableMapOf<String, ShortNameEntry>().apply {
        val localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL)
        val jarFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JAR_PROTOCOL)
        val jrtFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL)

        val classpathRoots = environment.configuration.jvmClasspathRoots.mapNotNull { file ->
            if (file.isFile) {
                jarFileSystem.findFileByPath("${file.absolutePath}!/")
            } else {
                localFileSystem.findFileByPath(file.absolutePath)
            }
        }

        val jdkHome = environment.configuration[JVMConfigurationKeys.JDK_HOME]
        val allModuleRoots = jrtFileSystem.findFileByPath("$jdkHome!/modules")?.children ?: emptyArray()
        val javaModuleRoots = allModuleRoots.filter { it.name.startsWith("java.") }

        val sourcesRoots = environment.configuration.javaSourceRoots.mapNotNull { localFileSystem.findFileByPath(it) }

        fun index(file: VirtualFile, pkgPrefix: String) {
            if (file.isDirectory) {
                val pkg = "$pkgPrefix${file.name}."
                file.children.forEach { index(it, pkg) }
            } else if (file.extension == "class") {
                val fileName = file.nameWithoutExtension
                val shortName = if ('$' in fileName) {
                    val innerName = fileName.substringAfterLast('$')
                    if (!innerName.first().isJavaIdentifierStart()) {
                        return
                    }
                    innerName
                } else {
                    fileName
                }
                getOrPut(shortName, ::ShortNameEntry).files.add(file)
            } else if (file.extension == "java") {
                val psi = psiManager.findFile(file) as? PsiJavaFile ?: return
                psi.classes.flatMap { listOf(it) + it.allInnerClasses }.forEach { psiClass ->
                    getOrPut(psiClass.name ?: return@forEach, ::ShortNameEntry).files.add(file)
                }
            }
        }

        (classpathRoots + javaModuleRoots + sourcesRoots).forEach { root ->
            root.children.forEach { index(it, "") }
        }
    }

    operator fun get(shortName: String): Set<PsiClass> {
        val entry = entries[shortName] ?: return emptySet()
        return entry.resolve(psiManager, shortName)
    }

    private class ShortNameEntry {
        var files = mutableListOf<VirtualFile>()
        private var classes: Set<PsiClass>? = null

        fun resolve(psiManager: PsiManager, shortName: String): Set<PsiClass> {
            return classes ?: resolveClasses(psiManager, shortName)
        }

        private fun resolveClasses(psiManager: PsiManager, shortName: String): Set<PsiClass> {
            val result = files.flatMap { file ->
                if (file.extension == "java" && file.nameWithoutExtension != shortName) {
                    val psi = psiManager.findFile(file) as? PsiJavaFile ?: return@flatMap emptyList()
                    psi.classes.flatMap { sequenceOf(it) + it.allInnerClasses.asIterable() }
                        .filter { it.qualifiedName?.endsWith(shortName) == true }
                } else if ('$' in file.name) {
                    val className = file.nameWithoutExtension.replace('$', '.')
                    val outerName = className.substringBefore(".")
                    val outerFile = file.parent.findChild("$outerName.class") ?: return@flatMap emptyList()
                    val outerPsi = psiManager.findFile(outerFile) as? PsiJavaFile ?: return@flatMap emptyList()
                    outerPsi.classes.flatMap { it.allInnerClasses.asIterable() }
                        .filter { it.qualifiedName?.endsWith(className) == true }
                } else {
                    (psiManager.findFile(file) as? PsiJavaFile)?.classes?.asIterable() ?: emptyList()
                }
            }.filter { it.hasModifier(JvmModifier.PUBLIC) }.toSet()
            classes = result
            return result
        }
    }
}