generated from JetBrains/intellij-platform-plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix Kotlin decompilation, support plugin options, support K2 Mode
- Loading branch information
Showing
9 changed files
with
403 additions
and
221 deletions.
There are no files selected for viewing
195 changes: 195 additions & 0 deletions
195
src/main/kotlin/org/vineflower/ijplugin/ClassicVineflowerPreferences.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
package org.vineflower.ijplugin | ||
|
||
import com.intellij.application.options.CodeStyle | ||
import com.intellij.openapi.application.ApplicationManager | ||
import com.intellij.openapi.util.text.StringUtil | ||
import com.intellij.ui.DocumentAdapter | ||
import com.intellij.ui.JBIntSpinner | ||
import com.intellij.ui.components.JBCheckBox | ||
import com.intellij.ui.components.JBTextField | ||
import java.lang.reflect.Field | ||
import java.lang.reflect.Modifier | ||
import java.util.* | ||
import javax.swing.event.DocumentEvent | ||
|
||
object ClassicVineflowerPreferences : VineflowerPreferences() { | ||
val ignoredPreferences = setOf( | ||
"ban", // banner | ||
"bsm", // bytecode source mapping | ||
"nls", // newline separator | ||
"__unit_test_mode__", | ||
"log", // log level | ||
"urc", // use renamer class | ||
"thr", // threads | ||
"mpm", // max processing method | ||
"\r\n", // irrelevant constant | ||
"\n", // irrelevant constant | ||
) | ||
|
||
val defaultOverrides = mapOf( | ||
"hdc" to "0", // hide default constructor | ||
"dgs" to "1", // decompile generic signatures | ||
"rsy" to "1", // remove synthetic | ||
"rbr" to "1", // remove bridge | ||
"nls" to "1", // newline separator | ||
"ban" to "//\n// Source code recreated from a .class file by Vineflower\n//\n\n", // banner | ||
"mpm" to 0, // max processing method | ||
"iib" to "1", // ignore invalid bytecode | ||
"vac" to "1", // verify anonymous classes | ||
"ind" to CodeStyle.getDefaultSettings().indentOptions.INDENT_SIZE, // indent size | ||
"__unit_test_mode__" to if (ApplicationManager.getApplication().isUnitTestMode) "1" else "0", | ||
) | ||
|
||
override fun setupSettings(entries: MutableList<SettingsEntry>, settingsMap: MutableMap<String, String>) { | ||
val classLoader = VineflowerState.getInstance().getVineflowerClassLoader().getNow(null) ?: return | ||
val preferencesClass = classLoader.loadClass("org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences") | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
val defaults = (preferencesClass.getField("DEFAULTS").get(null) as Map<String, *>).toMutableMap() | ||
defaults.putAll(defaultOverrides) | ||
|
||
|
||
val fieldAnnotations = FieldAnnotations(classLoader) | ||
|
||
for (field in preferencesClass.fields) { | ||
if (!Modifier.isStatic(field.modifiers) || field.type != String::class.java) { | ||
continue | ||
} | ||
val key = inferShortKey(field, fieldAnnotations) ?: continue | ||
if (key in ignoredPreferences) { | ||
continue | ||
} | ||
val longKey = inferLongKey(field) ?: continue | ||
|
||
val type = inferType(key, defaults, field, fieldAnnotations) ?: continue | ||
val name = inferName(key, field, fieldAnnotations) | ||
val description = inferDescription(field, fieldAnnotations) | ||
val currentValue = settingsMap[key] ?: (defaults[key] ?: defaults[longKey]!!).toString() | ||
val component = when (type) { | ||
Type.BOOLEAN -> JBCheckBox().also { checkBox -> | ||
checkBox.isSelected = currentValue == "1" | ||
checkBox.addActionListener { | ||
val newValue = if (checkBox.isSelected) "1" else "0" | ||
if (newValue != defaults[key]) { | ||
settingsMap[key] = newValue | ||
} else { | ||
settingsMap.remove(key) | ||
} | ||
} | ||
} | ||
Type.STRING -> JBTextField(currentValue).also { textField -> | ||
textField.columns = 20 | ||
textField.document.addDocumentListener(object : DocumentAdapter() { | ||
override fun textChanged(e: DocumentEvent) { | ||
val newValue = textField.text | ||
if (newValue != defaults[key]) { | ||
settingsMap[key] = newValue | ||
} else { | ||
settingsMap.remove(key) | ||
} | ||
} | ||
}) | ||
} | ||
Type.INTEGER -> JBIntSpinner(currentValue.toInt(), 0, Int.MAX_VALUE).also { spinner -> | ||
spinner.addChangeListener { | ||
val newValue = spinner.value.toString() | ||
if (newValue != defaults[key]) { | ||
settingsMap[key] = newValue | ||
} else { | ||
settingsMap.remove(key) | ||
} | ||
} | ||
} | ||
} | ||
entries += SettingsEntry(name, component, description) | ||
} | ||
} | ||
|
||
private val nameOverrides = mapOf( | ||
"dc4" to "Decompile Class 1.4", | ||
"ind" to "Indent Size", | ||
"lit" to "Literals As-Is", | ||
) | ||
|
||
private fun inferLongKey(field: Field): String? { | ||
return field.get(null) as String? | ||
} | ||
|
||
private fun inferShortKey(field: Field, fieldAnnotations: FieldAnnotations): String? { | ||
if (fieldAnnotations.shortName != null) { | ||
val shortName = field.getAnnotation(fieldAnnotations.shortName)?.value | ||
if (shortName != null) { | ||
return shortName | ||
} | ||
} | ||
|
||
return field.get(null) as String? | ||
} | ||
|
||
private fun inferType(key: String, defaults: Map<String, *>, field: Field, fieldAnnotations: FieldAnnotations): Type? { | ||
if (fieldAnnotations.type != null) { | ||
when (field.getAnnotation(fieldAnnotations.type)?.value) { | ||
"bool" -> return Type.BOOLEAN | ||
"int" -> return Type.INTEGER | ||
"string" -> return Type.STRING | ||
} | ||
} | ||
|
||
val dflt = defaults[key]?.toString() ?: return null | ||
if (dflt == "0" || dflt == "1") { | ||
return Type.BOOLEAN | ||
} | ||
if (dflt.toIntOrNull() != null) { | ||
return Type.INTEGER | ||
} | ||
return Type.STRING | ||
} | ||
|
||
private fun inferName(key: String, field: Field, fieldAnnotations: FieldAnnotations): String { | ||
if (fieldAnnotations.name != null) { | ||
val name = field.getAnnotation(fieldAnnotations.name)?.value | ||
if (name != null) { | ||
return name | ||
} | ||
} | ||
|
||
val nameOverride = nameOverrides[key] | ||
if (nameOverride != null) { | ||
return nameOverride | ||
} | ||
|
||
return StringUtil.toTitleCase(field.name.replace("_", " ").toLowerCase(Locale.ROOT)) | ||
} | ||
|
||
private fun inferDescription(field: Field, fieldAnnotations: FieldAnnotations): String? { | ||
if (fieldAnnotations.description != null) { | ||
val description = field.getAnnotation(fieldAnnotations.description)?.value | ||
if (description != null) { | ||
return description | ||
} | ||
} | ||
|
||
return null | ||
} | ||
|
||
private val Annotation.value get() = javaClass.getMethod("value").invoke(this) as? String | ||
|
||
class FieldAnnotations(classLoader: ClassLoader) { | ||
val name = classLoader.tryLoadAnnotation("org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences\$Name") | ||
val description = classLoader.tryLoadAnnotation("org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences\$Description") | ||
val shortName = classLoader.tryLoadAnnotation("org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences\$ShortName") | ||
val type = classLoader.tryLoadAnnotation("org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences\$Type") | ||
|
||
companion object { | ||
private fun ClassLoader.tryLoadAnnotation(name: String): Class<out Annotation>? { | ||
return try { | ||
loadClass(name).asSubclass(Annotation::class.java) | ||
} catch (e: ClassNotFoundException) { | ||
null | ||
} catch (e: ClassCastException) { | ||
null | ||
} | ||
} | ||
} | ||
} | ||
} |
144 changes: 144 additions & 0 deletions
144
src/main/kotlin/org/vineflower/ijplugin/NewVineflowerPreferences.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package org.vineflower.ijplugin | ||
|
||
import com.intellij.application.options.CodeStyle | ||
import com.intellij.openapi.application.ApplicationManager | ||
import com.intellij.ui.DocumentAdapter | ||
import com.intellij.ui.JBIntSpinner | ||
import com.intellij.ui.components.JBCheckBox | ||
import com.intellij.ui.components.JBTextField | ||
import javax.swing.event.DocumentEvent | ||
|
||
class NewVineflowerPreferences : VineflowerPreferences() { | ||
val ignoredPreferences = setOf( | ||
"banner", | ||
"bytecode-source-mapping", | ||
"new-line-separator", | ||
"log-level", | ||
"user-renamer-class", | ||
"thread-count", | ||
"max-time-per-method", | ||
"indent-string", // Custom implementation | ||
) | ||
|
||
val defaultOverrides = mapOf( | ||
"hide-default-constructor" to "0", | ||
"decompile-generics" to "1", | ||
"remove-synthetic" to "1", | ||
"remove-bridge" to "1", | ||
"new-line-separator" to "1", | ||
"banner" to "//\n// Source code recreated from a .class file by Vineflower\n//\n\n", | ||
"max-time-per-method" to "0", | ||
"ignore-invalid-bytecode" to "1", | ||
"verify-anonymous-classes" to "1", | ||
"indent-string" to " ".repeat(CodeStyle.getDefaultSettings().indentOptions.INDENT_SIZE), | ||
"__unit_test_mode__" to if (ApplicationManager.getApplication().isUnitTestMode) "1" else "0", | ||
) | ||
|
||
val nameOverrides = mapOf( | ||
"keep-literals" to "Literals As-Is", | ||
"decompiler-java4" to "Resugar 1-4 Class Refs", | ||
) | ||
|
||
data class Option( | ||
val key: String, | ||
val type: Type, | ||
val name: String, | ||
val description: String, | ||
val plugin: String?, | ||
val defaultValue: String?, | ||
) { | ||
constructor(vfOption: Any, vfClass: Class<*>) : this( | ||
key = vfClass.getMethod("id").invoke(vfOption) as String, | ||
type = when (vfClass.getMethod("type").invoke(vfOption).toString()) { | ||
"bool" -> Type.BOOLEAN | ||
"int" -> Type.INTEGER | ||
else -> Type.STRING | ||
}, | ||
name = vfClass.getMethod("name").invoke(vfOption) as String, | ||
description = vfClass.getMethod("description").invoke(vfOption) as String, | ||
plugin = vfClass.getMethod("plugin").invoke(vfOption) as String?, | ||
defaultValue = vfClass.getMethod("defaultValue").invoke(vfOption) as String?, | ||
) | ||
} | ||
|
||
private val classLoader = VineflowerState.getInstance().getVineflowerClassLoader().getNow(null) | ||
private val optionClass = classLoader.loadClass("org.jetbrains.java.decompiler.api.DecompilerOption") | ||
private val getAllOptions = optionClass.getMethod("getAll") | ||
private val initVineflower = classLoader.loadClass("org.jetbrains.java.decompiler.main.Init") | ||
.getMethod("init") | ||
|
||
override fun setupSettings(entries: MutableList<SettingsEntry>, settingsMap: MutableMap<String, String>) { | ||
initVineflower(null) | ||
|
||
val options = (getAllOptions(null) as List<*>) | ||
.map { Option(it!!, optionClass) } | ||
.filter { it.key !in ignoredPreferences } | ||
|
||
for (option in options) { | ||
if (option.key in ignoredPreferences) continue | ||
|
||
val defaultValue = if (option.key in defaultOverrides) defaultOverrides[option.key] else option.defaultValue | ||
val currentValue = settingsMap[option.key] ?: defaultValue | ||
|
||
val component = when (option.type) { | ||
Type.BOOLEAN -> JBCheckBox().apply { | ||
isSelected = currentValue == "1" | ||
addActionListener { | ||
val newValue = if (isSelected) "1" else "0" | ||
if (newValue != defaultValue) { | ||
settingsMap[option.key] = newValue | ||
} else { | ||
settingsMap.remove(option.key) | ||
} | ||
} | ||
} | ||
Type.STRING -> JBTextField(currentValue).apply { | ||
columns = 20 | ||
document.addDocumentListener(object : DocumentAdapter() { | ||
override fun textChanged(e: DocumentEvent) { | ||
val newValue = text | ||
if (newValue != defaultValue) { | ||
settingsMap[option.key] = newValue | ||
} else { | ||
settingsMap.remove(option.key) | ||
} | ||
} | ||
}) | ||
} | ||
Type.INTEGER -> JBIntSpinner(currentValue?.toInt() ?: 0, 0, Int.MAX_VALUE).apply { | ||
addChangeListener { | ||
val newValue = value.toString() | ||
if (newValue != defaultValue) { | ||
settingsMap[option.key] = newValue | ||
} else { | ||
settingsMap.remove(option.key) | ||
} | ||
} | ||
} | ||
} | ||
|
||
val name = nameOverrides[option.key] ?: option.name | ||
val desc = option.description | ||
|
||
entries.add(SettingsEntry(name, component, desc, option.plugin)) | ||
} | ||
|
||
run { | ||
val currentIndentString = settingsMap["indent-string"] ?: defaultOverrides["indent-string"]!! | ||
val component = JBIntSpinner(currentIndentString.length, 0, Int.MAX_VALUE).apply { | ||
addChangeListener { | ||
val newValue = " ".repeat(value.toString().toInt()) | ||
if (newValue != defaultOverrides["indent-string"]) { | ||
settingsMap["indent-string"] = newValue | ||
} else { | ||
settingsMap.remove("indent-string") | ||
} | ||
} | ||
} | ||
|
||
entries.add(SettingsEntry("Indent Size", component, "Number of spaces to use for each indentation level.")) | ||
} | ||
|
||
entries.sortBy { it.name } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.