feat(cli): implement config file load and save (#1731)
This commit is contained in:
@@ -172,7 +172,16 @@ options:
|
||||
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
|
||||
-v, --verbose - verbose output (set --log-level to DEBUG)
|
||||
-q, --quiet - turn off output (set --log-level to QUIET)
|
||||
--disable-plugins - comma separated list of plugin ids to disable, default:
|
||||
--disable-plugins - comma separated list of plugin ids to disable
|
||||
--config <config-ref> - load configuration from file, <config-ref> can be:
|
||||
path to '.json' file
|
||||
short name - uses file with this name from config directory
|
||||
'none' - to disable config loading
|
||||
--save-config <config-ref> - save current options into configuration file and exit, <config-ref> can be:
|
||||
empty - for default config
|
||||
path to '.json' file
|
||||
short name - file will be saved in config directory
|
||||
--print-files - print files and directories used by jadx (config, cache, temp)
|
||||
--version - print jadx version
|
||||
-h, --help - print this help
|
||||
|
||||
@@ -193,7 +202,7 @@ Plugin options (-P<name>=<value>):
|
||||
kotlin-smap: Use kotlin.SourceDebugExtension annotation for rename class alias
|
||||
- kotlin-smap.class-alias-source-dbg - rename class alias from SourceDebugExtension, values: [yes, no], default: no
|
||||
rename-mappings: various mappings support
|
||||
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, SRG_FILE, XSRG_FILE, JAM_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, PROGUARD_FILE, INTELLIJ_MIGRATION_MAP_FILE, RECAF_SIMPLE_FILE, JOBF_FILE], default: AUTO
|
||||
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, PROGUARD_FILE, SRG_FILE, XSRG_FILE, JAM_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, INTELLIJ_MIGRATION_MAP_FILE, RECAF_SIMPLE_FILE, JOBF_FILE], default: AUTO
|
||||
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
|
||||
smali-input: Load .smali files
|
||||
- smali-input.api-level - Android API level, default: 27
|
||||
|
||||
@@ -27,6 +27,7 @@ dependencies {
|
||||
|
||||
implementation("org.jcommander:jcommander:2.0")
|
||||
implementation("ch.qos.logback:logback-classic:1.5.21")
|
||||
implementation("com.google.code.gson:gson:2.13.2")
|
||||
}
|
||||
|
||||
application {
|
||||
|
||||
@@ -41,12 +41,12 @@ public class JCommanderWrapper {
|
||||
|
||||
public boolean parse(String[] args) {
|
||||
try {
|
||||
jc.parse(args);
|
||||
String[] fixedArgs = fixArgsForEmptySaveConfig(args);
|
||||
jc.parse(fixedArgs);
|
||||
applyFiles(argsObj);
|
||||
return true;
|
||||
} catch (ParameterException e) {
|
||||
System.err.println("Arguments parse error: " + e.getMessage());
|
||||
printUsage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -96,6 +96,41 @@ public class JCommanderWrapper {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround to allow empty value (i.e. zero arity) for '--save-config' option
|
||||
* Insert empty string arg if another option start right after this one, or it is a last one.
|
||||
*/
|
||||
private String[] fixArgsForEmptySaveConfig(String[] args) {
|
||||
int len = args.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
String arg = args[i];
|
||||
if (arg.equals("--save-config")) {
|
||||
int next = i + 1;
|
||||
if (next == len) {
|
||||
return insertEmptyArg(args, next, true);
|
||||
}
|
||||
if (next < len) {
|
||||
String nextArg = args[next];
|
||||
if (nextArg.startsWith("-")) {
|
||||
return insertEmptyArg(args, next, false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private static String[] insertEmptyArg(String[] args, int i, boolean add) {
|
||||
List<String> strings = new ArrayList<>(Arrays.asList(args));
|
||||
if (add) {
|
||||
strings.add("");
|
||||
} else {
|
||||
strings.add(i, "");
|
||||
}
|
||||
return strings.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public void printUsage() {
|
||||
LogHelper.setLogLevel(LogHelper.LogLevelEnum.ERROR); // mute logger while printing help
|
||||
|
||||
@@ -182,7 +217,9 @@ public class JCommanderWrapper {
|
||||
}
|
||||
if (addDefaults) {
|
||||
String defaultValue = getDefaultValue(args, f);
|
||||
if (defaultValue != null && !description.contains("(default)")) {
|
||||
if (defaultValue != null
|
||||
&& !defaultValue.isEmpty()
|
||||
&& !description.contains("(default)")) {
|
||||
opt.append(", default: ").append(defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import jadx.api.impl.NoOpCodeCache;
|
||||
import jadx.api.impl.SimpleCodeWriter;
|
||||
import jadx.api.usage.impl.EmptyUsageInfoCache;
|
||||
import jadx.cli.LogHelper.LogLevelEnum;
|
||||
import jadx.cli.config.JadxConfigAdapter;
|
||||
import jadx.cli.plugins.JadxFilesGetter;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
import jadx.plugins.tools.JadxExternalPluginsLoader;
|
||||
@@ -35,15 +36,17 @@ public class JadxCLI {
|
||||
|
||||
public static int execute(String[] args, @Nullable Consumer<JadxArgs> argsMod) {
|
||||
try {
|
||||
JadxCLIArgs cliArgs = new JadxCLIArgs();
|
||||
if (cliArgs.processArgs(args)) {
|
||||
JadxArgs jadxArgs = buildArgs(cliArgs);
|
||||
if (argsMod != null) {
|
||||
argsMod.accept(jadxArgs);
|
||||
}
|
||||
return runSave(jadxArgs, cliArgs);
|
||||
JadxCLIArgs cliArgs = JadxCLIArgs.processArgs(args,
|
||||
new JadxCLIArgs(),
|
||||
new JadxConfigAdapter<>(JadxCLIArgs.class, "cli"));
|
||||
if (cliArgs == null) {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
JadxArgs jadxArgs = buildArgs(cliArgs);
|
||||
if (argsMod != null) {
|
||||
argsMod.accept(jadxArgs);
|
||||
}
|
||||
return runSave(jadxArgs, cliArgs);
|
||||
} catch (JadxArgsValidateException e) {
|
||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||
return 1;
|
||||
@@ -54,8 +57,6 @@ public class JadxCLI {
|
||||
}
|
||||
|
||||
private static JadxArgs buildArgs(JadxCLIArgs cliArgs) {
|
||||
LogHelper.initLogLevel(cliArgs);
|
||||
LogHelper.applyLogLevels();
|
||||
JadxArgs jadxArgs = cliArgs.toJadxArgs();
|
||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||
jadxArgs.setUsageInfoCache(new EmptyUsageInfoCache());
|
||||
|
||||
@@ -31,22 +31,32 @@ import jadx.api.args.IntegerFormat;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.api.args.UseSourceNameAsClassNameAlias;
|
||||
import jadx.api.args.UserRenamesMappingsMode;
|
||||
import jadx.cli.config.IJadxConfig;
|
||||
import jadx.cli.config.JadxConfigAdapter;
|
||||
import jadx.cli.config.JadxConfigExclude;
|
||||
import jadx.commons.app.JadxCommonFiles;
|
||||
import jadx.commons.app.JadxTempFiles;
|
||||
import jadx.core.deobf.conditions.DeobfWhitelist;
|
||||
import jadx.core.export.ExportGradleType;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxCLIArgs {
|
||||
public class JadxCLIArgs implements IJadxConfig {
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .apkm, .jadx.kts)")
|
||||
protected List<String> files = Collections.emptyList();
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||
protected String outDir;
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(names = { "-ds", "--output-dir-src" }, description = "output directory for sources")
|
||||
protected String outDirSrc;
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(names = { "-dr", "--output-dir-res" }, description = "output directory for resources")
|
||||
protected String outDirRes;
|
||||
|
||||
@@ -59,9 +69,11 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
||||
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(names = { "--single-class" }, description = "decompile a single class, full name, raw or alias")
|
||||
protected String singleClass = null;
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(names = { "--single-class-output" }, description = "file or dir for write if decompile a single class")
|
||||
protected String singleClassOutput = null;
|
||||
|
||||
@@ -166,6 +178,7 @@ public class JadxCLIArgs {
|
||||
)
|
||||
protected String deobfuscationWhitelistStr = DeobfWhitelist.DEFAULT_STR;
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(
|
||||
names = { "--deobf-cfg-file" },
|
||||
description = "deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format),"
|
||||
@@ -241,7 +254,7 @@ public class JadxCLIArgs {
|
||||
+ "\n 'printable' - remove non-printable chars from identifiers,"
|
||||
+ "\nor single 'none' - to disable all renames"
|
||||
+ "\nor single 'all' - to enable all (default)",
|
||||
converter = RenameConverter.class
|
||||
listConverter = RenameConverter.class
|
||||
)
|
||||
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
|
||||
|
||||
@@ -287,27 +300,106 @@ public class JadxCLIArgs {
|
||||
)
|
||||
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
|
||||
protected boolean verbose = false;
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
|
||||
protected boolean quiet = false;
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(names = { "--disable-plugins" }, description = "comma separated list of plugin ids to disable")
|
||||
protected String disablePlugins = "";
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(
|
||||
names = { "--config" },
|
||||
defaultValueDescription = "<config-ref>",
|
||||
description = "load configuration from file, <config-ref> can be:"
|
||||
+ "\n path to '.json' file"
|
||||
+ "\n short name - uses file with this name from config directory"
|
||||
+ "\n 'none' - to disable config loading"
|
||||
)
|
||||
protected String config = "";
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(
|
||||
names = { "--save-config" },
|
||||
defaultValueDescription = "<config-ref>",
|
||||
description = "save current options into configuration file and exit, <config-ref> can be:"
|
||||
+ "\n empty - for default config"
|
||||
+ "\n path to '.json' file"
|
||||
+ "\n short name - file will be saved in config directory"
|
||||
)
|
||||
protected String saveConfig = null;
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(names = { "--print-files" }, description = "print files and directories used by jadx (config, cache, temp)")
|
||||
protected boolean printFiles = false;
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(names = { "--version" }, description = "print jadx version")
|
||||
protected boolean printVersion = false;
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
||||
protected boolean printHelp = false;
|
||||
|
||||
@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
|
||||
protected Map<String, String> pluginOptions = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Obsolete method without config support,
|
||||
* prefer {@link #processArgs(String[], JadxCLIArgs, JadxConfigAdapter)}
|
||||
*/
|
||||
public boolean processArgs(String[] args) {
|
||||
JCommanderWrapper jcw = new JCommanderWrapper(this);
|
||||
return jcw.parse(args) && process(jcw);
|
||||
return processArgs(args, this, null) != null;
|
||||
}
|
||||
|
||||
public static <T extends JadxCLIArgs> @Nullable T processArgs(
|
||||
String[] args, T argsObj, @Nullable JadxConfigAdapter<T> configAdapter) {
|
||||
JCommanderWrapper jcw = new JCommanderWrapper(argsObj);
|
||||
if (!jcw.parse(args)) {
|
||||
return null;
|
||||
}
|
||||
applyArgs(argsObj);
|
||||
|
||||
// process commands and early exit flags
|
||||
if (!argsObj.process(jcw)) {
|
||||
return null;
|
||||
}
|
||||
if (configAdapter != null) {
|
||||
if (argsObj.printFiles) {
|
||||
printFilesAndDirs(configAdapter.getDefaultConfigFileName());
|
||||
return null;
|
||||
}
|
||||
if (!argsObj.config.equalsIgnoreCase("none")) {
|
||||
// load config file and merge with command line args
|
||||
configAdapter.useConfigRef(argsObj.config);
|
||||
T configObj = configAdapter.load();
|
||||
if (configObj != null) {
|
||||
jcw.overrideProvided(configObj);
|
||||
argsObj = configObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
// verify result object
|
||||
argsObj.verify();
|
||||
applyArgs(argsObj);
|
||||
|
||||
// save config if requested
|
||||
if (argsObj.saveConfig != null) {
|
||||
saveConfig(argsObj, configAdapter);
|
||||
return null;
|
||||
}
|
||||
return argsObj;
|
||||
}
|
||||
|
||||
private static <T extends JadxCLIArgs> void applyArgs(T argsObj) {
|
||||
// apply log levels
|
||||
LogHelper.initLogLevel(argsObj);
|
||||
LogHelper.applyLogLevels();
|
||||
}
|
||||
|
||||
public boolean process(JCommanderWrapper jcw) {
|
||||
@@ -322,9 +414,7 @@ public class JadxCLIArgs {
|
||||
System.out.println(JadxDecompiler.getVersion());
|
||||
return false;
|
||||
}
|
||||
if (threadsCount <= 0) {
|
||||
throw new JadxArgsValidateException("Threads count must be positive, got: " + threadsCount);
|
||||
}
|
||||
// unknown options added to 'files', run checks
|
||||
for (String fileName : files) {
|
||||
if (fileName.startsWith("-")) {
|
||||
throw new JadxArgsValidateException("Unknown option: " + fileName);
|
||||
@@ -333,6 +423,29 @@ public class JadxCLIArgs {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void printFilesAndDirs(String defaultConfigFileName) {
|
||||
System.out.println("Files and directories used by jadx:");
|
||||
System.out.println(" - default config file: " + JadxCommonFiles.getConfigDir().resolve(defaultConfigFileName).toAbsolutePath());
|
||||
System.out.println(" - config directory: " + JadxCommonFiles.getConfigDir().toAbsolutePath());
|
||||
System.out.println(" - cache directory: " + JadxCommonFiles.getCacheDir().toAbsolutePath());
|
||||
System.out.println(" - temp directory: " + JadxTempFiles.getTempRootDir().getParent().toAbsolutePath());
|
||||
}
|
||||
|
||||
public void verify() {
|
||||
if (threadsCount <= 0) {
|
||||
throw new JadxArgsValidateException("Threads count must be positive, got: " + threadsCount);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends JadxCLIArgs> void saveConfig(T argsObj, @Nullable JadxConfigAdapter<T> configAdapter) {
|
||||
if (configAdapter == null) {
|
||||
throw new JadxRuntimeException("Config adapter set to null, can't save config");
|
||||
}
|
||||
configAdapter.useConfigRef(argsObj.saveConfig);
|
||||
configAdapter.save(argsObj);
|
||||
System.out.println("Config saved to " + configAdapter.getConfigPath().toAbsolutePath());
|
||||
}
|
||||
|
||||
public JadxArgs toJadxArgs() {
|
||||
JadxArgs args = new JadxArgs();
|
||||
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
|
||||
@@ -383,7 +496,7 @@ public class JadxCLIArgs {
|
||||
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
|
||||
args.setExtractFinally(extractFinally);
|
||||
args.setRestoreSwitchOverString(restoreSwitchOverString);
|
||||
args.setRenameFlags(renameFlags);
|
||||
args.setRenameFlags(buildEnumSetForRenameFlags());
|
||||
args.setFsCaseSensitive(fsCaseSensitive);
|
||||
args.setCommentsLevel(commentsLevel);
|
||||
args.setIntegerFormat(integerFormat);
|
||||
@@ -394,6 +507,12 @@ public class JadxCLIArgs {
|
||||
return args;
|
||||
}
|
||||
|
||||
private EnumSet<RenameEnum> buildEnumSetForRenameFlags() {
|
||||
EnumSet<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
|
||||
set.addAll(renameFlags);
|
||||
return set;
|
||||
}
|
||||
|
||||
public List<String> getFiles() {
|
||||
return files;
|
||||
}
|
||||
@@ -426,14 +545,26 @@ public class JadxCLIArgs {
|
||||
return skipResources;
|
||||
}
|
||||
|
||||
public void setSkipResources(boolean skipResources) {
|
||||
this.skipResources = skipResources;
|
||||
}
|
||||
|
||||
public boolean isSkipSources() {
|
||||
return skipSources;
|
||||
}
|
||||
|
||||
public void setSkipSources(boolean skipSources) {
|
||||
this.skipSources = skipSources;
|
||||
}
|
||||
|
||||
public int getThreadsCount() {
|
||||
return threadsCount;
|
||||
}
|
||||
|
||||
public void setThreadsCount(int threadsCount) {
|
||||
this.threadsCount = threadsCount;
|
||||
}
|
||||
|
||||
public boolean isFallbackMode() {
|
||||
return fallbackMode;
|
||||
}
|
||||
@@ -442,82 +573,170 @@ public class JadxCLIArgs {
|
||||
return useDx;
|
||||
}
|
||||
|
||||
public void setUseDx(boolean useDx) {
|
||||
this.useDx = useDx;
|
||||
}
|
||||
|
||||
public DecompilationMode getDecompilationMode() {
|
||||
return decompilationMode;
|
||||
}
|
||||
|
||||
public void setDecompilationMode(DecompilationMode decompilationMode) {
|
||||
this.decompilationMode = decompilationMode;
|
||||
}
|
||||
|
||||
public boolean isShowInconsistentCode() {
|
||||
return showInconsistentCode;
|
||||
}
|
||||
|
||||
public void setShowInconsistentCode(boolean showInconsistentCode) {
|
||||
this.showInconsistentCode = showInconsistentCode;
|
||||
}
|
||||
|
||||
public boolean isUseImports() {
|
||||
return useImports;
|
||||
}
|
||||
|
||||
public void setUseImports(boolean useImports) {
|
||||
this.useImports = useImports;
|
||||
}
|
||||
|
||||
public boolean isDebugInfo() {
|
||||
return debugInfo;
|
||||
}
|
||||
|
||||
public void setDebugInfo(boolean debugInfo) {
|
||||
this.debugInfo = debugInfo;
|
||||
}
|
||||
|
||||
public boolean isAddDebugLines() {
|
||||
return addDebugLines;
|
||||
}
|
||||
|
||||
public void setAddDebugLines(boolean addDebugLines) {
|
||||
this.addDebugLines = addDebugLines;
|
||||
}
|
||||
|
||||
public boolean isInlineAnonymousClasses() {
|
||||
return inlineAnonymousClasses;
|
||||
}
|
||||
|
||||
public void setInlineAnonymousClasses(boolean inlineAnonymousClasses) {
|
||||
this.inlineAnonymousClasses = inlineAnonymousClasses;
|
||||
}
|
||||
|
||||
public boolean isInlineMethods() {
|
||||
return inlineMethods;
|
||||
}
|
||||
|
||||
public void setInlineMethods(boolean inlineMethods) {
|
||||
this.inlineMethods = inlineMethods;
|
||||
}
|
||||
|
||||
public boolean isMoveInnerClasses() {
|
||||
return moveInnerClasses;
|
||||
}
|
||||
|
||||
public void setMoveInnerClasses(boolean moveInnerClasses) {
|
||||
this.moveInnerClasses = moveInnerClasses;
|
||||
}
|
||||
|
||||
public boolean isAllowInlineKotlinLambda() {
|
||||
return allowInlineKotlinLambda;
|
||||
}
|
||||
|
||||
public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
|
||||
this.allowInlineKotlinLambda = allowInlineKotlinLambda;
|
||||
}
|
||||
|
||||
public boolean isExtractFinally() {
|
||||
return extractFinally;
|
||||
}
|
||||
|
||||
public void setExtractFinally(boolean extractFinally) {
|
||||
this.extractFinally = extractFinally;
|
||||
}
|
||||
|
||||
public boolean isRestoreSwitchOverString() {
|
||||
return restoreSwitchOverString;
|
||||
}
|
||||
|
||||
public void setRestoreSwitchOverString(boolean restoreSwitchOverString) {
|
||||
this.restoreSwitchOverString = restoreSwitchOverString;
|
||||
}
|
||||
|
||||
public Path getUserRenamesMappingsPath() {
|
||||
return userRenamesMappingsPath;
|
||||
}
|
||||
|
||||
public void setUserRenamesMappingsPath(Path userRenamesMappingsPath) {
|
||||
this.userRenamesMappingsPath = userRenamesMappingsPath;
|
||||
}
|
||||
|
||||
public UserRenamesMappingsMode getUserRenamesMappingsMode() {
|
||||
return userRenamesMappingsMode;
|
||||
}
|
||||
|
||||
public void setUserRenamesMappingsMode(UserRenamesMappingsMode userRenamesMappingsMode) {
|
||||
this.userRenamesMappingsMode = userRenamesMappingsMode;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationOn() {
|
||||
return deobfuscationOn;
|
||||
}
|
||||
|
||||
public void setDeobfuscationOn(boolean deobfuscationOn) {
|
||||
this.deobfuscationOn = deobfuscationOn;
|
||||
}
|
||||
|
||||
public int getDeobfuscationMinLength() {
|
||||
return deobfuscationMinLength;
|
||||
}
|
||||
|
||||
public void setDeobfuscationMinLength(int deobfuscationMinLength) {
|
||||
this.deobfuscationMinLength = deobfuscationMinLength;
|
||||
}
|
||||
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
public void setDeobfuscationMaxLength(int deobfuscationMaxLength) {
|
||||
this.deobfuscationMaxLength = deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
public String getDeobfuscationWhitelistStr() {
|
||||
return deobfuscationWhitelistStr;
|
||||
}
|
||||
|
||||
public void setDeobfuscationWhitelistStr(String deobfuscationWhitelistStr) {
|
||||
this.deobfuscationWhitelistStr = deobfuscationWhitelistStr;
|
||||
}
|
||||
|
||||
public String getGeneratedRenamesMappingFile() {
|
||||
return generatedRenamesMappingFile;
|
||||
}
|
||||
|
||||
public void setGeneratedRenamesMappingFile(String generatedRenamesMappingFile) {
|
||||
this.generatedRenamesMappingFile = generatedRenamesMappingFile;
|
||||
}
|
||||
|
||||
public GeneratedRenamesMappingFileMode getGeneratedRenamesMappingFileMode() {
|
||||
return generatedRenamesMappingFileMode;
|
||||
}
|
||||
|
||||
public void setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode) {
|
||||
this.generatedRenamesMappingFileMode = generatedRenamesMappingFileMode;
|
||||
}
|
||||
|
||||
public int getSourceNameRepeatLimit() {
|
||||
return sourceNameRepeatLimit;
|
||||
}
|
||||
|
||||
public void setSourceNameRepeatLimit(int sourceNameRepeatLimit) {
|
||||
this.sourceNameRepeatLimit = sourceNameRepeatLimit;
|
||||
}
|
||||
|
||||
public UseSourceNameAsClassNameAlias getUseSourceNameAsClassNameAlias() {
|
||||
if (useSourceNameAsClassNameAlias != null) {
|
||||
return useSourceNameAsClassNameAlias;
|
||||
@@ -529,8 +748,8 @@ public class JadxCLIArgs {
|
||||
}
|
||||
}
|
||||
|
||||
public int getSourceNameRepeatLimit() {
|
||||
return sourceNameRepeatLimit;
|
||||
public void setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias useSourceNameAsClassNameAlias) {
|
||||
this.useSourceNameAsClassNameAlias = useSourceNameAsClassNameAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -541,50 +760,98 @@ public class JadxCLIArgs {
|
||||
return getUseSourceNameAsClassNameAlias().toBoolean();
|
||||
}
|
||||
|
||||
public void setDeobfuscationUseSourceNameAsAlias(Boolean deobfuscationUseSourceNameAsAlias) {
|
||||
this.deobfuscationUseSourceNameAsAlias = deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
public ResourceNameSource getResourceNameSource() {
|
||||
return resourceNameSource;
|
||||
}
|
||||
|
||||
public void setResourceNameSource(ResourceNameSource resourceNameSource) {
|
||||
this.resourceNameSource = resourceNameSource;
|
||||
}
|
||||
|
||||
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||
return useKotlinMethodsForVarNames;
|
||||
}
|
||||
|
||||
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
|
||||
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
||||
}
|
||||
|
||||
public IntegerFormat getIntegerFormat() {
|
||||
return integerFormat;
|
||||
}
|
||||
|
||||
public void setIntegerFormat(IntegerFormat integerFormat) {
|
||||
this.integerFormat = integerFormat;
|
||||
}
|
||||
|
||||
public int getTypeUpdatesLimitCount() {
|
||||
return typeUpdatesLimitCount;
|
||||
}
|
||||
|
||||
public void setTypeUpdatesLimitCount(int typeUpdatesLimitCount) {
|
||||
this.typeUpdatesLimitCount = typeUpdatesLimitCount;
|
||||
}
|
||||
|
||||
public boolean isEscapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
public void setEscapeUnicode(boolean escapeUnicode) {
|
||||
this.escapeUnicode = escapeUnicode;
|
||||
}
|
||||
|
||||
public boolean isCfgOutput() {
|
||||
return cfgOutput;
|
||||
}
|
||||
|
||||
public void setCfgOutput(boolean cfgOutput) {
|
||||
this.cfgOutput = cfgOutput;
|
||||
}
|
||||
|
||||
public boolean isRawCfgOutput() {
|
||||
return rawCfgOutput;
|
||||
}
|
||||
|
||||
public void setRawCfgOutput(boolean rawCfgOutput) {
|
||||
this.rawCfgOutput = rawCfgOutput;
|
||||
}
|
||||
|
||||
public boolean isReplaceConsts() {
|
||||
return replaceConsts;
|
||||
}
|
||||
|
||||
public void setReplaceConsts(boolean replaceConsts) {
|
||||
this.replaceConsts = replaceConsts;
|
||||
}
|
||||
|
||||
public boolean isRespectBytecodeAccessModifiers() {
|
||||
return respectBytecodeAccessModifiers;
|
||||
}
|
||||
|
||||
public void setRespectBytecodeAccessModifiers(boolean respectBytecodeAccessModifiers) {
|
||||
this.respectBytecodeAccessModifiers = respectBytecodeAccessModifiers;
|
||||
}
|
||||
|
||||
public boolean isExportAsGradleProject() {
|
||||
return exportAsGradleProject;
|
||||
}
|
||||
|
||||
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
||||
this.exportAsGradleProject = exportAsGradleProject;
|
||||
}
|
||||
|
||||
public boolean isSkipXmlPrettyPrint() {
|
||||
return skipXmlPrettyPrint;
|
||||
}
|
||||
|
||||
public void setSkipXmlPrettyPrint(boolean skipXmlPrettyPrint) {
|
||||
this.skipXmlPrettyPrint = skipXmlPrettyPrint;
|
||||
}
|
||||
|
||||
public boolean isRenameCaseSensitive() {
|
||||
return renameFlags.contains(RenameEnum.CASE);
|
||||
}
|
||||
@@ -601,26 +868,70 @@ public class JadxCLIArgs {
|
||||
return fsCaseSensitive;
|
||||
}
|
||||
|
||||
public void setFsCaseSensitive(boolean fsCaseSensitive) {
|
||||
this.fsCaseSensitive = fsCaseSensitive;
|
||||
}
|
||||
|
||||
public boolean isUseHeadersForDetectResourceExtensions() {
|
||||
return useHeadersForDetectResourceExtensions;
|
||||
}
|
||||
|
||||
public void setUseHeadersForDetectResourceExtensions(boolean useHeadersForDetectResourceExtensions) {
|
||||
this.useHeadersForDetectResourceExtensions = useHeadersForDetectResourceExtensions;
|
||||
}
|
||||
|
||||
public CommentsLevel getCommentsLevel() {
|
||||
return commentsLevel;
|
||||
}
|
||||
|
||||
public void setCommentsLevel(CommentsLevel commentsLevel) {
|
||||
this.commentsLevel = commentsLevel;
|
||||
}
|
||||
|
||||
public LogHelper.LogLevelEnum getLogLevel() {
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
public void setLogLevel(LogHelper.LogLevelEnum logLevel) {
|
||||
this.logLevel = logLevel;
|
||||
}
|
||||
|
||||
public Map<String, String> getPluginOptions() {
|
||||
return pluginOptions;
|
||||
}
|
||||
|
||||
public void setPluginOptions(Map<String, String> pluginOptions) {
|
||||
this.pluginOptions = pluginOptions;
|
||||
}
|
||||
|
||||
public String getDisablePlugins() {
|
||||
return disablePlugins;
|
||||
}
|
||||
|
||||
public void setDisablePlugins(String disablePlugins) {
|
||||
this.disablePlugins = disablePlugins;
|
||||
}
|
||||
|
||||
public void setExportGradleType(@Nullable ExportGradleType exportGradleType) {
|
||||
this.exportGradleType = exportGradleType;
|
||||
}
|
||||
|
||||
public void setOutputFormat(String outputFormat) {
|
||||
this.outputFormat = outputFormat;
|
||||
}
|
||||
|
||||
public Set<RenameEnum> getRenameFlags() {
|
||||
return renameFlags;
|
||||
}
|
||||
|
||||
public void setRenameFlags(Set<RenameEnum> renameFlags) {
|
||||
this.renameFlags = renameFlags;
|
||||
}
|
||||
|
||||
public String getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
||||
private final String paramName;
|
||||
|
||||
|
||||
@@ -43,10 +43,9 @@ public class LogHelper {
|
||||
return null;
|
||||
}
|
||||
if (args.quiet) {
|
||||
return LogLevelEnum.QUIET;
|
||||
}
|
||||
if (args.verbose) {
|
||||
return LogLevelEnum.DEBUG;
|
||||
args.logLevel = LogLevelEnum.QUIET;
|
||||
} else if (args.verbose) {
|
||||
args.logLevel = LogLevelEnum.DEBUG;
|
||||
}
|
||||
return args.logLevel;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package jadx.cli.config;
|
||||
|
||||
/**
|
||||
* Marker interface for jadx config objects
|
||||
*/
|
||||
public interface IJadxConfig {
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package jadx.cli.config;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.FieldAttributes;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import jadx.commons.app.JadxCommonFiles;
|
||||
import jadx.core.utils.GsonUtils;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static java.nio.file.StandardOpenOption.CREATE;
|
||||
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
|
||||
import static java.nio.file.StandardOpenOption.WRITE;
|
||||
|
||||
public class JadxConfigAdapter<T extends IJadxConfig> {
|
||||
private static final ExclusionStrategy GSON_EXCLUSION_STRATEGY = new ExclusionStrategy() {
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes f) {
|
||||
return f.getAnnotation(JadxConfigExclude.class) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(Class<?> clazz) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private final Class<T> configCls;
|
||||
private final String defaultConfigFileName;
|
||||
private final Gson gson;
|
||||
|
||||
private Path configPath;
|
||||
|
||||
public JadxConfigAdapter(Class<T> configCls, String defaultConfigName) {
|
||||
this(configCls, defaultConfigName, gsonBuilder -> {
|
||||
});
|
||||
}
|
||||
|
||||
public JadxConfigAdapter(Class<T> configCls, String defaultConfigName, Consumer<GsonBuilder> applyGsonOptions) {
|
||||
this.configCls = configCls;
|
||||
this.defaultConfigFileName = defaultConfigName + ".json";
|
||||
GsonBuilder gsonBuilder = GsonUtils.defaultGsonBuilder();
|
||||
gsonBuilder.setExclusionStrategies(GSON_EXCLUSION_STRATEGY);
|
||||
applyGsonOptions.accept(gsonBuilder);
|
||||
this.gson = gsonBuilder.create();
|
||||
}
|
||||
|
||||
public void useConfigRef(String configRef) {
|
||||
this.configPath = resolveConfigRef(configRef);
|
||||
}
|
||||
|
||||
public Path getConfigPath() {
|
||||
return configPath;
|
||||
}
|
||||
|
||||
public String getDefaultConfigFileName() {
|
||||
return defaultConfigFileName;
|
||||
}
|
||||
|
||||
public @Nullable T load() {
|
||||
if (!Files.isRegularFile(configPath)) {
|
||||
// file not found
|
||||
return null;
|
||||
}
|
||||
try (JsonReader reader = gson.newJsonReader(Files.newBufferedReader(configPath))) {
|
||||
return gson.fromJson(reader, configCls);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to load config file: " + configPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void save(T configObject) {
|
||||
try (JsonWriter writer = gson.newJsonWriter(
|
||||
Files.newBufferedWriter(configPath, StandardCharsets.UTF_8, WRITE, CREATE, TRUNCATE_EXISTING))) {
|
||||
gson.toJson(configObject, configCls, writer);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to save config file: " + configPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
public String objectToJsonString(T configObject) {
|
||||
return gson.toJson(configObject, configCls);
|
||||
}
|
||||
|
||||
public T jsonStringToObject(String jsonStr) {
|
||||
return gson.fromJson(jsonStr, configCls);
|
||||
}
|
||||
|
||||
private Path resolveConfigRef(String configRef) {
|
||||
if (configRef == null || configRef.isEmpty()) {
|
||||
// use default config file
|
||||
return JadxCommonFiles.getConfigDir().resolve(defaultConfigFileName);
|
||||
}
|
||||
if (configRef.contains("/") || configRef.contains("\\")) {
|
||||
if (!configRef.toLowerCase().endsWith(".json")) {
|
||||
throw new JadxArgsValidateException("Config file extension should be '.json'");
|
||||
}
|
||||
return Path.of(configRef);
|
||||
}
|
||||
// treat as a short name
|
||||
return JadxCommonFiles.getConfigDir().resolve(configRef + ".json");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package jadx.cli.config;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface JadxConfigExclude {
|
||||
}
|
||||
@@ -18,7 +18,9 @@ public class JadxTempFiles {
|
||||
String jadxTmpDir = System.getenv("JADX_TMP_DIR");
|
||||
Path dir;
|
||||
if (jadxTmpDir != null) {
|
||||
dir = Files.createTempDirectory(Paths.get(jadxTmpDir), JADX_TMP_INSTANCE_PREFIX);
|
||||
Path customTmpRootDir = Paths.get(jadxTmpDir);
|
||||
Files.createDirectories(customTmpRootDir);
|
||||
dir = Files.createTempDirectory(customTmpRootDir, JADX_TMP_INSTANCE_PREFIX);
|
||||
} else {
|
||||
dir = Files.createTempDirectory(JADX_TMP_INSTANCE_PREFIX);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import java.lang.reflect.Type;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.InstanceCreator;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.Strictness;
|
||||
|
||||
public class GsonUtils {
|
||||
|
||||
@@ -21,15 +21,11 @@ public class GsonUtils {
|
||||
public static GsonBuilder defaultGsonBuilder() {
|
||||
return new GsonBuilder()
|
||||
.disableJdkUnsafe()
|
||||
.disableInnerClassSerialization()
|
||||
.setStrictness(Strictness.STRICT)
|
||||
.setPrettyPrinting();
|
||||
}
|
||||
|
||||
public static void fillObjectFromJsonString(GsonBuilder builder, Object obj, String jsonStr) {
|
||||
Class<?> type = obj.getClass();
|
||||
Gson gson = builder.registerTypeAdapter(type, (InstanceCreator<?>) t -> obj).create();
|
||||
gson.fromJson(jsonStr, type);
|
||||
}
|
||||
|
||||
public static <T> InterfaceReplace<T> interfaceReplace(Class<T> replaceCls) {
|
||||
return new InterfaceReplace<>(replaceCls);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public class AndroidResourcesUtils {
|
||||
addResourceFields(resCls, resStorage, true);
|
||||
return resCls;
|
||||
}
|
||||
LOG.info("Can't find 'R' class in app package: {}", appPackage);
|
||||
LOG.debug("Can't find 'R' class in app package: {}", appPackage);
|
||||
List<ClassNode> candidates = root.searchClassByShortName("R");
|
||||
if (candidates.size() == 1) {
|
||||
ClassNode resClsCandidate = candidates.get(0);
|
||||
|
||||
@@ -7,14 +7,14 @@ import javax.swing.SwingUtilities;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.cli.JCommanderWrapper;
|
||||
import jadx.cli.LogHelper;
|
||||
import jadx.cli.JadxCLIArgs;
|
||||
import jadx.cli.config.JadxConfigAdapter;
|
||||
import jadx.commons.app.JadxSystemInfo;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.logs.LogCollector;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.settings.JadxSettingsAdapter;
|
||||
import jadx.gui.settings.JadxSettingsData;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.dialog.ExceptionDialog;
|
||||
import jadx.gui.utils.LafManager;
|
||||
@@ -25,20 +25,16 @@ public class JadxGUI {
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
JadxSettings cliArgs = new JadxSettings();
|
||||
JCommanderWrapper jcw = new JCommanderWrapper(cliArgs);
|
||||
if (!jcw.parse(args) || !cliArgs.process(jcw)) {
|
||||
JadxConfigAdapter<JadxSettingsData> configAdapter = JadxSettings.buildConfigAdapter();
|
||||
JadxSettingsData settingsData = JadxCLIArgs.processArgs(args, new JadxSettingsData(), configAdapter);
|
||||
if (settingsData == null) {
|
||||
return;
|
||||
}
|
||||
LogHelper.initLogLevel(cliArgs);
|
||||
LogHelper.applyLogLevels();
|
||||
JadxSettings settings = new JadxSettings(configAdapter);
|
||||
settings.loadSettingsData(settingsData);
|
||||
|
||||
LogCollector.register();
|
||||
printSystemInfo();
|
||||
|
||||
JadxSettings settings = JadxSettingsAdapter.load();
|
||||
// overwrite loaded settings by command line arguments
|
||||
jcw.overrideProvided(settings);
|
||||
|
||||
LafManager.init(settings);
|
||||
NLS.setLocale(settings.getLangLocale());
|
||||
ExceptionDialog.registerUncaughtExceptionHandler();
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
import java.awt.Font;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.gui.utils.FontUtils;
|
||||
|
||||
public class FontLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FontLoader.class);
|
||||
|
||||
private final Font defaultFont;
|
||||
|
||||
private Font font;
|
||||
|
||||
public FontLoader(Font defaultFont) {
|
||||
this.defaultFont = defaultFont;
|
||||
this.font = defaultFont;
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
public void setFont(@Nullable Font font) {
|
||||
this.font = Utils.getOrElse(font, defaultFont);
|
||||
}
|
||||
|
||||
public void load(String fontStr) {
|
||||
if (fontStr == null || fontStr.isEmpty()) {
|
||||
font = defaultFont;
|
||||
} else {
|
||||
try {
|
||||
font = FontUtils.loadByStr(fontStr);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to load font: {}, reset to default", fontStr, e);
|
||||
font = defaultFont;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getFontStr() {
|
||||
return FontUtils.convertToStr(font);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark fields excluded from config export/copy actions.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface JadxConfigExcludeExport {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
|
||||
import jadx.cli.JadxCLIArgs;
|
||||
import jadx.cli.config.JadxConfigExclude;
|
||||
|
||||
public class JadxGUIArgs extends JadxCLIArgs {
|
||||
|
||||
@JadxConfigExclude
|
||||
@Parameter(
|
||||
names = { "-sc", "--select-class" },
|
||||
description = "GUI: Open the selected class and show the decompiled code"
|
||||
)
|
||||
private String cmdSelectClass = null;
|
||||
|
||||
public String getCmdSelectClass() {
|
||||
return cmdSelectClass;
|
||||
}
|
||||
|
||||
public void setCmdSelectClass(String cmdSelectClass) {
|
||||
this.cmdSelectClass = cmdSelectClass;
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.cache.manager.CacheManager;
|
||||
import jadx.gui.settings.data.ProjectData;
|
||||
import jadx.gui.settings.data.SaveOptionEnum;
|
||||
import jadx.gui.settings.data.TabViewState;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.codearea.EditorViewState;
|
||||
@@ -267,7 +268,7 @@ public class JadxProject {
|
||||
|
||||
private void changed() {
|
||||
JadxSettings settings = mainWindow.getSettings();
|
||||
if (settings != null && settings.getSaveOption() == JadxSettings.SAVEOPTION.ALWAYS) {
|
||||
if (settings != null && settings.getSaveOption() == SaveOptionEnum.ALWAYS) {
|
||||
save();
|
||||
} else {
|
||||
saved = false;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,108 +0,0 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.FieldAttributes;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import jadx.core.utils.GsonUtils;
|
||||
import jadx.gui.utils.PathTypeAdapter;
|
||||
import jadx.gui.utils.RectangleTypeAdapter;
|
||||
|
||||
public class JadxSettingsAdapter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxSettingsAdapter.class);
|
||||
|
||||
private static final JadxSettingsStorage STORAGE = new JadxSettingsStorage();
|
||||
|
||||
private static final ExclusionStrategy EXCLUDE_FIELDS = new ExclusionStrategy() {
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes f) {
|
||||
return JadxSettings.SKIP_FIELDS.contains(f.getName())
|
||||
|| f.hasModifier(Modifier.PUBLIC)
|
||||
|| f.hasModifier(Modifier.TRANSIENT)
|
||||
|| f.hasModifier(Modifier.STATIC)
|
||||
|| f.getAnnotation(GsonExclude.class) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(Class<?> clazz) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Gson GSON = makeGsonBuilder().create();
|
||||
|
||||
private JadxSettingsAdapter() {
|
||||
}
|
||||
|
||||
public static JadxSettings load() {
|
||||
try {
|
||||
JadxSettings settings = fromString(STORAGE.load());
|
||||
if (settings == null) {
|
||||
LOG.debug("Created new settings.");
|
||||
settings = JadxSettings.makeDefault();
|
||||
} else {
|
||||
settings.fixOnLoad();
|
||||
}
|
||||
return settings;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error load settings. Settings will reset", e);
|
||||
return new JadxSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public static void store(JadxSettings settings) {
|
||||
try {
|
||||
STORAGE.save(makeString(settings));
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error store settings", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static JadxSettings fromString(String jsonSettings) {
|
||||
if (jsonSettings == null) {
|
||||
return null;
|
||||
}
|
||||
return GSON.fromJson(jsonSettings, JadxSettings.class);
|
||||
}
|
||||
|
||||
public static String makeString(JadxSettings settings) {
|
||||
return GSON.toJson(settings);
|
||||
}
|
||||
|
||||
public static JsonObject makeJsonObject(JadxSettings settings) {
|
||||
return GSON.toJsonTree(settings).getAsJsonObject();
|
||||
}
|
||||
|
||||
public static void fill(JadxSettings settings, String jsonStr) {
|
||||
GsonUtils.fillObjectFromJsonString(makeGsonBuilder(), settings, jsonStr);
|
||||
}
|
||||
|
||||
private static GsonBuilder makeGsonBuilder() {
|
||||
return GsonUtils.defaultGsonBuilder()
|
||||
.setExclusionStrategies(EXCLUDE_FIELDS)
|
||||
.registerTypeHierarchyAdapter(Path.class, PathTypeAdapter.singleton())
|
||||
.registerTypeHierarchyAdapter(Rectangle.class, RectangleTypeAdapter.singleton());
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation for specifying fields that should not be saved/loaded
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface GsonExclude {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.cli.LogHelper;
|
||||
import jadx.gui.cache.code.CodeCacheMode;
|
||||
import jadx.gui.cache.usage.UsageCacheMode;
|
||||
import jadx.gui.settings.data.SaveOptionEnum;
|
||||
import jadx.gui.ui.action.ActionModel;
|
||||
import jadx.gui.ui.tab.dnd.TabDndGhostType;
|
||||
import jadx.gui.utils.LafManager;
|
||||
import jadx.gui.utils.LangLocale;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.shortcut.Shortcut;
|
||||
|
||||
/**
|
||||
* Data class to hold all jadx-gui settings.
|
||||
* Also inherit all options from jadx-cli.
|
||||
* Serialized/deserialized as JSON in {@link jadx.cli.config.JadxConfigAdapter}.
|
||||
* Annotation {@link JadxConfigExcludeExport} used to exclude environment (files, window states)
|
||||
* fields from copy/export.
|
||||
*/
|
||||
public class JadxSettingsData extends JadxGUIArgs {
|
||||
public static final int CURRENT_SETTINGS_VERSION = 23;
|
||||
|
||||
private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));
|
||||
|
||||
@JadxConfigExcludeExport
|
||||
private Path lastSaveProjectPath = USER_HOME;
|
||||
@JadxConfigExcludeExport
|
||||
private Path lastOpenFilePath = USER_HOME;
|
||||
@JadxConfigExcludeExport
|
||||
private Path lastSaveFilePath = USER_HOME;
|
||||
@JadxConfigExcludeExport
|
||||
private List<Path> recentProjects = new ArrayList<>();
|
||||
|
||||
@JadxConfigExcludeExport
|
||||
private Map<String, WindowLocation> windowPos = new HashMap<>();
|
||||
@JadxConfigExcludeExport
|
||||
private int mainWindowExtendedState = JFrame.NORMAL;
|
||||
|
||||
private boolean flattenPackage = false;
|
||||
private boolean checkForUpdates = true;
|
||||
private JadxUpdateChannel jadxUpdateChannel = JadxUpdateChannel.STABLE;
|
||||
|
||||
private float uiZoom = 1.0f;
|
||||
private String fontStr = "";
|
||||
private String smaliFontStr = "";
|
||||
private String editorTheme = "";
|
||||
|
||||
private String lafTheme = LafManager.INITIAL_THEME_NAME;
|
||||
private LangLocale langLocale = NLS.defaultLocale();
|
||||
private boolean autoStartJobs = false;
|
||||
private String excludedPackages = "";
|
||||
private SaveOptionEnum saveOption = SaveOptionEnum.ASK;
|
||||
|
||||
private Map<ActionModel, Shortcut> shortcuts = new HashMap<>();
|
||||
|
||||
private boolean showHeapUsageBar = false;
|
||||
private boolean alwaysSelectOpened = false;
|
||||
private boolean enablePreviewTab = false;
|
||||
private boolean useAlternativeFileDialog = false;
|
||||
private boolean codeAreaLineWrap = false;
|
||||
private int searchResultsPerPage = 50;
|
||||
private boolean useAutoSearch = true;
|
||||
private boolean keepCommonDialogOpen = false;
|
||||
private boolean smaliAreaShowBytecode = false;
|
||||
private LineNumbersMode lineNumbersMode = LineNumbersMode.AUTO;
|
||||
|
||||
private int mainWindowVerticalSplitterLoc = 300;
|
||||
private int debuggerStackFrameSplitterLoc = 300;
|
||||
private int debuggerVarTreeSplitterLoc = 700;
|
||||
|
||||
private String adbDialogPath = "";
|
||||
private String adbDialogHost = "localhost";
|
||||
private String adbDialogPort = "5037";
|
||||
|
||||
private CodeCacheMode codeCacheMode = CodeCacheMode.DISK;
|
||||
private UsageCacheMode usageCacheMode = UsageCacheMode.DISK;
|
||||
|
||||
/**
|
||||
* Cache dir option values:
|
||||
* null - default (system)
|
||||
* "." - at project dir
|
||||
* other - custom
|
||||
*/
|
||||
private @Nullable String cacheDir = null;
|
||||
|
||||
private boolean jumpOnDoubleClick = true;
|
||||
private boolean disableTooltipOnHover = false;
|
||||
|
||||
private XposedCodegenLanguage xposedCodegenLanguage = XposedCodegenLanguage.JAVA;
|
||||
|
||||
private int treeWidth = 130;
|
||||
private boolean dockLogViewer = true;
|
||||
private boolean dockQuickTabs = false;
|
||||
private TabDndGhostType tabDndGhostType = TabDndGhostType.OUTLINE;
|
||||
|
||||
private int settingsVersion = CURRENT_SETTINGS_VERSION;
|
||||
|
||||
public JadxSettingsData() {
|
||||
this.logLevel = LogHelper.LogLevelEnum.INFO;
|
||||
}
|
||||
|
||||
public String getAdbDialogHost() {
|
||||
return adbDialogHost;
|
||||
}
|
||||
|
||||
public void setAdbDialogHost(String adbDialogHost) {
|
||||
this.adbDialogHost = adbDialogHost;
|
||||
}
|
||||
|
||||
public String getAdbDialogPath() {
|
||||
return adbDialogPath;
|
||||
}
|
||||
|
||||
public void setAdbDialogPath(String adbDialogPath) {
|
||||
this.adbDialogPath = adbDialogPath;
|
||||
}
|
||||
|
||||
public String getAdbDialogPort() {
|
||||
return adbDialogPort;
|
||||
}
|
||||
|
||||
public void setAdbDialogPort(String adbDialogPort) {
|
||||
this.adbDialogPort = adbDialogPort;
|
||||
}
|
||||
|
||||
public boolean isAlwaysSelectOpened() {
|
||||
return alwaysSelectOpened;
|
||||
}
|
||||
|
||||
public void setAlwaysSelectOpened(boolean alwaysSelectOpened) {
|
||||
this.alwaysSelectOpened = alwaysSelectOpened;
|
||||
}
|
||||
|
||||
public boolean isAutoStartJobs() {
|
||||
return autoStartJobs;
|
||||
}
|
||||
|
||||
public void setAutoStartJobs(boolean autoStartJobs) {
|
||||
this.autoStartJobs = autoStartJobs;
|
||||
}
|
||||
|
||||
public @Nullable String getCacheDir() {
|
||||
return cacheDir;
|
||||
}
|
||||
|
||||
public void setCacheDir(@Nullable String cacheDir) {
|
||||
this.cacheDir = cacheDir;
|
||||
}
|
||||
|
||||
public boolean isCheckForUpdates() {
|
||||
return checkForUpdates;
|
||||
}
|
||||
|
||||
public void setCheckForUpdates(boolean checkForUpdates) {
|
||||
this.checkForUpdates = checkForUpdates;
|
||||
}
|
||||
|
||||
public boolean isCodeAreaLineWrap() {
|
||||
return codeAreaLineWrap;
|
||||
}
|
||||
|
||||
public void setCodeAreaLineWrap(boolean codeAreaLineWrap) {
|
||||
this.codeAreaLineWrap = codeAreaLineWrap;
|
||||
}
|
||||
|
||||
public CodeCacheMode getCodeCacheMode() {
|
||||
return codeCacheMode;
|
||||
}
|
||||
|
||||
public void setCodeCacheMode(CodeCacheMode codeCacheMode) {
|
||||
this.codeCacheMode = codeCacheMode;
|
||||
}
|
||||
|
||||
public int getDebuggerStackFrameSplitterLoc() {
|
||||
return debuggerStackFrameSplitterLoc;
|
||||
}
|
||||
|
||||
public void setDebuggerStackFrameSplitterLoc(int debuggerStackFrameSplitterLoc) {
|
||||
this.debuggerStackFrameSplitterLoc = debuggerStackFrameSplitterLoc;
|
||||
}
|
||||
|
||||
public int getDebuggerVarTreeSplitterLoc() {
|
||||
return debuggerVarTreeSplitterLoc;
|
||||
}
|
||||
|
||||
public void setDebuggerVarTreeSplitterLoc(int debuggerVarTreeSplitterLoc) {
|
||||
this.debuggerVarTreeSplitterLoc = debuggerVarTreeSplitterLoc;
|
||||
}
|
||||
|
||||
public boolean isDisableTooltipOnHover() {
|
||||
return disableTooltipOnHover;
|
||||
}
|
||||
|
||||
public void setDisableTooltipOnHover(boolean disableTooltipOnHover) {
|
||||
this.disableTooltipOnHover = disableTooltipOnHover;
|
||||
}
|
||||
|
||||
public boolean isDockLogViewer() {
|
||||
return dockLogViewer;
|
||||
}
|
||||
|
||||
public void setDockLogViewer(boolean dockLogViewer) {
|
||||
this.dockLogViewer = dockLogViewer;
|
||||
}
|
||||
|
||||
public boolean isDockQuickTabs() {
|
||||
return dockQuickTabs;
|
||||
}
|
||||
|
||||
public void setDockQuickTabs(boolean dockQuickTabs) {
|
||||
this.dockQuickTabs = dockQuickTabs;
|
||||
}
|
||||
|
||||
public String getEditorTheme() {
|
||||
return editorTheme;
|
||||
}
|
||||
|
||||
public void setEditorTheme(String editorTheme) {
|
||||
this.editorTheme = editorTheme;
|
||||
}
|
||||
|
||||
public boolean isEnablePreviewTab() {
|
||||
return enablePreviewTab;
|
||||
}
|
||||
|
||||
public void setEnablePreviewTab(boolean enablePreviewTab) {
|
||||
this.enablePreviewTab = enablePreviewTab;
|
||||
}
|
||||
|
||||
public String getExcludedPackages() {
|
||||
return excludedPackages;
|
||||
}
|
||||
|
||||
public void setExcludedPackages(String excludedPackages) {
|
||||
this.excludedPackages = excludedPackages;
|
||||
}
|
||||
|
||||
public boolean isFlattenPackage() {
|
||||
return flattenPackage;
|
||||
}
|
||||
|
||||
public void setFlattenPackage(boolean flattenPackage) {
|
||||
this.flattenPackage = flattenPackage;
|
||||
}
|
||||
|
||||
public JadxUpdateChannel getJadxUpdateChannel() {
|
||||
return jadxUpdateChannel;
|
||||
}
|
||||
|
||||
public void setJadxUpdateChannel(JadxUpdateChannel jadxUpdateChannel) {
|
||||
this.jadxUpdateChannel = jadxUpdateChannel;
|
||||
}
|
||||
|
||||
public boolean isJumpOnDoubleClick() {
|
||||
return jumpOnDoubleClick;
|
||||
}
|
||||
|
||||
public void setJumpOnDoubleClick(boolean jumpOnDoubleClick) {
|
||||
this.jumpOnDoubleClick = jumpOnDoubleClick;
|
||||
}
|
||||
|
||||
public boolean isKeepCommonDialogOpen() {
|
||||
return keepCommonDialogOpen;
|
||||
}
|
||||
|
||||
public void setKeepCommonDialogOpen(boolean keepCommonDialogOpen) {
|
||||
this.keepCommonDialogOpen = keepCommonDialogOpen;
|
||||
}
|
||||
|
||||
public String getLafTheme() {
|
||||
return lafTheme;
|
||||
}
|
||||
|
||||
public void setLafTheme(String lafTheme) {
|
||||
this.lafTheme = lafTheme;
|
||||
}
|
||||
|
||||
public LangLocale getLangLocale() {
|
||||
return langLocale;
|
||||
}
|
||||
|
||||
public void setLangLocale(LangLocale langLocale) {
|
||||
this.langLocale = langLocale;
|
||||
}
|
||||
|
||||
public Path getLastOpenFilePath() {
|
||||
return lastOpenFilePath;
|
||||
}
|
||||
|
||||
public void setLastOpenFilePath(Path lastOpenFilePath) {
|
||||
this.lastOpenFilePath = lastOpenFilePath;
|
||||
}
|
||||
|
||||
public Path getLastSaveFilePath() {
|
||||
return lastSaveFilePath;
|
||||
}
|
||||
|
||||
public void setLastSaveFilePath(Path lastSaveFilePath) {
|
||||
this.lastSaveFilePath = lastSaveFilePath;
|
||||
}
|
||||
|
||||
public Path getLastSaveProjectPath() {
|
||||
return lastSaveProjectPath;
|
||||
}
|
||||
|
||||
public void setLastSaveProjectPath(Path lastSaveProjectPath) {
|
||||
this.lastSaveProjectPath = lastSaveProjectPath;
|
||||
}
|
||||
|
||||
public LineNumbersMode getLineNumbersMode() {
|
||||
return lineNumbersMode;
|
||||
}
|
||||
|
||||
public void setLineNumbersMode(LineNumbersMode lineNumbersMode) {
|
||||
this.lineNumbersMode = lineNumbersMode;
|
||||
}
|
||||
|
||||
public int getMainWindowExtendedState() {
|
||||
return mainWindowExtendedState;
|
||||
}
|
||||
|
||||
public void setMainWindowExtendedState(int mainWindowExtendedState) {
|
||||
this.mainWindowExtendedState = mainWindowExtendedState;
|
||||
}
|
||||
|
||||
public int getMainWindowVerticalSplitterLoc() {
|
||||
return mainWindowVerticalSplitterLoc;
|
||||
}
|
||||
|
||||
public void setMainWindowVerticalSplitterLoc(int mainWindowVerticalSplitterLoc) {
|
||||
this.mainWindowVerticalSplitterLoc = mainWindowVerticalSplitterLoc;
|
||||
}
|
||||
|
||||
public List<Path> getRecentProjects() {
|
||||
return recentProjects;
|
||||
}
|
||||
|
||||
public void setRecentProjects(List<Path> recentProjects) {
|
||||
this.recentProjects = recentProjects;
|
||||
}
|
||||
|
||||
public SaveOptionEnum getSaveOption() {
|
||||
return saveOption;
|
||||
}
|
||||
|
||||
public void setSaveOption(SaveOptionEnum saveOption) {
|
||||
this.saveOption = saveOption;
|
||||
}
|
||||
|
||||
public int getSearchResultsPerPage() {
|
||||
return searchResultsPerPage;
|
||||
}
|
||||
|
||||
public void setSearchResultsPerPage(int searchResultsPerPage) {
|
||||
this.searchResultsPerPage = searchResultsPerPage;
|
||||
}
|
||||
|
||||
public int getSettingsVersion() {
|
||||
return settingsVersion;
|
||||
}
|
||||
|
||||
public void setSettingsVersion(int settingsVersion) {
|
||||
this.settingsVersion = settingsVersion;
|
||||
}
|
||||
|
||||
public Map<ActionModel, Shortcut> getShortcuts() {
|
||||
return shortcuts;
|
||||
}
|
||||
|
||||
public void setShortcuts(Map<ActionModel, Shortcut> shortcuts) {
|
||||
this.shortcuts = shortcuts;
|
||||
}
|
||||
|
||||
public boolean isShowHeapUsageBar() {
|
||||
return showHeapUsageBar;
|
||||
}
|
||||
|
||||
public void setShowHeapUsageBar(boolean showHeapUsageBar) {
|
||||
this.showHeapUsageBar = showHeapUsageBar;
|
||||
}
|
||||
|
||||
public boolean isSmaliAreaShowBytecode() {
|
||||
return smaliAreaShowBytecode;
|
||||
}
|
||||
|
||||
public void setSmaliAreaShowBytecode(boolean smaliAreaShowBytecode) {
|
||||
this.smaliAreaShowBytecode = smaliAreaShowBytecode;
|
||||
}
|
||||
|
||||
public String getFontStr() {
|
||||
return fontStr;
|
||||
}
|
||||
|
||||
public void setFontStr(String fontStr) {
|
||||
this.fontStr = fontStr;
|
||||
}
|
||||
|
||||
public String getSmaliFontStr() {
|
||||
return smaliFontStr;
|
||||
}
|
||||
|
||||
public void setSmaliFontStr(String smaliFontStr) {
|
||||
this.smaliFontStr = smaliFontStr;
|
||||
}
|
||||
|
||||
public TabDndGhostType getTabDndGhostType() {
|
||||
return tabDndGhostType;
|
||||
}
|
||||
|
||||
public void setTabDndGhostType(TabDndGhostType tabDndGhostType) {
|
||||
this.tabDndGhostType = tabDndGhostType;
|
||||
}
|
||||
|
||||
public int getTreeWidth() {
|
||||
return treeWidth;
|
||||
}
|
||||
|
||||
public void setTreeWidth(int treeWidth) {
|
||||
this.treeWidth = treeWidth;
|
||||
}
|
||||
|
||||
public float getUiZoom() {
|
||||
return uiZoom;
|
||||
}
|
||||
|
||||
public void setUiZoom(float uiZoom) {
|
||||
this.uiZoom = uiZoom;
|
||||
}
|
||||
|
||||
public UsageCacheMode getUsageCacheMode() {
|
||||
return usageCacheMode;
|
||||
}
|
||||
|
||||
public void setUsageCacheMode(UsageCacheMode usageCacheMode) {
|
||||
this.usageCacheMode = usageCacheMode;
|
||||
}
|
||||
|
||||
public boolean isUseAlternativeFileDialog() {
|
||||
return useAlternativeFileDialog;
|
||||
}
|
||||
|
||||
public void setUseAlternativeFileDialog(boolean useAlternativeFileDialog) {
|
||||
this.useAlternativeFileDialog = useAlternativeFileDialog;
|
||||
}
|
||||
|
||||
public boolean isUseAutoSearch() {
|
||||
return useAutoSearch;
|
||||
}
|
||||
|
||||
public void setUseAutoSearch(boolean useAutoSearch) {
|
||||
this.useAutoSearch = useAutoSearch;
|
||||
}
|
||||
|
||||
public Map<String, WindowLocation> getWindowPos() {
|
||||
return windowPos;
|
||||
}
|
||||
|
||||
public void setWindowPos(Map<String, WindowLocation> windowPos) {
|
||||
this.windowPos = windowPos;
|
||||
}
|
||||
|
||||
public XposedCodegenLanguage getXposedCodegenLanguage() {
|
||||
return xposedCodegenLanguage;
|
||||
}
|
||||
|
||||
public void setXposedCodegenLanguage(XposedCodegenLanguage xposedCodegenLanguage) {
|
||||
this.xposedCodegenLanguage = xposedCodegenLanguage;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.JadxGUI;
|
||||
import jadx.gui.utils.files.JadxFiles;
|
||||
|
||||
/**
|
||||
* Jadx settings storage. Select first available option:
|
||||
* 1. json file in system 'config' directory (preferred)
|
||||
* 2. using java preferences api: use Windows registry or xml on Linux/Mac (obsolete, load only)
|
||||
*/
|
||||
public class JadxSettingsStorage {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxSettingsStorage.class);
|
||||
|
||||
private final Path configFile = initConfigFile();
|
||||
|
||||
public synchronized @Nullable String load() throws IOException {
|
||||
if (Files.exists(configFile)) {
|
||||
return FileUtils.readFile(configFile);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public synchronized void save(String jsonStr) throws IOException {
|
||||
FileUtils.writeFile(configFile, jsonStr);
|
||||
}
|
||||
|
||||
private static Path initConfigFile() {
|
||||
Path confPath = JadxFiles.GUI_CONF;
|
||||
if (!Files.exists(confPath)) {
|
||||
copyFromPreferences(confPath);
|
||||
}
|
||||
LOG.debug("Using config: {}", confPath);
|
||||
return confPath;
|
||||
}
|
||||
|
||||
private static void copyFromPreferences(Path confPath) {
|
||||
try {
|
||||
Preferences prefs = Preferences.userNodeForPackage(JadxGUI.class);
|
||||
String str = prefs.get("jadx.gui.settings", "");
|
||||
if (StringUtils.notEmpty(str)) {
|
||||
FileUtils.writeFile(confPath, str);
|
||||
LOG.warn("Settings moved from java preferences to config file: {}", confPath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to load settings from preferences", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
public enum JadxUpdateChannel {
|
||||
STABLE,
|
||||
UNSTABLE,
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package jadx.gui.settings
|
||||
|
||||
enum class JadxUpdateChannel {
|
||||
STABLE,
|
||||
UNSTABLE,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
public enum XposedCodegenLanguage {
|
||||
JAVA,
|
||||
KOTLIN,
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package jadx.gui.settings
|
||||
|
||||
enum class XposedCodegenLanguage {
|
||||
JAVA,
|
||||
KOTLIN,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package jadx.gui.settings.data;
|
||||
|
||||
public enum SaveOptionEnum {
|
||||
ASK,
|
||||
NEVER,
|
||||
ALWAYS
|
||||
}
|
||||
@@ -42,8 +42,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
import jadx.api.DecompilationMode;
|
||||
import jadx.api.JadxArgs;
|
||||
@@ -56,14 +54,14 @@ import jadx.api.args.UseSourceNameAsClassNameAlias;
|
||||
import jadx.api.plugins.events.JadxEvents;
|
||||
import jadx.api.plugins.events.types.ReloadSettingsWindow;
|
||||
import jadx.api.plugins.gui.ISettingsGroup;
|
||||
import jadx.core.utils.GsonUtils;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.settings.JadxSettingsAdapter;
|
||||
import jadx.gui.settings.JadxSettingsData;
|
||||
import jadx.gui.settings.JadxUpdateChannel;
|
||||
import jadx.gui.settings.LineNumbersMode;
|
||||
import jadx.gui.settings.XposedCodegenLanguage;
|
||||
import jadx.gui.settings.data.SaveOptionEnum;
|
||||
import jadx.gui.settings.ui.cache.CacheSettingsGroup;
|
||||
import jadx.gui.settings.ui.font.JadxFontDialog;
|
||||
import jadx.gui.settings.ui.plugins.PluginSettings;
|
||||
@@ -99,7 +97,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
public JadxSettingsWindow(MainWindow mainWindow, JadxSettings settings) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.settings = settings;
|
||||
this.startSettings = JadxSettingsAdapter.makeString(settings);
|
||||
this.startSettings = settings.getSettingsJsonString();
|
||||
this.startSettingsHash = calcSettingsHash();
|
||||
this.prevLang = settings.getLangLocale();
|
||||
|
||||
@@ -279,7 +277,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
JCheckBox useHeaders = new JCheckBox();
|
||||
useHeaders.setSelected(settings.isUseHeadersForDetectResourceExtensions());
|
||||
useHeaders.addItemListener(e -> {
|
||||
settings.setUseHeadersForDetectResourceExtension(e.getStateChange() == ItemEvent.SELECTED);
|
||||
settings.setUseHeadersForDetectResourceExtensions(e.getStateChange() == ItemEvent.SELECTED);
|
||||
needReload();
|
||||
});
|
||||
|
||||
@@ -376,10 +374,10 @@ public class JadxSettingsWindow extends JDialog {
|
||||
}
|
||||
|
||||
private SettingsGroup makeProjectGroup() {
|
||||
JComboBox<JadxSettings.SAVEOPTION> dropdown = new JComboBox<>(JadxSettings.SAVEOPTION.values());
|
||||
JComboBox<SaveOptionEnum> dropdown = new JComboBox<>(SaveOptionEnum.values());
|
||||
dropdown.setSelectedItem(settings.getSaveOption());
|
||||
dropdown.addActionListener(e -> {
|
||||
settings.setSaveOption((JadxSettings.SAVEOPTION) dropdown.getSelectedItem());
|
||||
settings.setSaveOption((SaveOptionEnum) dropdown.getSelectedItem());
|
||||
needReload();
|
||||
});
|
||||
|
||||
@@ -730,16 +728,14 @@ public class JadxSettingsWindow extends JDialog {
|
||||
needReload();
|
||||
});
|
||||
|
||||
JComboBox<XposedCodegenLanguage> xposedCodegenLanguage =
|
||||
new JComboBox<>(XposedCodegenLanguage.getEntries().toArray(new XposedCodegenLanguage[0]));
|
||||
JComboBox<XposedCodegenLanguage> xposedCodegenLanguage = new JComboBox<>(XposedCodegenLanguage.values());
|
||||
xposedCodegenLanguage.setSelectedItem(settings.getXposedCodegenLanguage());
|
||||
xposedCodegenLanguage.addActionListener(e -> {
|
||||
settings.setXposedCodegenLanguage((XposedCodegenLanguage) xposedCodegenLanguage.getSelectedItem());
|
||||
mainWindow.loadSettings();
|
||||
});
|
||||
|
||||
JComboBox<JadxUpdateChannel> updateChannel =
|
||||
new JComboBox<>(JadxUpdateChannel.getEntries().toArray(new JadxUpdateChannel[0]));
|
||||
JComboBox<JadxUpdateChannel> updateChannel = new JComboBox<>(JadxUpdateChannel.values());
|
||||
updateChannel.setSelectedItem(settings.getJadxUpdateChannel());
|
||||
updateChannel.addActionListener(e -> {
|
||||
settings.setJadxUpdateChannel((JadxUpdateChannel) updateChannel.getSelectedItem());
|
||||
@@ -788,7 +784,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
|
||||
private void cancel() {
|
||||
closeGroups(false);
|
||||
JadxSettingsAdapter.fill(settings, startSettings);
|
||||
settings.loadSettingsFromJsonString(startSettings);
|
||||
mainWindow.loadSettings();
|
||||
dispose();
|
||||
}
|
||||
@@ -800,8 +796,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
NLS.str("preferences.reset_title"),
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
if (res == JOptionPane.YES_OPTION) {
|
||||
String defaults = JadxSettingsAdapter.makeString(JadxSettings.makeDefault());
|
||||
JadxSettingsAdapter.fill(settings, defaults);
|
||||
settings.loadSettingsData(new JadxSettingsData());
|
||||
mainWindow.loadSettings();
|
||||
needReload();
|
||||
getContentPane().removeAll();
|
||||
@@ -812,15 +807,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
}
|
||||
|
||||
private void copySettings() {
|
||||
JsonObject settingsJson = JadxSettingsAdapter.makeJsonObject(this.settings);
|
||||
// remove irrelevant preferences
|
||||
settingsJson.remove("windowPos");
|
||||
settingsJson.remove("mainWindowExtendedState");
|
||||
settingsJson.remove("lastSaveProjectPath");
|
||||
settingsJson.remove("lastOpenFilePath");
|
||||
settingsJson.remove("lastSaveFilePath");
|
||||
settingsJson.remove("recentProjects");
|
||||
String settingsText = GsonUtils.buildGson().toJson(settingsJson);
|
||||
String settingsText = settings.exportSettingsString();
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
StringSelection selection = new StringSelection(settingsText);
|
||||
clipboard.setContents(selection, selection);
|
||||
|
||||
@@ -118,6 +118,7 @@ import jadx.gui.plugins.mappings.RenameMappingsGui;
|
||||
import jadx.gui.plugins.quark.QuarkDialog;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.settings.data.SaveOptionEnum;
|
||||
import jadx.gui.settings.ui.JadxSettingsWindow;
|
||||
import jadx.gui.tree.TreeExpansionService;
|
||||
import jadx.gui.treemodel.ApkSignatureNode;
|
||||
@@ -700,10 +701,10 @@ public class MainWindow extends JFrame {
|
||||
return true;
|
||||
}
|
||||
// Check if we saved settings that indicate what to do
|
||||
if (settings.getSaveOption() == JadxSettings.SAVEOPTION.NEVER) {
|
||||
if (settings.getSaveOption() == SaveOptionEnum.NEVER) {
|
||||
return true;
|
||||
}
|
||||
if (settings.getSaveOption() == JadxSettings.SAVEOPTION.ALWAYS) {
|
||||
if (settings.getSaveOption() == SaveOptionEnum.ALWAYS) {
|
||||
saveProject();
|
||||
return true;
|
||||
}
|
||||
@@ -723,7 +724,7 @@ public class MainWindow extends JFrame {
|
||||
switch (res) {
|
||||
case JOptionPane.YES_OPTION:
|
||||
if (remember.isSelected()) {
|
||||
settings.setSaveOption(JadxSettings.SAVEOPTION.ALWAYS);
|
||||
settings.setSaveOption(SaveOptionEnum.ALWAYS);
|
||||
settings.sync();
|
||||
}
|
||||
saveProject();
|
||||
@@ -731,7 +732,7 @@ public class MainWindow extends JFrame {
|
||||
|
||||
case JOptionPane.NO_OPTION:
|
||||
if (remember.isSelected()) {
|
||||
settings.setSaveOption(JadxSettings.SAVEOPTION.NEVER);
|
||||
settings.setSaveOption(SaveOptionEnum.NEVER);
|
||||
settings.sync();
|
||||
}
|
||||
return true;
|
||||
@@ -1163,12 +1164,12 @@ public class MainWindow extends JFrame {
|
||||
|
||||
JCheckBoxMenuItem dockLog = new JCheckBoxMenuItem(NLS.str("menu.dock_log"));
|
||||
dockLog.setState(settings.isDockLogViewer());
|
||||
dockLog.addActionListener(event -> settings.setDockLogViewer(!settings.isDockLogViewer()));
|
||||
dockLog.addActionListener(event -> settings.saveDockLogViewer(!settings.isDockLogViewer()));
|
||||
|
||||
ActionHandler quickTabsAction = new ActionHandler(ev -> {
|
||||
boolean visible = quickTabsTree == null;
|
||||
setQuickTabsVisibility(visible);
|
||||
settings.setDockQuickTabs(visible);
|
||||
settings.saveDockQuickTabs(visible);
|
||||
});
|
||||
quickTabsAction.setNameAndDesc(NLS.str("menu.dock_quick_tabs"));
|
||||
quickTabsAction.setIcon(Icons.QUICK_TABS);
|
||||
@@ -1586,6 +1587,7 @@ public class MainWindow extends JFrame {
|
||||
shortcutsController.loadSettings();
|
||||
}
|
||||
|
||||
@SuppressWarnings("finally")
|
||||
private void closeWindow() {
|
||||
saveAll();
|
||||
if (!ensureProjectIsSaved()) {
|
||||
@@ -1599,6 +1601,7 @@ public class MainWindow extends JFrame {
|
||||
if (debuggerPanel != null) {
|
||||
saveSplittersInfo();
|
||||
}
|
||||
settings.sync();
|
||||
closeAll();
|
||||
UiUtils.uiRunAndWait(() -> {
|
||||
heapUsageBar.reset();
|
||||
@@ -1758,7 +1761,7 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
Runnable undock = () -> {
|
||||
hideDockedLog();
|
||||
settings.setDockLogViewer(false);
|
||||
settings.saveDockLogViewer(false);
|
||||
LogViewerDialog.open(this, logOptions);
|
||||
};
|
||||
logPanel = new LogPanel(this, logOptions, undock, this::hideDockedLog);
|
||||
|
||||
@@ -162,6 +162,7 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
boolean wrap = !getLineWrap();
|
||||
settings.setCodeAreaLineWrap(wrap);
|
||||
settings.sync();
|
||||
contentPanel.getTabbedPane().getTabs().forEach(v -> {
|
||||
if (v instanceof AbstractCodeContentPanel) {
|
||||
AbstractCodeArea codeArea = ((AbstractCodeContentPanel) v).getCodeArea();
|
||||
@@ -172,7 +173,6 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
}
|
||||
}
|
||||
});
|
||||
settings.sync();
|
||||
}
|
||||
});
|
||||
popupMenu.add(wrapItem);
|
||||
|
||||
@@ -75,7 +75,7 @@ public final class SmaliArea extends AbstractCodeArea {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JadxSettings settings = getContentPanel().getMainWindow().getSettings();
|
||||
settings.setSmaliAreaShowBytecode(!settings.getSmaliAreaShowBytecode());
|
||||
settings.setSmaliAreaShowBytecode(!settings.isSmaliAreaShowBytecode());
|
||||
contentPanel.getTabbedPane().getTabs().forEach(v -> {
|
||||
if (v instanceof ClassCodeContentPanel) {
|
||||
switchModel();
|
||||
@@ -156,7 +156,7 @@ public final class SmaliArea extends AbstractCodeArea {
|
||||
}
|
||||
|
||||
private boolean shouldUseSmaliPrinterV2() {
|
||||
return getContentPanel().getMainWindow().getSettings().getSmaliAreaShowBytecode();
|
||||
return getContentPanel().getMainWindow().getSettings().isSmaliAreaShowBytecode();
|
||||
}
|
||||
|
||||
private abstract class SmaliModel {
|
||||
|
||||
@@ -46,6 +46,7 @@ import jadx.gui.device.debugger.DebugSettings;
|
||||
import jadx.gui.device.protocol.ADB;
|
||||
import jadx.gui.device.protocol.ADBDevice;
|
||||
import jadx.gui.device.protocol.ADBDeviceInfo;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.panel.IDebugController;
|
||||
import jadx.gui.utils.NLS;
|
||||
@@ -427,13 +428,17 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
|
||||
@Override
|
||||
public void dispose() {
|
||||
clear();
|
||||
super.dispose();
|
||||
boolean save = mainWindow.getSettings().getAdbDialogPath().equals(pathTextField.getText());
|
||||
boolean save1 = mainWindow.getSettings().getAdbDialogHost().equals(hostTextField.getText());
|
||||
boolean save2 = mainWindow.getSettings().getAdbDialogPort().equals(portTextField.getText());
|
||||
if (save || save1 || save2) {
|
||||
mainWindow.getSettings().sync();
|
||||
JadxSettings settings = mainWindow.getSettings();
|
||||
boolean changed = !settings.getAdbDialogPath().equals(pathTextField.getText());
|
||||
changed |= !settings.getAdbDialogHost().equals(hostTextField.getText());
|
||||
changed |= !settings.getAdbDialogPort().equals(portTextField.getText());
|
||||
if (changed) {
|
||||
settings.setAdbDialogPath(pathTextField.getText());
|
||||
settings.setAdbDialogHost(hostTextField.getText());
|
||||
settings.setAdbDialogPort(portTextField.getText());
|
||||
settings.sync();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -153,7 +153,7 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
} else {
|
||||
tabsController.codeJump(node);
|
||||
}
|
||||
if (!mainWindow.getSettings().getKeepCommonDialogOpen()) {
|
||||
if (!mainWindow.getSettings().isKeepCommonDialogOpen()) {
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
@@ -210,11 +210,8 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
copyBtn.addActionListener(event -> copyAllSearchResults());
|
||||
|
||||
JCheckBox cbKeepOpen = new JCheckBox(NLS.str("search_dialog.keep_open"));
|
||||
cbKeepOpen.setSelected(mainWindow.getSettings().getKeepCommonDialogOpen());
|
||||
cbKeepOpen.addActionListener(e -> {
|
||||
mainWindow.getSettings().setKeepCommonDialogOpen(cbKeepOpen.isSelected());
|
||||
mainWindow.getSettings().sync();
|
||||
});
|
||||
cbKeepOpen.setSelected(mainWindow.getSettings().isKeepCommonDialogOpen());
|
||||
cbKeepOpen.addActionListener(e -> mainWindow.getSettings().saveKeepCommonDialogOpen(cbKeepOpen.isSelected()));
|
||||
cbKeepOpen.setAlignmentY(Component.CENTER_ALIGNMENT);
|
||||
|
||||
JPanel buttonPane = new JPanel();
|
||||
|
||||
@@ -32,10 +32,11 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.cli.config.JadxConfigAdapter;
|
||||
import jadx.commons.app.JadxSystemInfo;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.settings.JadxSettingsAdapter;
|
||||
import jadx.gui.settings.JadxSettingsData;
|
||||
import jadx.gui.utils.LafManager;
|
||||
import jadx.gui.utils.Link;
|
||||
|
||||
@@ -130,10 +131,10 @@ public class ExceptionDialog extends JDialog {
|
||||
|
||||
JPanel buttonPanel = new JPanel();
|
||||
JButton exitButton = new JButton("Terminate Jadx");
|
||||
exitButton.addActionListener((event) -> System.exit(1));
|
||||
exitButton.addActionListener(event -> System.exit(1));
|
||||
buttonPanel.add(exitButton);
|
||||
JButton closeButton = new JButton("Go back to Jadx");
|
||||
closeButton.addActionListener((event) -> setVisible(false));
|
||||
closeButton.addActionListener(event -> setVisible(false));
|
||||
buttonPanel.add(closeButton);
|
||||
JScrollPane messageAreaScroller = new JScrollPane(messageArea);
|
||||
messageAreaScroller.setMinimumSize(new Dimension(600, 400));
|
||||
@@ -152,7 +153,7 @@ public class ExceptionDialog extends JDialog {
|
||||
final int y = (screenSize.height - getHeight()) / 2;
|
||||
setLocation(x, y);
|
||||
|
||||
getRootPane().registerKeyboardAction((event) -> setVisible(false),
|
||||
getRootPane().registerKeyboardAction(event -> setVisible(false),
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
|
||||
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||
|
||||
@@ -177,8 +178,14 @@ public class ExceptionDialog extends JDialog {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
JadxSettings settings = JadxSettingsAdapter.load();
|
||||
LafManager.init(settings);
|
||||
JadxConfigAdapter<JadxSettingsData> configAdapter = JadxSettings.buildConfigAdapter();
|
||||
configAdapter.useConfigRef("");
|
||||
JadxSettingsData settingsData = configAdapter.load();
|
||||
if (settingsData != null) {
|
||||
JadxSettings settings = new JadxSettings(configAdapter);
|
||||
settings.loadSettingsData(settingsData);
|
||||
LafManager.init(settings);
|
||||
}
|
||||
showTestExceptionDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class LogViewerDialog extends JFrame {
|
||||
UiUtils.setWindowIcons(this);
|
||||
|
||||
Runnable dock = () -> {
|
||||
mainWindow.getSettings().setDockLogViewer(true);
|
||||
mainWindow.getSettings().saveDockLogViewer(true);
|
||||
dispose();
|
||||
mainWindow.showLogViewer(LogOptions.current());
|
||||
};
|
||||
|
||||
@@ -270,7 +270,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
autoSearchCB.setSelected(autoSearch);
|
||||
autoSearchCB.addActionListener(ev -> {
|
||||
boolean newValue = autoSearchCB.isSelected();
|
||||
mainWindow.getSettings().setUseAutoSearch(newValue);
|
||||
mainWindow.getSettings().saveUseAutoSearch(newValue);
|
||||
searchBtn.setVisible(!newValue);
|
||||
initSearchEvents();
|
||||
if (newValue) {
|
||||
|
||||
@@ -597,11 +597,8 @@ public class UsageDialogPlus extends CommonSearchDialog {
|
||||
getRootPane().setDefaultButton(openBtn);
|
||||
|
||||
JCheckBox cbKeepOpen = new JCheckBox(NLS.str("search_dialog.keep_open"));
|
||||
cbKeepOpen.setSelected(mainWindow.getSettings().getKeepCommonDialogOpen());
|
||||
cbKeepOpen.addActionListener(e -> {
|
||||
mainWindow.getSettings().setKeepCommonDialogOpen(cbKeepOpen.isSelected());
|
||||
mainWindow.getSettings().sync();
|
||||
});
|
||||
cbKeepOpen.setSelected(mainWindow.getSettings().isKeepCommonDialogOpen());
|
||||
cbKeepOpen.addActionListener(e -> mainWindow.getSettings().saveKeepCommonDialogOpen(cbKeepOpen.isSelected()));
|
||||
cbKeepOpen.setAlignmentY(Component.CENTER_ALIGNMENT);
|
||||
|
||||
JPanel buttonPane = new JPanel();
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.google.gson.stream.JsonWriter;
|
||||
|
||||
public class PathTypeAdapter {
|
||||
|
||||
private static final TypeAdapter<Path> SINGLETON = new TypeAdapter<Path>() {
|
||||
private static final TypeAdapter<Path> SINGLETON = new TypeAdapter<>() {
|
||||
@Override
|
||||
public void write(JsonWriter out, Path value) throws IOException {
|
||||
if (value == null) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import com.google.gson.stream.JsonWriter;
|
||||
|
||||
public class RectangleTypeAdapter {
|
||||
|
||||
private static final TypeAdapter<Rectangle> SINGLETON = new TypeAdapter<Rectangle>() {
|
||||
private static final TypeAdapter<Rectangle> SINGLETON = new TypeAdapter<>() {
|
||||
@Override
|
||||
public void write(JsonWriter out, Rectangle value) throws IOException {
|
||||
if (value == null) {
|
||||
|
||||
Reference in New Issue
Block a user