Implement "Decompile Kotlin to Java" action

Add this action to "Attach sources" notification panel if present
 #KT-11657 Fixed
This commit is contained in:
Pavel V. Talanov
2016-05-17 15:51:56 +03:00
parent 68dd176d66
commit d03fedde74
6 changed files with 119 additions and 12 deletions
+1
View File
@@ -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:
+7
View File
@@ -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>()