Migrate decompiled light classes factories from IJ

This commit is contained in:
Ilya Kirillov
2021-12-21 18:56:46 +03:00
parent 09f5ec7bd3
commit 1bba3b2256
2 changed files with 178 additions and 0 deletions
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.decompiled.light.classes
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.impl.compiled.ClsFileImpl
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl
import com.intellij.util.cls.ClsFormatException
import com.intellij.util.containers.ContainerUtil
import java.io.IOException
internal class ClsJavaStubByVirtualFileCache {
private class CachedJavaStub(val modificationStamp: Long, val javaFileStub: PsiJavaFileStubImpl)
private val cache = ContainerUtil.createConcurrentWeakKeySoftValueMap<VirtualFile, CachedJavaStub>()
fun get(classFile: VirtualFile): PsiJavaFileStubImpl? {
val cached = cache[classFile]
val fileModificationStamp = classFile.modificationStamp
if (cached != null && cached.modificationStamp == fileModificationStamp) {
return cached.javaFileStub
}
val stub = createStub(classFile) as PsiJavaFileStubImpl? ?: return null
cache[classFile] = CachedJavaStub(fileModificationStamp, stub)
return stub
}
private fun createStub(file: VirtualFile): PsiJavaFileStub? {
if (file.fileType !== JavaClassFileType.INSTANCE) return null
try {
return ClsFileImpl.buildFileStub(file, file.contentsToByteArray(false))
} catch (e: ClsFormatException) {
LOG.warn("Failed to build java cls class for " + file.canonicalPath!!, e)
} catch (e: IOException) {
LOG.warn("Failed to build java cls class for " + file.canonicalPath!!, e)
}
return null
}
companion object {
private val LOG = Logger.getInstance(ClsJavaStubByVirtualFileCache::class.java)
fun getInstance(project: Project): ClsJavaStubByVirtualFileCache =
project.getService(ClsJavaStubByVirtualFileCache::class.java)
}
}
@@ -0,0 +1,123 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.decompiled.light.classes
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.ClassFileViewProvider
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.compiled.ClsClassImpl
import com.intellij.psi.impl.compiled.ClsFileImpl
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.analysis.decompiler.psi.file.KtClsFile
import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtEnumEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.utils.checkWithAttachment
object DecompiledLightClassesFactory {
fun getLightClassForDecompiledClassOrObject(
decompiledClassOrObject: KtClassOrObject,
project: Project
): KtLightClassForDecompiledDeclaration? {
if (decompiledClassOrObject is KtEnumEntry) {
return null
}
val containingKtFile = decompiledClassOrObject.containingFile as? KtClsFile ?: return null
val rootLightClassForDecompiledFile = createLightClassForDecompiledKotlinFile(containingKtFile, project) ?: return null
return findCorrespondingLightClass(decompiledClassOrObject, rootLightClassForDecompiledFile)
}
private fun findCorrespondingLightClass(
decompiledClassOrObject: KtClassOrObject,
rootLightClassForDecompiledFile: KtLightClassForDecompiledDeclaration
): KtLightClassForDecompiledDeclaration? {
val relativeFqName = getClassRelativeName(decompiledClassOrObject) ?: return null
val iterator = relativeFqName.pathSegments().iterator()
val base = iterator.next()
// In case class files have been obfuscated (i.e., SomeClass belongs to a.class file), just ignore them
if (rootLightClassForDecompiledFile.name != base.asString()) return null
var current: KtLightClassForDecompiledDeclaration = rootLightClassForDecompiledFile
while (iterator.hasNext()) {
val name = iterator.next()
val innerClass = current.findInnerClassByName(name.asString(), false)
checkWithAttachment(
innerClass != null,
{ "Could not find corresponding inner/nested class " + relativeFqName + " in class " + decompiledClassOrObject.fqName + "\nFile: " + decompiledClassOrObject.containingKtFile.virtualFile.name },
{
it.withAttachment("decompiledClassOrObject", decompiledClassOrObject.text)
it.withAttachment("fileClass", decompiledClassOrObject.containingFile::class)
it.withAttachment("file", decompiledClassOrObject.containingFile.text)
it.withAttachment("root", rootLightClassForDecompiledFile.text)
},
)
current = innerClass as KtLightClassForDecompiledDeclaration
}
return current
}
private fun getClassRelativeName(decompiledClassOrObject: KtClassOrObject): FqName? {
val name = decompiledClassOrObject.nameAsName ?: return null
val parent = PsiTreeUtil.getParentOfType(
decompiledClassOrObject,
KtClassOrObject::class.java,
true
)
if (parent == null) {
assert(decompiledClassOrObject.isTopLevel())
return FqName.topLevel(name)
}
return getClassRelativeName(parent)?.child(name)
}
fun createLightClassForDecompiledKotlinFile(file: KtClsFile, project: Project): KtLightClassForDecompiledDeclaration? {
val virtualFile = file.virtualFile ?: return null
val classOrObject = file.declarations.filterIsInstance<KtClassOrObject>().singleOrNull()
val javaClsClass = createClsJavaClassFromVirtualFile(
file, virtualFile,
correspondingClassOrObject = classOrObject,
project,
) ?: return null
return KtLightClassForDecompiledDeclaration(javaClsClass, javaClsClass.parent, file, classOrObject)
}
fun createClsJavaClassFromVirtualFile(
mirrorFile: KtFile,
classFile: VirtualFile,
correspondingClassOrObject: KtClassOrObject?,
project: Project,
): ClsClassImpl? {
val javaFileStub = ClsJavaStubByVirtualFileCache.getInstance(project).get(classFile) ?: return null
javaFileStub.psiFactory = ClsWrapperStubPsiFactory.INSTANCE
val manager = PsiManager.getInstance(mirrorFile.project)
val fakeFile = object : ClsFileImpl(ClassFileViewProvider(manager, classFile)) {
override fun getNavigationElement(): PsiElement {
if (correspondingClassOrObject != null) {
return correspondingClassOrObject.navigationElement.containingFile
}
return super.getNavigationElement()
}
override fun getStub() = javaFileStub
override fun getMirror() = mirrorFile
override fun isPhysical() = false
}
javaFileStub.psi = fakeFile
return fakeFile.classes.single() as ClsClassImpl
}
}