diff --git a/.idea/libraries/java_decompiler_plugin.xml b/.idea/libraries/java_decompiler_plugin.xml
new file mode 100644
index 00000000000..85bbc24dfdd
--- /dev/null
+++ b/.idea/libraries/java_decompiler_plugin.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChangeLog.md b/ChangeLog.md
index d8d9598b32d..a696e21e10c 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -25,6 +25,7 @@ New features:
- [KT-11704](https://youtrack.jetbrains.com/issue/KT-11704) Support file path references inside of Kotlin string literals
- [KT-12092](https://youtrack.jetbrains.com/issue/KT-12092) Implement bean references in @Qualifier annotations
- [KT-12175](https://youtrack.jetbrains.com/issue/KT-12175) Don't enforce empty line after one-line constructor
+- Added "Decompile" button to Kotlin bytecode toolwindow
Issues fixed:
diff --git a/idea/idea.iml b/idea/idea.iml
index d1133b53984..7b5ac27bb8f 100644
--- a/idea/idea.iml
+++ b/idea/idea.iml
@@ -51,6 +51,7 @@
+
diff --git a/idea/src/META-INF/decompiler.xml b/idea/src/META-INF/decompiler.xml
new file mode 100644
index 00000000000..e0d38be40b2
--- /dev/null
+++ b/idea/src/META-INF/decompiler.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 108426074ca..9d4c6c91e98 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -18,6 +18,7 @@
Coverage
com.intellij.java-i18n
org.intellij.intelliLang
+ org.jetbrains.java.decompiler
diff --git a/idea/src/org/jetbrains/kotlin/idea/internal/KotlinBytecodeToolWindow.java b/idea/src/org/jetbrains/kotlin/idea/internal/KotlinBytecodeToolWindow.java
index ea41eb465af..a9c54938510 100644
--- a/idea/src/org/jetbrains/kotlin/idea/internal/KotlinBytecodeToolWindow.java
+++ b/idea/src/org/jetbrains/kotlin/idea/internal/KotlinBytecodeToolWindow.java
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.internal;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
@@ -26,12 +27,14 @@ import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.util.Alarm;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.java.decompiler.IdeaLogger;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
import org.jetbrains.kotlin.codegen.ClassBuilderFactories;
@@ -54,12 +57,16 @@ import org.jetbrains.kotlin.utils.StringsKt;
import javax.swing.*;
import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import java.util.List;
public class KotlinBytecodeToolWindow extends JPanel implements Disposable {
+ private final Logger LOG = Logger.getInstance(KotlinBytecodeToolWindow.class);
+
private static final int UPDATE_DELAY = 1000;
private static final String DEFAULT_TEXT = "/*\n" +
"Generated bytecode for Kotlin source file.\n" +
@@ -152,8 +159,9 @@ public class KotlinBytecodeToolWindow extends JPanel implements Disposable {
private final JCheckBox enableInline;
private final JCheckBox enableOptimization;
private final JCheckBox enableAssertions;
+ private final JButton decompile;
- public KotlinBytecodeToolWindow(Project project, ToolWindow toolWindow) {
+ public KotlinBytecodeToolWindow(final Project project, ToolWindow toolWindow) {
super(new BorderLayout());
myProject = project;
this.toolWindow = toolWindow;
@@ -165,10 +173,31 @@ public class KotlinBytecodeToolWindow extends JPanel implements Disposable {
JPanel optionPanel = new JPanel(new FlowLayout());
add(optionPanel, BorderLayout.NORTH);
+ decompile = new JButton("Decompile");
+ if (KotlinDecompilerService.Companion.getInstance() != null) {
+ optionPanel.add(decompile);
+ decompile.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Location location = Location.fromEditor(FileEditorManager.getInstance(myProject).getSelectedTextEditor(), myProject);
+ KtFile file = location.getKFile();
+ if (file != null) {
+ try {
+ KotlinDecompilerAdapterKt.showDecompiledCode(file);
+ }
+ catch (IdeaLogger.InternalException ex) {
+ LOG.info(ex);
+ Messages.showErrorDialog(myProject, "Failed to decompile " + file.getName() + ": " + ex, "Kotlin Bytecode Decompiler");
+ }
+ }
+ }
+ });
+ }
+
/*TODO: try to extract default parameter from compiler options*/
- enableInline = new JCheckBox("Enable inline", true);
- enableOptimization = new JCheckBox("Enable optimization", true);
- enableAssertions = new JCheckBox("Enable assertions", true);
+ enableInline = new JCheckBox("Inline", true);
+ enableOptimization = new JCheckBox("Optimization", true);
+ enableAssertions = new JCheckBox("Assertions", true);
optionPanel.add(enableInline);
optionPanel.add(enableOptimization);
optionPanel.add(enableAssertions);
@@ -193,50 +222,7 @@ public class KotlinBytecodeToolWindow extends JPanel implements Disposable {
) {
GenerationState state;
try {
- ResolutionFacade resolutionFacade = ResolutionUtils.getResolutionFacade(jetFile);
-
- BindingContext bindingContextForFile = resolutionFacade.analyzeFullyAndGetResult(Collections.singletonList(jetFile)).getBindingContext();
-
- kotlin.Pair> result = DebuggerUtils.INSTANCE.analyzeInlinedFunctions(
- resolutionFacade, bindingContextForFile, jetFile, !enableInline
- );
-
- BindingContext bindingContext = result.getFirst();
- List toProcess = result.getSecond();
-
- GenerationState.GenerateClassFilter generateClassFilter = new GenerationState.GenerateClassFilter() {
-
- @Override
- public boolean shouldGeneratePackagePart(@NotNull KtFile file) {
- return file == jetFile;
- }
-
- @Override
- public boolean shouldAnnotateClass(@NotNull KtClassOrObject processingClassOrObject) {
- return true;
- }
-
- @Override
- public boolean shouldGenerateClass(@NotNull KtClassOrObject processingClassOrObject) {
- return processingClassOrObject.getContainingKtFile() == jetFile;
- }
-
- @Override
- public boolean shouldGenerateScript(@NotNull KtScript script) {
- return script.getContainingKtFile() == jetFile;
- }
- };
-
- state = new GenerationState(jetFile.getProject(), ClassBuilderFactories.TEST,
- resolutionFacade.getModuleDescriptor(), bindingContext,
- toProcess,
- !enableAssertions,
- !enableAssertions,
- generateClassFilter,
- !enableInline,
- !enableOptimization,
- /*useTypeTableInSerializer=*/false);
- KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION);
+ state = compileSingleFile(jetFile, enableInline, enableAssertions, enableOptimization);
}
catch (ProcessCanceledException e) {
throw e;
@@ -273,6 +259,60 @@ public class KotlinBytecodeToolWindow extends JPanel implements Disposable {
return answer.toString();
}
+ @NotNull
+ public static GenerationState compileSingleFile(
+ final KtFile ktFile,
+ boolean enableInline,
+ boolean enableAssertions,
+ boolean enableOptimization
+ ) {
+ ResolutionFacade resolutionFacade = ResolutionUtils.getResolutionFacade(ktFile);
+
+ BindingContext bindingContextForFile = resolutionFacade.analyzeFullyAndGetResult(Collections.singletonList(ktFile)).getBindingContext();
+
+ kotlin.Pair> result = DebuggerUtils.INSTANCE.analyzeInlinedFunctions(
+ resolutionFacade, bindingContextForFile, ktFile, !enableInline
+ );
+
+ BindingContext bindingContext = result.getFirst();
+ List toProcess = result.getSecond();
+
+ GenerationState.GenerateClassFilter generateClassFilter = new GenerationState.GenerateClassFilter() {
+
+ @Override
+ public boolean shouldGeneratePackagePart(@NotNull KtFile file) {
+ return file == ktFile;
+ }
+
+ @Override
+ public boolean shouldAnnotateClass(@NotNull KtClassOrObject processingClassOrObject) {
+ return true;
+ }
+
+ @Override
+ public boolean shouldGenerateClass(@NotNull KtClassOrObject processingClassOrObject) {
+ return processingClassOrObject.getContainingKtFile() == ktFile;
+ }
+
+ @Override
+ public boolean shouldGenerateScript(@NotNull KtScript script) {
+ return script.getContainingKtFile() == ktFile;
+ }
+ };
+
+ GenerationState state = new GenerationState(ktFile.getProject(), ClassBuilderFactories.TEST,
+ resolutionFacade.getModuleDescriptor(), bindingContext,
+ toProcess,
+ !enableAssertions,
+ !enableAssertions,
+ generateClassFilter,
+ !enableInline,
+ !enableOptimization,
+ /*useTypeTableInSerializer=*/false);
+ KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION);
+ return state;
+ }
+
private static Pair mapLines(String text, int startLine, int endLine) {
int byteCodeLine = 0;
int byteCodeStartLine = -1;
diff --git a/idea/src/org/jetbrains/kotlin/idea/internal/KotlinDecompilerAdapter.kt b/idea/src/org/jetbrains/kotlin/idea/internal/KotlinDecompilerAdapter.kt
new file mode 100644
index 00000000000..65d7d0b4cb5
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/internal/KotlinDecompilerAdapter.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.internal
+
+import com.intellij.openapi.fileEditor.OpenFileDescriptor
+import com.intellij.openapi.util.io.FileUtil
+import com.intellij.openapi.vfs.VfsUtil
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.vfs.VirtualFileManager
+import com.intellij.openapi.vfs.WritingAccessProvider
+import com.intellij.openapi.vfs.ex.dummy.DummyFileSystem
+import org.jetbrains.kotlin.idea.util.application.runWriteAction
+import org.jetbrains.kotlin.psi.KtFile
+
+fun showDecompiledCode(sourceFile: KtFile) {
+ val decompilerService = KotlinDecompilerService.getInstance() ?: return
+ val decompiledCode = decompilerService.decompile(sourceFile)
+
+ runWriteAction {
+ val root = getOrCreateDummyRoot()
+ val decompiledFileName = FileUtil.getNameWithoutExtension(sourceFile.name) + ".decompiled.java"
+ val result = DummyFileSystem.getInstance().createChildFile(null, root, decompiledFileName)
+ VfsUtil.saveText(result, decompiledCode)
+
+ OpenFileDescriptor(sourceFile.project, result).navigate(true)
+ }
+}
+
+val KOTLIN_DECOMPILED_FOLDER = "kotlinDecompiled"
+val KOTLIN_DECOMPILED_ROOT = "dummy://$KOTLIN_DECOMPILED_FOLDER"
+
+fun getOrCreateDummyRoot(): VirtualFile =
+ VirtualFileManager.getInstance().refreshAndFindFileByUrl(KOTLIN_DECOMPILED_ROOT) ?:
+ DummyFileSystem.getInstance().createRoot(KOTLIN_DECOMPILED_FOLDER)
+
+class DecompiledFileWritingAccessProvider : WritingAccessProvider() {
+ override fun isPotentiallyWritable(file: VirtualFile): Boolean {
+ if (file.fileSystem is DummyFileSystem && file.parent.name == KOTLIN_DECOMPILED_FOLDER) {
+ return false
+ }
+ return true
+ }
+
+ override fun requestWriting(vararg files: VirtualFile): Collection = emptyList()
+}
diff --git a/idea/src/org/jetbrains/kotlin/idea/internal/KotlinDecompilerService.kt b/idea/src/org/jetbrains/kotlin/idea/internal/KotlinDecompilerService.kt
new file mode 100644
index 00000000000..f1e99e0e56e
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/internal/KotlinDecompilerService.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.internal
+
+import com.intellij.openapi.components.ServiceManager
+import org.jetbrains.kotlin.psi.KtFile
+
+interface KotlinDecompilerService {
+ fun decompile(file: KtFile): String
+
+ companion object {
+ fun getInstance(): KotlinDecompilerService? {
+ return ServiceManager.getService(KotlinDecompilerService::class.java)
+ }
+ }
+}
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/internal/KotlinDecompilerServiceImpl.kt b/idea/src/org/jetbrains/kotlin/idea/internal/KotlinDecompilerServiceImpl.kt
new file mode 100644
index 00000000000..f533dd7e685
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/internal/KotlinDecompilerServiceImpl.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.internal
+
+import com.intellij.openapi.util.io.FileUtilRt
+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.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)
+
+ val bytecodeMap = hashMapOf()
+ generationState.factory.asList().filter { FileUtilRt.extensionEquals(it.relativePath, "class") }.forEach {
+ bytecodeMap[File("/${it.relativePath}").absoluteFile] = it.asByteArray()
+ }
+
+ val bytecodeProvider = KotlinBytecodeProvider(bytecodeMap)
+ val resultSaver = KotlinResultSaver()
+ val options = hashMapOf(
+ IFernflowerPreferences.REMOVE_BRIDGE to "0"
+ )
+ val decompiler = BaseDecompiler(bytecodeProvider, resultSaver, options, IdeaLogger())
+ for (path in bytecodeMap.keys) {
+ decompiler.addSpace(path, true)
+ }
+ decompiler.decompileContext()
+ return resultSaver.resultText
+ }
+
+ class KotlinBytecodeProvider(val bytecodeMap: Map) : IBytecodeProvider {
+ override fun getBytecode(externalPath: String?, internalPath: String?): ByteArray? {
+ return bytecodeMap[File(externalPath)]
+ }
+ }
+
+ class KotlinResultSaver : IResultSaver {
+ private val decompiledText = mutableMapOf()
+
+ val resultText: String
+ get() {
+ decompiledText.values.singleOrNull()?.let { return it }
+ return buildString {
+ for ((filename, content) in decompiledText) {
+ appendln("// $filename")
+ append(content)
+ }
+ }
+ }
+
+ override fun saveFolder(path: String?) { }
+
+ override fun closeArchive(path: String?, archiveName: String?) { }
+
+ override fun copyEntry(source: String?, path: String?, archiveName: String?, entry: String?) { }
+
+ override fun createArchive(path: String?, archiveName: String?, manifest: Manifest?) { }
+
+ override fun saveClassFile(path: String?, qualifiedName: String?, entryName: String?, content: String?, mapping: IntArray?) {
+ if (entryName != null && content != null) {
+ decompiledText[entryName] = content
+ }
+ }
+
+ override fun copyFile(source: String?, path: String?, entryName: String?) { }
+
+ override fun saveClassEntry(path: String?, archiveName: String?, qualifiedName: String?, entryName: String?, content: String?) { }
+
+ override fun saveDirEntry(path: String?, archiveName: String?, entryName: String?) { }
+
+ }
+}
\ No newline at end of file