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
|
###### New features
|
||||||
|
|
||||||
- Added "Decompile" button to Kotlin bytecode toolwindow
|
- 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
|
- [`KT-11704`](https://youtrack.jetbrains.com/issue/KT-11704) Support file path references inside of Kotlin string literals
|
||||||
|
|
||||||
###### Issues fixed:
|
###### Issues fixed:
|
||||||
|
|||||||
@@ -99,6 +99,11 @@
|
|||||||
<add-to-group group-id="KotlinToolsGroup"/>
|
<add-to-group group-id="KotlinToolsGroup"/>
|
||||||
</action>
|
</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"
|
<action id="ExtractFunction" class="org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractFunctionAction"
|
||||||
text="_Function..." use-shortcut-of="ExtractMethod">
|
text="_Function..." use-shortcut-of="ExtractMethod">
|
||||||
<add-to-group group-id="IntroduceActionsGroup" anchor="after" relative-to-action="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.versions.UnsupportedAbiVersionNotificationPanelProvider"/>
|
||||||
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.configuration.KotlinSetupEnvironmentNotificationProvider"/>
|
<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"/>
|
<referencesSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearcher"/>
|
||||||
<directClassInheritorsSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinDirectInheritorsSearcher"/>
|
<directClassInheritorsSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinDirectInheritorsSearcher"/>
|
||||||
<overridingMethodsSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinOverridingMethodsWithGenericsSearcher"/>
|
<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
|
package org.jetbrains.kotlin.idea.internal
|
||||||
|
|
||||||
import com.intellij.openapi.fileEditor.OpenFileDescriptor
|
import com.intellij.openapi.fileEditor.OpenFileDescriptor
|
||||||
|
import com.intellij.openapi.ui.Messages
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import com.intellij.openapi.vfs.VfsUtil
|
import com.intellij.openapi.vfs.VfsUtil
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
@@ -29,6 +30,10 @@ import org.jetbrains.kotlin.psi.KtFile
|
|||||||
fun showDecompiledCode(sourceFile: KtFile) {
|
fun showDecompiledCode(sourceFile: KtFile) {
|
||||||
val decompilerService = KotlinDecompilerService.getInstance() ?: return
|
val decompilerService = KotlinDecompilerService.getInstance() ?: return
|
||||||
val decompiledCode = decompilerService.decompile(sourceFile)
|
val decompiledCode = decompilerService.decompile(sourceFile)
|
||||||
|
if (decompiledCode == null) {
|
||||||
|
Messages.showErrorDialog("Cannot decompile ${sourceFile.name}", "Decompiler error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
runWriteAction {
|
runWriteAction {
|
||||||
val root = getOrCreateDummyRoot()
|
val root = getOrCreateDummyRoot()
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import com.intellij.openapi.components.ServiceManager
|
|||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
interface KotlinDecompilerService {
|
interface KotlinDecompilerService {
|
||||||
fun decompile(file: KtFile): String
|
fun decompile(file: KtFile): String?
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getInstance(): KotlinDecompilerService? {
|
fun getInstance(): KotlinDecompilerService? {
|
||||||
|
|||||||
@@ -16,30 +16,39 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.internal
|
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.util.io.FileUtilRt
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import org.jetbrains.java.decompiler.IdeaLogger
|
import org.jetbrains.java.decompiler.IdeaLogger
|
||||||
import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler
|
import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler
|
||||||
import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider
|
import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider
|
||||||
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences
|
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences
|
||||||
import org.jetbrains.java.decompiler.main.extern.IResultSaver
|
import org.jetbrains.java.decompiler.main.extern.IResultSaver
|
||||||
|
import org.jetbrains.kotlin.idea.actions.canBeDecompiledToJava
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.jar.Manifest
|
import java.util.jar.Manifest
|
||||||
|
|
||||||
class KotlinDecompilerServiceImpl : KotlinDecompilerService {
|
class KotlinDecompilerServiceImpl : KotlinDecompilerService {
|
||||||
override fun decompile(file: KtFile): String {
|
override fun decompile(file: KtFile): String? {
|
||||||
val generationState = KotlinBytecodeToolWindow.compileSingleFile(file, true, true, true)
|
|
||||||
|
|
||||||
val bytecodeMap = hashMapOf<File, ByteArray>()
|
val bytecodeMap = when {
|
||||||
generationState.factory.asList().filter { FileUtilRt.extensionEquals(it.relativePath, "class") }.forEach {
|
file.canBeDecompiledToJava() -> bytecodeMapForExistingClassfile(file.virtualFile)
|
||||||
bytecodeMap[File("/${it.relativePath}").absoluteFile] = it.asByteArray()
|
!file.isCompiled -> bytecodeMapForSourceFile(file)
|
||||||
|
else -> return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val bytecodeProvider = KotlinBytecodeProvider(bytecodeMap)
|
|
||||||
val resultSaver = KotlinResultSaver()
|
val resultSaver = KotlinResultSaver()
|
||||||
val options = hashMapOf<String, Any>(
|
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())
|
val decompiler = BaseDecompiler(bytecodeProvider, resultSaver, options, IdeaLogger())
|
||||||
for (path in bytecodeMap.keys) {
|
for (path in bytecodeMap.keys) {
|
||||||
decompiler.addSpace(path, true)
|
decompiler.addSpace(path, true)
|
||||||
@@ -48,12 +57,29 @@ class KotlinDecompilerServiceImpl : KotlinDecompilerService {
|
|||||||
return resultSaver.resultText
|
return resultSaver.resultText
|
||||||
}
|
}
|
||||||
|
|
||||||
class KotlinBytecodeProvider(val bytecodeMap: Map<File, ByteArray>) : IBytecodeProvider {
|
fun bytecodeMapForExistingClassfile(file: VirtualFile): Map<File, () -> ByteArray> {
|
||||||
override fun getBytecode(externalPath: String?, internalPath: String?): ByteArray? {
|
val mask = "${file.nameWithoutExtension}$"
|
||||||
return bytecodeMap[File(externalPath)]
|
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 {
|
class KotlinResultSaver : IResultSaver {
|
||||||
private val decompiledText = mutableMapOf<String, String>()
|
private val decompiledText = mutableMapOf<String, String>()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user