Skip to content

Commit

Permalink
Add a programmatic way of getting all options, including plugin options
Browse files Browse the repository at this point in the history
  • Loading branch information
sschr15 committed Aug 18, 2024
1 parent 6930e47 commit 121cd7a
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 158 deletions.
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
package org.vineflower.kotlin;

import org.jetbrains.java.decompiler.api.DecompilerOption;
import org.jetbrains.java.decompiler.api.plugin.PluginOptions;
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences.*;

public interface KotlinOptions {
@Name("Show public visibility")
@Description("If a construct is public, show the public keyword")
@Type(Type.BOOLEAN)
@Type(DecompilerOption.Type.BOOLEAN)
String SHOW_PUBLIC_VISIBILITY = "kt-show-public";

@Name("Enable Kotlin plugin")
@Description("Decompile Kotlin classes as Kotlin instead of Java")
@Type(Type.BOOLEAN)
@Type(DecompilerOption.Type.BOOLEAN)
String DECOMPILE_KOTLIN = "kt-enable";

@Name("Unknown default arg string")
@Description("String to use for unknown default arguments, or empty to not indicate unknown defaults")
@Type(Type.STRING)
@Type(DecompilerOption.Type.STRING)
String UNKNOWN_DEFAULT_ARG_STRING = "kt-unknown-defaults";

@Name("Collapse string concatenation")
@Description("Convert string concatenations to Kotlin string templates.")
@Type(Type.BOOLEAN)
@Type(DecompilerOption.Type.BOOLEAN)
String COLLAPSE_STRING_CONCATENATION = "kt-collapse-string-concat";

static void addDefaults(PluginOptions.AddDefaults cons) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
package org.vineflower.variablerenaming;

import org.jetbrains.java.decompiler.api.DecompilerOption;
import org.jetbrains.java.decompiler.api.plugin.PluginOptions;
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences.*;

public interface VariableRenamingOptions {
@Name("Variable Renaming")
@Description("Use a custom renamer for variable names. Built-in options include \"jad\" and \"tiny\".")
@Type(Type.STRING)
@Type(DecompilerOption.Type.STRING)
String VARIABLE_RENAMER = "variable-renaming";

@Name("Rename Parameters")
@Description("Use the custom renamer for parameters in addition to locals.")
@Type(Type.BOOLEAN)
@Type(DecompilerOption.Type.BOOLEAN)
String RENAME_PARAMETERS = "rename-parameters";

@Name("[Deprecated] JAD-Style Variable Naming")
@Description("Use JAD-style variable naming. Deprecated, set \"variable-renamer=jad\" instead.")
@Type(Type.BOOLEAN)
@Type(DecompilerOption.Type.BOOLEAN)
String USE_JAD_VARNAMING = "jad-style-variable-naming";

@Name("[Deprecated] JAD-Style Parameter Naming")
@Description("Alias for \"rename-parameters\". Deprecated, use that option instead.")
@Type(Type.BOOLEAN)
@Type(DecompilerOption.Type.BOOLEAN)
String USE_JAD_PARAMETER_NAMING = "jad-style-parameter-naming";

static void addDefaults(PluginOptions.AddDefaults cons) {
Expand Down
184 changes: 184 additions & 0 deletions src/org/jetbrains/java/decompiler/api/DecompilerOption.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package org.jetbrains.java.decompiler.api;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.java.decompiler.api.plugin.Plugin;
import org.jetbrains.java.decompiler.api.plugin.PluginOptions;
import org.jetbrains.java.decompiler.main.Fernflower;
import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler;
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
import org.jetbrains.java.decompiler.main.plugins.PluginContext;

import java.lang.reflect.Field;
import java.util.*;

/**
* Represents a decompiler option. These can be passed from command line or as
* a map into a {@link BaseDecompiler} or {@link Fernflower} constructor.
* <p>
* As plugins might not provide all information, some fields are nullable.
*
* @param id The unique identifier of the option. This is what is passed to the decompiler.
* @param name A human-readable name for the option.
* @param description A human-readable description of the option.
* @param type The type of the option.
* @param plugin The plugin that provides this option. If {@code null}, it comes from the core decompiler.
* @param defaultValue The default value of the option.
*/
public record DecompilerOption(
@NotNull String id,
@NotNull String name,
@NotNull String description,
@NotNull Type type,
@Nullable String plugin,
@Nullable String defaultValue
) implements Comparable<DecompilerOption> {
public enum Type {
BOOLEAN("bool"),
STRING("string"),
INTEGER("int"),

;

private final String friendlyName;

Type(String friendlyName) {
this.friendlyName = friendlyName;
}

public String toString() {
return friendlyName;
}
}

/**
* Compares this option to another option. The order is set first by the plugin, then by the id.
* Core decompiler options come first.
*/
@Override
public int compareTo(@NotNull DecompilerOption decompilerOption) {
if (!Objects.equals(decompilerOption.plugin, plugin)) {
if (plugin == null) {
return -1;
}
if (decompilerOption.plugin == null) {
return 1;
}
return plugin.compareTo(decompilerOption.plugin);
}
return id.compareTo(decompilerOption.id);
}

public static List<DecompilerOption> getAll() {
List<DecompilerOption> options = new ArrayList<>();

List<Field> fields = Arrays.stream(IFernflowerPreferences.class.getDeclaredFields())
.filter(field -> field.getType() == String.class)
.toList();

Map<String, Object> defaults = IFernflowerPreferences.DEFAULTS;
addOptions(fields, options, defaults, null);

PluginContext ctx = new PluginContext();
ctx.findPlugins();

for (Plugin plugin : ctx.getPlugins()) {
PluginOptions opt = plugin.getPluginOptions();

if (opt != null) {
var opts = opt.provideOptions();

List<Field> pluginFields = Arrays.stream(opts.a.getDeclaredFields())
.filter(field -> field.getType() == String.class)
.toList();

Map<String, Object> pluginDefaults = new HashMap<>();
opts.b.accept(pluginDefaults::put);

addOptions(pluginFields, options, pluginDefaults, plugin);
}
}

options.sort(DecompilerOption::compareTo);

return options;
}

public static Map<Plugin, List<DecompilerOption>> getAllByPlugin() {
Map<Plugin, List<DecompilerOption>> options = new HashMap<>();

List<Field> fields = Arrays.stream(IFernflowerPreferences.class.getDeclaredFields())
.filter(field -> field.getType() == String.class)
.toList();

Map<String, Object> defaults = IFernflowerPreferences.DEFAULTS;
List<DecompilerOption> coreOptions = new ArrayList<>();
addOptions(fields, coreOptions, defaults, null);
coreOptions.sort(DecompilerOption::compareTo);
options.put(null, coreOptions);

PluginContext ctx = new PluginContext();
ctx.findPlugins();

for (Plugin plugin : ctx.getPlugins()) {
PluginOptions opt = plugin.getPluginOptions();

if (opt != null) {
var opts = opt.provideOptions();

List<Field> pluginFields = Arrays.stream(opts.a.getDeclaredFields())
.filter(field -> field.getType() == String.class)
.toList();

Map<String, Object> pluginDefaults = new HashMap<>();
opts.b.accept(pluginDefaults::put);

List<DecompilerOption> pluginOptions = new ArrayList<>();
addOptions(pluginFields, pluginOptions, pluginDefaults, plugin);

pluginOptions.sort(DecompilerOption::compareTo);

options.put(plugin, pluginOptions);
}
}

return options;
}

private static void addOptions(List<Field> fields, List<DecompilerOption> options, Map<String, Object> defaults, Plugin plugin) {
for (Field field : fields) {
IFernflowerPreferences.Name name = field.getAnnotation(IFernflowerPreferences.Name.class);
IFernflowerPreferences.Description description = field.getAnnotation(IFernflowerPreferences.Description.class);
IFernflowerPreferences.Type type = field.getAnnotation(IFernflowerPreferences.Type.class);

String paramName;
try {
paramName = (String) field.get(null);
} catch (IllegalAccessException e) {
IFernflowerPreferences.ShortName shortName = field.getAnnotation(IFernflowerPreferences.ShortName.class);
if (shortName == null) {
continue;
}
paramName = shortName.value();
}

if (name == null || description == null || type == null) {
continue;
}

String friendlyName = name.value();
String friendlyDescription = description.value();
Type friendlyType = type.value();
String defaultValue = defaults.containsKey(paramName) ? defaults.get(paramName).toString() : null;

options.add(new DecompilerOption(
paramName,
friendlyName,
friendlyDescription,
friendlyType,
plugin != null ? plugin.id() : null,
defaultValue
));
}
}
}
114 changes: 33 additions & 81 deletions src/org/jetbrains/java/decompiler/main/decompiler/ConsoleHelp.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.jetbrains.java.decompiler.main.decompiler;

import org.jetbrains.java.decompiler.api.DecompilerOption;
import org.jetbrains.java.decompiler.api.plugin.Plugin;
import org.jetbrains.java.decompiler.api.plugin.PluginOptions;
import org.jetbrains.java.decompiler.main.Init;
Expand Down Expand Up @@ -51,104 +52,55 @@ static void printHelp() {
System.out.println(line);
}

List<Field> fields = Arrays.stream(IFernflowerPreferences.class.getDeclaredFields())
.filter(field -> field.getType() == String.class)
.collect(Collectors.toList());
Map<Plugin, List<DecompilerOption>> options = DecompilerOption.getAllByPlugin();

Map<String, Object> defaults = IFernflowerPreferences.DEFAULTS;

writeOptions(fields, defaults);

PluginContext ctx = new PluginContext();

ctx.findPlugins();

for (Plugin plugin : ctx.getPlugins()) {
PluginOptions opt = plugin.getPluginOptions();

if (opt != null) {
var opts = opt.provideOptions();

List<Field> pluginFields = Arrays.stream(opts.a.getDeclaredFields())
.filter(field -> field.getType() == String.class)
.collect(Collectors.toList());

Map<String, Object> pluginDefaults = new HashMap<>();
opts.b.accept(pluginDefaults::put);

if (!pluginFields.isEmpty()) {
System.out.println();
System.out.println("====== Options for plugin '" + plugin.id() + "' ======");
writeOptions(pluginFields, pluginDefaults);
}
}
List<DecompilerOption> coreOptions = options.remove(null);
if (coreOptions != null) {
writeOptions(coreOptions);
}
}

private static void writeOptions(List<Field> fields, Map<String, Object> defaults) {
fields.sort(Comparator.comparing((Field a) -> {
try {
return a.get(null).toString();
} catch (IllegalAccessException e) {
return "";
}
}));

for (Field field : fields) {
IFernflowerPreferences.Name name = field.getAnnotation(IFernflowerPreferences.Name.class);
IFernflowerPreferences.Description description = field.getAnnotation(IFernflowerPreferences.Description.class);
IFernflowerPreferences.Type type = field.getAnnotation(IFernflowerPreferences.Type.class);

String paramName;
boolean isShortName = false;
try {
paramName = (String) field.get(null);
} catch (IllegalAccessException e) {
IFernflowerPreferences.ShortName shortName = field.getAnnotation(IFernflowerPreferences.ShortName.class);
if (shortName == null) {
continue;
}
paramName = shortName.value();
isShortName = true;
}

if (name == null || description == null || type == null) {
for (Map.Entry<Plugin, List<DecompilerOption>> entry : options.entrySet()) {
if (entry.getValue().isEmpty()) {
continue;
}

System.out.println();
System.out.println("====== Options from " + entry.getKey().id() + " ======");
writeOptions(entry.getValue());
}
}

private static void writeOptions(List<DecompilerOption> options) {
for (DecompilerOption option : options) {
StringBuilder sb = new StringBuilder();
sb.append(isShortName ? "-" : "--")
.append(paramName)
.append(" ".repeat(Math.max(40 - paramName.length(), 0)))
sb.append("--")
.append(option.id())
.append(" ".repeat(Math.max(40 - option.id().length(), 0)))
.append("[")
.append(type.value())
.append(option.type())
.append("]")
.append(" ".repeat(Math.max(8 - type.value().length(), 0)))
.append(name.value())
.append(" ".repeat(Math.max(50 - name.value().length(), 0)))
.append(":");
.append(" ".repeat(Math.max(8 - option.type().toString().length(), 0)))
.append(option.name())
.append(" ".repeat(Math.max(50 - option.name().length(), 0)))
.append(": ");

StringBuilder sb2 = new StringBuilder();
if (defaults.containsKey(paramName)) {
sb2.append(" (default: ");
Object value = defaults.get(paramName);
switch (type.value()) {
case IFernflowerPreferences.Type.BOOLEAN:
sb2.append(value.equals("1"));
break;
case IFernflowerPreferences.Type.STRING:
sb2.append('"').append(value).append('"');
break;
default:
sb2.append(value);
break;
if (option.defaultValue() != null) {
sb2.append("(default: ");
String value = option.defaultValue();
switch (option.type()) {
case BOOLEAN -> sb2.append(value.equals("1"));
case STRING -> sb2.append('"').append(value).append('"');
case INTEGER -> sb2.append(value);
}
sb2.append(")");
sb2.append(" ".repeat(Math.max(18 - sb2.length(), 1)));
sb.append(sb2);
}

sb.append(description.value());
if (option.description() != null) {
sb.append(option.description());
}

System.out.println(sb);
}
Expand Down
Loading

0 comments on commit 121cd7a

Please sign in to comment.