Implement "Decompile Kotlin to Java" action
Add this action to "Attach sources" notification panel if present #KT-11657 Fixed
This commit is contained in:
@@ -213,6 +213,7 @@
|
||||
###### New features
|
||||
|
||||
- Added "Decompile" button to Kotlin bytecode toolwindow
|
||||
- [`KT-11657`](https://youtrack.jetbrains.com/issue/KT-11657) Allow viewing decompiled Java source code for Kotlin-compiled classes
|
||||
- [`KT-11704`](https://youtrack.jetbrains.com/issue/KT-11704) Support file path references inside of Kotlin string literals
|
||||
|
||||
###### Issues fixed:
|
||||
|
||||
@@ -99,6 +99,11 @@
|
||||
<add-to-group group-id="KotlinToolsGroup"/>
|
||||
</action>
|
||||
|
||||
<action id="DecompileKotlinToJava" class="org.jetbrains.kotlin.idea.actions.DecompileKotlinToJavaAction"
|
||||
text="Decompile Kotlin To Java">
|
||||
<add-to-group group-id="KotlinToolsGroup"/>
|
||||
</action>
|
||||
|
||||
<action id="ExtractFunction" class="org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractFunctionAction"
|
||||
text="_Function..." use-shortcut-of="ExtractMethod">
|
||||
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="ExtractMethod"/>
|
||||
@@ -600,6 +605,8 @@
|
||||
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.versions.UnsupportedAbiVersionNotificationPanelProvider"/>
|
||||
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.configuration.KotlinSetupEnvironmentNotificationProvider"/>
|
||||
|
||||
<attachSourcesProvider implementation="org.jetbrains.kotlin.idea.actions.DecompileKotlinToJavaActionProvider"/>
|
||||
|
||||
<referencesSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearcher"/>
|
||||
<directClassInheritorsSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinDirectInheritorsSearcher"/>
|
||||
<overridingMethodsSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinOverridingMethodsWithGenericsSearcher"/>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.actions
|
||||
|
||||
import com.intellij.codeInsight.AttachSourcesProvider
|
||||
import com.intellij.ide.highlighter.JavaClassFileType
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.roots.LibraryOrderEntry
|
||||
import com.intellij.openapi.util.ActionCallback
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.idea.internal.showDecompiledCode
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class DecompileKotlinToJavaAction : AnAction() {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val binaryFile = getBinaryKotlinFile(e) ?: return
|
||||
|
||||
showDecompiledCode(binaryFile)
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabled = getBinaryKotlinFile(e) != null
|
||||
}
|
||||
|
||||
private fun getBinaryKotlinFile(e: AnActionEvent): KtFile? {
|
||||
val file = e.getData(CommonDataKeys.PSI_FILE) as? KtFile ?: return null
|
||||
if (!file.canBeDecompiledToJava()) return null
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun KtFile.canBeDecompiledToJava() = isCompiled && virtualFile?.fileType == JavaClassFileType.INSTANCE
|
||||
|
||||
// Add action to "Attach sources" notification panel
|
||||
class DecompileKotlinToJavaActionProvider() : AttachSourcesProvider {
|
||||
override fun getActions(orderEntries: MutableList<LibraryOrderEntry>, psiFile: PsiFile): Collection<AttachSourcesProvider.AttachSourcesAction> {
|
||||
if (psiFile !is KtFile || !psiFile.canBeDecompiledToJava()) return emptyList()
|
||||
|
||||
return listOf(object : AttachSourcesProvider.AttachSourcesAction {
|
||||
override fun getName() = "Decompile to Java"
|
||||
|
||||
override fun perform(orderEntriesContainingFile: List<LibraryOrderEntry>?): ActionCallback {
|
||||
showDecompiledCode(psiFile)
|
||||
return ActionCallback.DONE
|
||||
}
|
||||
|
||||
override fun getBusyText() = "Kotlin Classfile"
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.idea.internal
|
||||
|
||||
import com.intellij.openapi.fileEditor.OpenFileDescriptor
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
@@ -29,6 +30,10 @@ import org.jetbrains.kotlin.psi.KtFile
|
||||
fun showDecompiledCode(sourceFile: KtFile) {
|
||||
val decompilerService = KotlinDecompilerService.getInstance() ?: return
|
||||
val decompiledCode = decompilerService.decompile(sourceFile)
|
||||
if (decompiledCode == null) {
|
||||
Messages.showErrorDialog("Cannot decompile ${sourceFile.name}", "Decompiler error")
|
||||
return
|
||||
}
|
||||
|
||||
runWriteAction {
|
||||
val root = getOrCreateDummyRoot()
|
||||
|
||||
@@ -20,7 +20,7 @@ import com.intellij.openapi.components.ServiceManager
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
interface KotlinDecompilerService {
|
||||
fun decompile(file: KtFile): String
|
||||
fun decompile(file: KtFile): String?
|
||||
|
||||
companion object {
|
||||
fun getInstance(): KotlinDecompilerService? {
|
||||
|
||||
@@ -16,30 +16,39 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.internal
|
||||
|
||||
import com.intellij.openapi.fileTypes.StdFileTypes
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.java.decompiler.IdeaLogger
|
||||
import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler
|
||||
import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider
|
||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences
|
||||
import org.jetbrains.java.decompiler.main.extern.IResultSaver
|
||||
import org.jetbrains.kotlin.idea.actions.canBeDecompiledToJava
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.io.File
|
||||
import java.util.jar.Manifest
|
||||
|
||||
class KotlinDecompilerServiceImpl : KotlinDecompilerService {
|
||||
override fun decompile(file: KtFile): String {
|
||||
val generationState = KotlinBytecodeToolWindow.compileSingleFile(file, true, true, true)
|
||||
override fun decompile(file: KtFile): String? {
|
||||
|
||||
val bytecodeMap = hashMapOf<File, ByteArray>()
|
||||
generationState.factory.asList().filter { FileUtilRt.extensionEquals(it.relativePath, "class") }.forEach {
|
||||
bytecodeMap[File("/${it.relativePath}").absoluteFile] = it.asByteArray()
|
||||
val bytecodeMap = when {
|
||||
file.canBeDecompiledToJava() -> bytecodeMapForExistingClassfile(file.virtualFile)
|
||||
!file.isCompiled -> bytecodeMapForSourceFile(file)
|
||||
else -> return null
|
||||
}
|
||||
|
||||
val bytecodeProvider = KotlinBytecodeProvider(bytecodeMap)
|
||||
val resultSaver = KotlinResultSaver()
|
||||
val options = hashMapOf<String, Any>(
|
||||
IFernflowerPreferences.REMOVE_BRIDGE to "0"
|
||||
IFernflowerPreferences.REMOVE_BRIDGE to "0"
|
||||
)
|
||||
|
||||
val bytecodeProvider = IBytecodeProvider {
|
||||
externalPath, internalPath ->
|
||||
val path = File(FileUtil.toSystemIndependentName(externalPath))
|
||||
bytecodeMap[path]?.invoke()
|
||||
}
|
||||
|
||||
val decompiler = BaseDecompiler(bytecodeProvider, resultSaver, options, IdeaLogger())
|
||||
for (path in bytecodeMap.keys) {
|
||||
decompiler.addSpace(path, true)
|
||||
@@ -48,12 +57,29 @@ class KotlinDecompilerServiceImpl : KotlinDecompilerService {
|
||||
return resultSaver.resultText
|
||||
}
|
||||
|
||||
class KotlinBytecodeProvider(val bytecodeMap: Map<File, ByteArray>) : IBytecodeProvider {
|
||||
override fun getBytecode(externalPath: String?, internalPath: String?): ByteArray? {
|
||||
return bytecodeMap[File(externalPath)]
|
||||
fun bytecodeMapForExistingClassfile(file: VirtualFile): Map<File, () -> ByteArray> {
|
||||
val mask = "${file.nameWithoutExtension}$"
|
||||
val files =
|
||||
mapOf(file.path to file) +
|
||||
file.parent.children.filter {
|
||||
it.nameWithoutExtension.startsWith(mask) && it.fileType === StdFileTypes.CLASS
|
||||
}.map { it.path to it }
|
||||
|
||||
return files.entries.associate {
|
||||
Pair(File(it.key), { it.value.contentsToByteArray() })
|
||||
}
|
||||
}
|
||||
|
||||
fun bytecodeMapForSourceFile(file: KtFile): Map<File, () -> ByteArray> {
|
||||
val generationState = KotlinBytecodeToolWindow.compileSingleFile(file, true, true, true)
|
||||
|
||||
val bytecodeMap = hashMapOf<File, () -> ByteArray>()
|
||||
generationState.factory.asList().filter { FileUtilRt.extensionEquals(it.relativePath, "class") }.forEach {
|
||||
bytecodeMap[File("/${it.relativePath}").absoluteFile] = { it.asByteArray() }
|
||||
}
|
||||
return bytecodeMap
|
||||
}
|
||||
|
||||
class KotlinResultSaver : IResultSaver {
|
||||
private val decompiledText = mutableMapOf<String, String>()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user