fix(plugins): add a better way to init plugins options
This commit is contained in:
@@ -6,6 +6,10 @@ import java.util.function.Function;
|
||||
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
|
||||
/**
|
||||
* Prefer {@link BasePluginOptionsBuilder} as a better way to init and parse options
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class BaseOptionsParser implements JadxPluginOptions {
|
||||
|
||||
protected Map<String, String> options;
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
package jadx.api.plugins.options.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.api.plugins.options.OptionFlag;
|
||||
import jadx.api.plugins.options.OptionType;
|
||||
|
||||
/**
|
||||
* Base class for {@link JadxPluginOptions} implementation
|
||||
* <p>
|
||||
* Override {@link BasePluginOptionsBuilder#registerOptions()} method
|
||||
* and use *option methods to add option info.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class BasePluginOptionsBuilder implements JadxPluginOptions {
|
||||
|
||||
private final List<OptionData<?>> options = new ArrayList<>();
|
||||
|
||||
public abstract void registerOptions();
|
||||
|
||||
public BasePluginOptionsBuilder() {
|
||||
registerOptions();
|
||||
for (OptionData<?> option : options) {
|
||||
option.validate();
|
||||
}
|
||||
}
|
||||
|
||||
public <T> OptionBuilder<T> option(String name) {
|
||||
return addOption(new OptionData<>(name));
|
||||
}
|
||||
|
||||
public <T> OptionBuilder<T> option(String name, Class<T> optionType) {
|
||||
return addOption(new OptionData<>(name));
|
||||
}
|
||||
|
||||
public OptionBuilder<Boolean> boolOption(String name) {
|
||||
return addOption(
|
||||
new OptionData<Boolean>(name)
|
||||
.type(OptionType.BOOLEAN)
|
||||
.values(Arrays.asList(Boolean.TRUE, Boolean.FALSE))
|
||||
.formatter(b -> b ? "yes" : "no")
|
||||
.parser(val -> parseBoolOption(name, val)));
|
||||
}
|
||||
|
||||
public OptionBuilder<String> strOption(String name) {
|
||||
return addOption(
|
||||
new OptionData<String>(name)
|
||||
.type(OptionType.STRING)
|
||||
.formatter(v -> v)
|
||||
.parser(v -> v));
|
||||
}
|
||||
|
||||
public <E extends Enum<?>> OptionBuilder<E> enumOption(String name, E[] values, Function<String, E> valueOf) {
|
||||
return addOption(
|
||||
new OptionData<E>(name)
|
||||
.type(OptionType.STRING)
|
||||
.values(Arrays.asList(values))
|
||||
.formatter(v -> v.name().toLowerCase(Locale.ROOT))
|
||||
.parser(v -> valueOf.apply(v.toUpperCase(Locale.ROOT))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOptions(Map<String, String> map) {
|
||||
for (OptionData<?> option : options) {
|
||||
parseOption(option, map.get(option.name));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OptionDescription> getOptionsDescriptions() {
|
||||
return Collections.unmodifiableList(options);
|
||||
}
|
||||
|
||||
private static <T> void parseOption(OptionData<T> option, @Nullable String value) {
|
||||
T parsedValue;
|
||||
if (value == null) {
|
||||
parsedValue = option.defaultValue;
|
||||
} else {
|
||||
try {
|
||||
parsedValue = option.getParser().apply(value);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Parse failed for option: " + option.name + ", value: " + value, e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
option.getSetter().accept(parsedValue);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Setter invoke failed for option: " + option.name + ", value: " + parsedValue, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean parseBoolOption(String name, String val) {
|
||||
String valLower = val.trim().toLowerCase(Locale.ROOT);
|
||||
if (valLower.equals("yes") || valLower.equals("true")) {
|
||||
return true;
|
||||
}
|
||||
if (valLower.equals("no") || valLower.equals("false")) {
|
||||
return false;
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown value '" + val + "' for option '" + name + "', expect: 'yes' or 'no'");
|
||||
}
|
||||
|
||||
private <T> OptionBuilder<T> addOption(OptionBuilder<T> optionData) {
|
||||
this.options.add((OptionData<?>) optionData);
|
||||
return optionData;
|
||||
}
|
||||
|
||||
protected static class OptionData<T> implements OptionDescription, OptionBuilder<T> {
|
||||
private final String name;
|
||||
private String desc;
|
||||
private List<T> values = Collections.emptyList();
|
||||
private OptionType type = OptionType.STRING;
|
||||
private Set<OptionFlag> flags = EnumSet.noneOf(OptionFlag.class);
|
||||
private Function<String, T> parser;
|
||||
private Function<T, String> formatter;
|
||||
private Consumer<T> setter;
|
||||
private T defaultValue;
|
||||
|
||||
public OptionData(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> values() {
|
||||
return values.stream().map(formatter).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String defaultValue() {
|
||||
return formatter.apply(defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<OptionFlag> getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionBuilder<T> description(String desc) {
|
||||
this.desc = desc;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionBuilder<T> defaultValue(@Nullable T defValue) {
|
||||
this.defaultValue = defValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionBuilder<T> parser(Function<String, T> parser) {
|
||||
this.parser = parser;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionBuilder<T> formatter(Function<T, String> formatter) {
|
||||
this.formatter = formatter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionBuilder<T> setter(Consumer<T> setter) {
|
||||
this.setter = setter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionBuilder<T> type(OptionType optionType) {
|
||||
this.type = optionType;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionBuilder<T> flags(OptionFlag... flags) {
|
||||
this.flags = EnumSet.copyOf(Arrays.asList(flags));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionBuilder<T> values(List<T> values) {
|
||||
this.values = values;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Function<String, T> getParser() {
|
||||
return parser;
|
||||
}
|
||||
|
||||
public Function<T, String> getFormatter() {
|
||||
return formatter;
|
||||
}
|
||||
|
||||
public Consumer<T> getSetter() {
|
||||
return setter;
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
if (desc == null || desc.isEmpty()) {
|
||||
throw new IllegalArgumentException("Description should be set for option: " + name);
|
||||
}
|
||||
if (parser == null) {
|
||||
throw new IllegalArgumentException("Parser should be set for option: " + name);
|
||||
}
|
||||
if (formatter == null) {
|
||||
throw new IllegalArgumentException("Formatter should be set for option: " + name);
|
||||
}
|
||||
if (setter == null) {
|
||||
throw new IllegalArgumentException("Setter should be set for option: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package jadx.api.plugins.options.impl;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jadx.api.plugins.options.OptionFlag;
|
||||
import jadx.api.plugins.options.OptionType;
|
||||
|
||||
public interface OptionBuilder<T> {
|
||||
|
||||
/**
|
||||
* Option description (required)
|
||||
*/
|
||||
OptionBuilder<T> description(String desc);
|
||||
|
||||
OptionBuilder<T> defaultValue(T defValue);
|
||||
|
||||
/**
|
||||
* Function to parse input string into option value (required)
|
||||
*/
|
||||
OptionBuilder<T> parser(Function<String, T> parser);
|
||||
|
||||
/**
|
||||
* Function to format option value into string for build help (required)
|
||||
*/
|
||||
OptionBuilder<T> formatter(Function<T, String> formatter);
|
||||
|
||||
/**
|
||||
* Function to save/apply parsed option value (required)
|
||||
*/
|
||||
OptionBuilder<T> setter(Consumer<T> setter);
|
||||
|
||||
/**
|
||||
* Possible option values
|
||||
*/
|
||||
OptionBuilder<T> values(List<T> values);
|
||||
|
||||
OptionBuilder<T> type(OptionType optionType);
|
||||
|
||||
OptionBuilder<T> flags(OptionFlag... flags);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -64,6 +65,13 @@ public class ListUtils {
|
||||
return new ArrayList<>(new LinkedHashSet<>(list));
|
||||
}
|
||||
|
||||
public static <T> List<T> concat(T first, T[] values) {
|
||||
List<T> list = new ArrayList<>(1 + values.length);
|
||||
list.add(first);
|
||||
list.addAll(Arrays.asList(values));
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace old element to new one.
|
||||
* Support null and empty immutable list (created by Collections.emptyList())
|
||||
@@ -175,4 +183,5 @@ public class ListUtils {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+8
-20
@@ -1,29 +1,17 @@
|
||||
package jadx.plugins.input.dex;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import jadx.api.plugins.options.impl.BasePluginOptionsBuilder;
|
||||
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.api.plugins.options.impl.BaseOptionsParser;
|
||||
import jadx.api.plugins.options.impl.JadxOptionDescription;
|
||||
public class DexInputOptions extends BasePluginOptionsBuilder {
|
||||
|
||||
public class DexInputOptions extends BaseOptionsParser {
|
||||
|
||||
private static final String VERIFY_CHECKSUM_OPT = DexInputPlugin.PLUGIN_ID + ".verify-checksum";
|
||||
|
||||
private boolean verifyChecksum = true;
|
||||
private boolean verifyChecksum;
|
||||
|
||||
@Override
|
||||
public void parseOptions() {
|
||||
verifyChecksum = getBooleanOption(VERIFY_CHECKSUM_OPT, true);
|
||||
}
|
||||
|
||||
public List<OptionDescription> getOptionsDescriptions() {
|
||||
return Collections.singletonList(
|
||||
JadxOptionDescription.booleanOption(
|
||||
VERIFY_CHECKSUM_OPT,
|
||||
"verify dex file checksum before load",
|
||||
true));
|
||||
public void registerOptions() {
|
||||
boolOption(DexInputPlugin.PLUGIN_ID + ".verify-checksum")
|
||||
.description("verify dex file checksum before load")
|
||||
.defaultValue(true)
|
||||
.setter(v -> verifyChecksum = v);
|
||||
}
|
||||
|
||||
public boolean isVerifyChecksum() {
|
||||
|
||||
+14
-33
@@ -1,44 +1,29 @@
|
||||
package jadx.plugins.input.javaconvert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import jadx.api.plugins.options.impl.BasePluginOptionsBuilder;
|
||||
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.api.plugins.options.impl.BaseOptionsParser;
|
||||
import jadx.api.plugins.options.impl.JadxOptionDescription;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import static jadx.plugins.input.javaconvert.JavaConvertPlugin.PLUGIN_ID;
|
||||
|
||||
public class JavaConvertOptions extends BaseOptionsParser {
|
||||
|
||||
private static final String MODE_OPT = JavaConvertPlugin.PLUGIN_ID + ".mode";
|
||||
private static final String D8_DESUGAR_OPT = JavaConvertPlugin.PLUGIN_ID + ".d8-desugar";
|
||||
public class JavaConvertOptions extends BasePluginOptionsBuilder {
|
||||
|
||||
public enum Mode {
|
||||
DX, D8, BOTH
|
||||
}
|
||||
|
||||
private Mode mode = Mode.BOTH;
|
||||
private boolean d8Desugar = false;
|
||||
private Mode mode;
|
||||
private boolean d8Desugar;
|
||||
|
||||
@Override
|
||||
public void parseOptions() {
|
||||
mode = getOption(MODE_OPT, name -> Mode.valueOf(name.toUpperCase(Locale.ROOT)), Mode.BOTH);
|
||||
d8Desugar = getBooleanOption(D8_DESUGAR_OPT, false);
|
||||
}
|
||||
public void registerOptions() {
|
||||
enumOption(PLUGIN_ID + ".mode", Mode.values(), Mode::valueOf)
|
||||
.description("convert mode")
|
||||
.defaultValue(Mode.BOTH)
|
||||
.setter(v -> mode = v);
|
||||
|
||||
@Override
|
||||
public List<OptionDescription> getOptionsDescriptions() {
|
||||
return Arrays.asList(
|
||||
new JadxOptionDescription(
|
||||
MODE_OPT,
|
||||
"convert mode",
|
||||
"both",
|
||||
Arrays.asList("dx", "d8", "both")),
|
||||
JadxOptionDescription.booleanOption(
|
||||
D8_DESUGAR_OPT,
|
||||
"use desugar in d8",
|
||||
false));
|
||||
boolOption(PLUGIN_ID + ".d8-desugar")
|
||||
.description("use desugar in d8")
|
||||
.defaultValue(false)
|
||||
.setter(v -> d8Desugar = v);
|
||||
}
|
||||
|
||||
public Mode getMode() {
|
||||
@@ -48,8 +33,4 @@ public class JavaConvertOptions extends BaseOptionsParser {
|
||||
public boolean isD8Desugar() {
|
||||
return d8Desugar;
|
||||
}
|
||||
|
||||
public String getOptionsHash() {
|
||||
return FileUtils.md5Sum(mode + ":" + d8Desugar);
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -32,7 +32,6 @@ public class JavaConvertPlugin implements JadxPlugin, JadxCodeInput {
|
||||
public void init(JadxPluginContext context) {
|
||||
context.registerOptions(options);
|
||||
context.addCodeInput(this);
|
||||
context.registerInputsHashSupplier(options::getOptionsHash);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+36
-23
@@ -1,11 +1,9 @@
|
||||
package jadx.plugins.kotlin.metadata
|
||||
|
||||
import jadx.api.plugins.options.OptionDescription
|
||||
import jadx.api.plugins.options.impl.BaseOptionsParser
|
||||
import jadx.api.plugins.options.impl.JadxOptionDescription.booleanOption
|
||||
import jadx.api.plugins.options.impl.BasePluginOptionsBuilder
|
||||
import jadx.plugins.kotlin.metadata.KotlinMetadataPlugin.Companion.PLUGIN_ID
|
||||
|
||||
class KotlinMetadataOptions : BaseOptionsParser() {
|
||||
class KotlinMetadataOptions : BasePluginOptionsBuilder() {
|
||||
var isClassAlias: Boolean = true
|
||||
private set
|
||||
var isMethodArgs: Boolean = true
|
||||
@@ -21,26 +19,41 @@ class KotlinMetadataOptions : BaseOptionsParser() {
|
||||
var isGetters: Boolean = true
|
||||
private set
|
||||
|
||||
override fun parseOptions() {
|
||||
isClassAlias = getBooleanOption(CLASS_ALIAS_OPT, true)
|
||||
isMethodArgs = getBooleanOption(METHOD_ARGS_OPT, true)
|
||||
isFields = getBooleanOption(FIELDS_OPT, true)
|
||||
isCompanion = getBooleanOption(COMPANION_OPT, true)
|
||||
isDataClass = getBooleanOption(DATA_CLASS_OPT, true)
|
||||
isToString = getBooleanOption(TO_STRING_OPT, true)
|
||||
isGetters = getBooleanOption(GETTERS_OPT, true)
|
||||
}
|
||||
override fun registerOptions() {
|
||||
boolOption(CLASS_ALIAS_OPT)
|
||||
.description("rename class alias")
|
||||
.defaultValue(true)
|
||||
.setter { isClassAlias = it }
|
||||
|
||||
override fun getOptionsDescriptions(): List<OptionDescription> {
|
||||
return listOf(
|
||||
booleanOption(CLASS_ALIAS_OPT, "rename class alias", true),
|
||||
booleanOption(METHOD_ARGS_OPT, "rename function arguments", true),
|
||||
booleanOption(FIELDS_OPT, "rename fields", true),
|
||||
booleanOption(COMPANION_OPT, "rename companion object", true),
|
||||
booleanOption(DATA_CLASS_OPT, "add data class modifier", true),
|
||||
booleanOption(TO_STRING_OPT, "rename fields using toString", true),
|
||||
booleanOption(GETTERS_OPT, "rename simple getters to field names", true),
|
||||
)
|
||||
boolOption(METHOD_ARGS_OPT)
|
||||
.description("rename function arguments")
|
||||
.defaultValue(true)
|
||||
.setter { isMethodArgs = it }
|
||||
|
||||
boolOption(FIELDS_OPT)
|
||||
.description("rename fields")
|
||||
.defaultValue(true)
|
||||
.setter { isFields = it }
|
||||
|
||||
boolOption(COMPANION_OPT)
|
||||
.description("rename companion object")
|
||||
.defaultValue(true)
|
||||
.setter { isCompanion = it }
|
||||
|
||||
boolOption(DATA_CLASS_OPT)
|
||||
.description("add data class modifier")
|
||||
.defaultValue(true)
|
||||
.setter { isDataClass = it }
|
||||
|
||||
boolOption(TO_STRING_OPT)
|
||||
.description("rename fields using toString")
|
||||
.defaultValue(true)
|
||||
.setter { isToString = it }
|
||||
|
||||
boolOption(GETTERS_OPT)
|
||||
.description("rename simple getters to field names")
|
||||
.defaultValue(true)
|
||||
.setter { isGetters = it }
|
||||
}
|
||||
|
||||
fun isPreparePassNeeded(): Boolean {
|
||||
|
||||
+18
-28
@@ -1,22 +1,18 @@
|
||||
package jadx.plugins.mappings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.fabricmc.mappingio.format.MappingFormat;
|
||||
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.api.plugins.options.OptionFlag;
|
||||
import jadx.api.plugins.options.impl.BaseOptionsParser;
|
||||
import jadx.api.plugins.options.impl.JadxOptionDescription;
|
||||
import jadx.api.plugins.options.impl.BasePluginOptionsBuilder;
|
||||
import jadx.core.utils.ListUtils;
|
||||
|
||||
import static jadx.plugins.mappings.RenameMappingsPlugin.PLUGIN_ID;
|
||||
|
||||
public class RenameMappingsOptions extends BaseOptionsParser {
|
||||
public class RenameMappingsOptions extends BasePluginOptionsBuilder {
|
||||
|
||||
public static final String INVERT_OPT = PLUGIN_ID + ".invert";
|
||||
public static final String FORMAT_OPT = PLUGIN_ID + ".format";
|
||||
@@ -29,18 +25,21 @@ public class RenameMappingsOptions extends BaseOptionsParser {
|
||||
private @Nullable MappingFormat format = null;
|
||||
|
||||
@Override
|
||||
public void parseOptions() {
|
||||
format = getOption(FORMAT_OPT, RenameMappingsOptions::parseMappingFormat, null);
|
||||
invert = getBooleanOption(INVERT_OPT, false);
|
||||
}
|
||||
public void registerOptions() {
|
||||
option(FORMAT_OPT, MappingFormat.class)
|
||||
.description("mapping format")
|
||||
.parser(RenameMappingsOptions::parseMappingFormat)
|
||||
.formatter(v -> v == null ? "AUTO" : v.name())
|
||||
.values(ListUtils.concat(null, MappingFormat.values()))
|
||||
.defaultValue(null)
|
||||
.flags(OptionFlag.PER_PROJECT, OptionFlag.DISABLE_IN_GUI)
|
||||
.setter(v -> format = v);
|
||||
|
||||
@Override
|
||||
public List<OptionDescription> getOptionsDescriptions() {
|
||||
return Arrays.asList(
|
||||
new JadxOptionDescription(FORMAT_OPT, "mapping format", "auto", getMappingFormats())
|
||||
.withFlags(OptionFlag.PER_PROJECT, OptionFlag.DISABLE_IN_GUI),
|
||||
JadxOptionDescription.booleanOption(INVERT_OPT, "invert mapping", false)
|
||||
.withFlag(OptionFlag.PER_PROJECT));
|
||||
boolOption(INVERT_OPT)
|
||||
.description("invert mapping on load")
|
||||
.defaultValue(false)
|
||||
.flags(OptionFlag.PER_PROJECT)
|
||||
.setter(v -> invert = v);
|
||||
}
|
||||
|
||||
private static MappingFormat parseMappingFormat(String name) {
|
||||
@@ -51,16 +50,7 @@ public class RenameMappingsOptions extends BaseOptionsParser {
|
||||
return MappingFormat.valueOf(upName);
|
||||
}
|
||||
|
||||
private static List<String> getMappingFormats() {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("auto");
|
||||
for (MappingFormat value : MappingFormat.values()) {
|
||||
list.add(value.name());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public MappingFormat getFormat() {
|
||||
public @Nullable MappingFormat getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user