Refactor: abstract over decompiled text indexing

Move navigation-related code from KtDecompiledFile
This commit is contained in:
Pavel V. Talanov
2016-03-18 20:31:06 +03:00
parent ef25b14eab
commit 4824d4b880
5 changed files with 122 additions and 75 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,25 +16,12 @@
package org.jetbrains.kotlin.idea.decompiler
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.decompiler.textBuilder.DecompiledText
import org.jetbrains.kotlin.idea.decompiler.textBuilder.descriptorToKey
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.kotlin.BuiltInClassesAreSerializableOnJvm
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.idea.decompiler.textBuilder.DecompiledTextIndexer
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass
import org.jetbrains.kotlin.utils.concurrent.block.LockedClearableLazyValue
open class KtDecompiledFile(
@@ -46,38 +33,6 @@ open class KtDecompiledFile(
buildDecompiledText(provider.virtualFile)
}
fun getDeclarationForDescriptor(descriptor: DeclarationDescriptor): KtDeclaration? {
val original = descriptor.original
if (original is ValueParameterDescriptor) {
val callable = original.containingDeclaration
val callableDeclaration = getDeclarationForDescriptor(callable) as? KtCallableDeclaration ?: return null
return callableDeclaration.valueParameters[original.index]
}
if (original is ConstructorDescriptor && original.isPrimary) {
val classOrObject = getDeclarationForDescriptor(original.containingDeclaration) as? KtClassOrObject
return classOrObject?.getPrimaryConstructor() ?: classOrObject
}
return original.findElementForDescriptor() ?: run {
if (descriptor !is ClassDescriptor) return null
val classFqName = descriptor.fqNameSafe
if (BuiltInClassesAreSerializableOnJvm.isSerializableInJava(classFqName)) {
val builtInDescriptor = TargetPlatform.Default.builtIns.builtInsModule.resolveTopLevelClass(classFqName, NoLookupLocation.FROM_IDE)
return builtInDescriptor?.findElementForDescriptor()
}
return null
}
}
private fun DeclarationDescriptor.findElementForDescriptor(): KtDeclaration? {
return decompiledText.get().renderedDescriptorsToRange[descriptorToKey(this)]?.let { range ->
PsiTreeUtil.findElementOfClassAtRange(this@KtDecompiledFile, range.startOffset, range.endOffset, KtDeclaration::class.java)
}
}
override fun getText(): String? {
return decompiledText.get().text
}
@@ -89,9 +44,8 @@ open class KtDecompiledFile(
decompiledText.drop()
}
@TestOnly
fun getRenderedDescriptorsToRange(): Map<String, TextRange> {
return decompiledText.get().renderedDescriptorsToRange
fun <T : Any> getDeclaration(indexer: DecompiledTextIndexer<T>, key: T): KtDeclaration? {
val range = decompiledText.get().index.getRange(indexer, key) ?: return null
return PsiTreeUtil.findElementOfClassAtRange(this@KtDecompiledFile, range.startOffset, range.endOffset, KtDeclaration::class.java)
}
}
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.idea.decompiler.common
import org.jetbrains.kotlin.idea.decompiler.textBuilder.DecompiledText
import org.jetbrains.kotlin.idea.decompiler.textBuilder.DecompiledTextIndex
import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion
private val FILE_ABI_VERSION_MARKER: String = "FILE_ABI"
@@ -34,6 +35,6 @@ fun <V : BinaryVersion> createIncompatibleAbiVersionDecompiledText(expectedVersi
INCOMPATIBLE_ABI_VERSION_COMMENT
.replace(CURRENT_ABI_VERSION_MARKER, expectedVersion.toString())
.replace(FILE_ABI_VERSION_MARKER, actualVersion.toString()),
mapOf()
DecompiledTextIndex.Empty
)
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,13 +20,22 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.decompiler.KtDecompiledFile
import org.jetbrains.kotlin.idea.decompiler.textBuilder.DecompiledTextIndexer
import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelFunctionFqnNameIndex
import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelPropertyFqnNameIndex
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.kotlin.BuiltInClassesAreSerializableOnJvm
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass
import org.jetbrains.kotlin.types.ErrorUtils
import java.util.*
@@ -40,8 +49,8 @@ fun findDecompiledDeclaration(
val decompiledFiles = findDecompiledFilesForDescriptor(project, referencedDescriptor)
return decompiledFiles.asSequence().mapNotNull {
it.getDeclarationForDescriptor(referencedDescriptor)
return decompiledFiles.asSequence().mapNotNull { file ->
ByDescriptorIndexer.getDeclarationForDescriptor(referencedDescriptor, file)
}.firstOrNull()
}
@@ -92,3 +101,45 @@ private fun findCandidateDeclarationsInIndex(
}
}
object ByDescriptorIndexer : DecompiledTextIndexer<String> {
override fun indexDescriptor(descriptor: DeclarationDescriptor): Collection<String> {
return listOf(descriptor.toStringKey())
}
internal fun getDeclarationForDescriptor(descriptor: DeclarationDescriptor, file: KtDecompiledFile): KtDeclaration? {
val original = descriptor.original
if (original is ValueParameterDescriptor) {
val callable = original.containingDeclaration
val callableDeclaration = getDeclarationForDescriptor(callable, file) as? KtCallableDeclaration ?: return null
return callableDeclaration.valueParameters[original.index]
}
if (original is ConstructorDescriptor && original.isPrimary) {
val classOrObject = getDeclarationForDescriptor(original.containingDeclaration, file) as? KtClassOrObject
return classOrObject?.getPrimaryConstructor() ?: classOrObject
}
return file.getDeclaration(this, original.toStringKey()) ?: run {
if (descriptor !is ClassDescriptor) return null
val classFqName = descriptor.fqNameSafe
if (BuiltInClassesAreSerializableOnJvm.isSerializableInJava(classFqName)) {
val builtInDescriptor = TargetPlatform.Default.builtIns.builtInsModule.resolveTopLevelClass(classFqName, NoLookupLocation.FROM_IDE)
return builtInDescriptor?.let { file.getDeclaration(this, it.toStringKey()) }
}
return null
}
}
private fun DeclarationDescriptor.toStringKey(): String {
return descriptorRendererForKeys.render(this)
}
private val descriptorRendererForKeys = DescriptorRenderer.COMPACT_WITH_MODIFIERS.withOptions {
modifiers = DescriptorRendererModifier.ALL
withDefinedIn = true
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.decompiler.textBuilder
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.utils.keysToMap
data class DecompiledText(val text: String, val index: DecompiledTextIndex)
interface DecompiledTextIndexer<T: Any> {
fun indexDescriptor(descriptor: DeclarationDescriptor): Collection<T>
}
// In-memory HashMap-based index of decompiled text
// allows navigation features
class DecompiledTextIndex(private val indexers: Collection<DecompiledTextIndexer<*>>) {
private val indexerToMap: Map<DecompiledTextIndexer<*>, MutableMap<Any, TextRange>> = indexers.keysToMap { hashMapOf<Any, TextRange>() }
fun addToIndex(descriptor: DeclarationDescriptor, textRange: TextRange) {
indexers.forEach { mapper ->
val correspondingMap = indexerToMap[mapper]!!
mapper.indexDescriptor(descriptor).forEach { key ->
correspondingMap[key] = textRange
}
}
}
fun <T: Any> getRange(mapper: DecompiledTextIndexer<T>, key: T): TextRange? {
return indexerToMap[mapper]?.get(key)
}
companion object {
val Empty = DecompiledTextIndex(listOf())
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.decompiler.textBuilder
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.decompiler.navigation.ByDescriptorIndexer
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier
@@ -27,23 +28,11 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry
import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike
import org.jetbrains.kotlin.resolve.descriptorUtil.secondaryConstructors
import org.jetbrains.kotlin.types.isFlexible
import java.util.*
private val DECOMPILED_CODE_COMMENT = "/* compiled code */"
private val DECOMPILED_COMMENT_FOR_PARAMETER = "/* = compiled code */"
private val FLEXIBLE_TYPE_COMMENT = "/* platform type */"
private val descriptorRendererForKeys = DescriptorRenderer.COMPACT_WITH_MODIFIERS.withOptions {
modifiers = DescriptorRendererModifier.ALL
withDefinedIn = true
}
fun descriptorToKey(descriptor: DeclarationDescriptor): String {
return descriptorRendererForKeys.render(descriptor)
}
data class DecompiledText(val text: String, val renderedDescriptorsToRange: Map<String, TextRange>)
fun DescriptorRendererOptions.defaultDecompilerRendererOptions() {
withDefinedIn = false
classWithPrimaryConstructor = true
@@ -56,10 +45,10 @@ fun DescriptorRendererOptions.defaultDecompilerRendererOptions() {
fun buildDecompiledText(
packageFqName: FqName,
descriptors: List<DeclarationDescriptor>,
descriptorRenderer: DescriptorRenderer
descriptorRenderer: DescriptorRenderer,
indexers: Collection<DecompiledTextIndexer<*>> = listOf(ByDescriptorIndexer)
): DecompiledText {
val builder = StringBuilder()
val renderedDescriptorsToRange = HashMap<String, TextRange>()
fun appendDecompiledTextAndPackageName() {
builder.append("// IntelliJ API Decompiler stub source generated from a class file\n" + "// Implementation of methods is not available")
@@ -69,8 +58,10 @@ fun buildDecompiledText(
}
}
fun saveDescriptorToRange(descriptor: DeclarationDescriptor, startOffset: Int, endOffset: Int) {
renderedDescriptorsToRange[descriptorToKey(descriptor)] = TextRange(startOffset, endOffset)
val textIndex = DecompiledTextIndex(indexers)
fun indexDescriptor(descriptor: DeclarationDescriptor, startOffset: Int, endOffset: Int) {
textIndex.addToIndex(descriptor, TextRange(startOffset, endOffset))
}
fun appendDescriptor(descriptor: DeclarationDescriptor, indent: String, lastEnumEntry: Boolean? = null) {
@@ -161,12 +152,12 @@ fun buildDecompiledText(
}
builder.append("\n")
saveDescriptorToRange(descriptor, startOffset, endOffset)
indexDescriptor(descriptor, startOffset, endOffset)
if (descriptor is ClassDescriptor) {
val primaryConstructor = descriptor.unsubstitutedPrimaryConstructor
if (primaryConstructor != null) {
saveDescriptorToRange(primaryConstructor, startOffset, endOffset)
indexDescriptor(primaryConstructor, startOffset, endOffset)
}
}
}
@@ -177,5 +168,5 @@ fun buildDecompiledText(
builder.append("\n")
}
return DecompiledText(builder.toString(), renderedDescriptorsToRange)
return DecompiledText(builder.toString(), textIndex)
}