Commit b09c7ba6 authored by Ahmed Ashour's avatar Ahmed Ashour Committed by skylot

feat(gui): support project (#526) (PR #543)

parent ec66476a
...@@ -231,10 +231,6 @@ public class JadxCLIArgs { ...@@ -231,10 +231,6 @@ public class JadxCLIArgs {
return deobfuscationUseSourceNameAsAlias; return deobfuscationUseSourceNameAsAlias;
} }
public boolean escapeUnicode() {
return escapeUnicode;
}
public boolean isEscapeUnicode() { public boolean isEscapeUnicode() {
return escapeUnicode; return escapeUnicode;
} }
......
...@@ -26,7 +26,7 @@ public class JadxGUI { ...@@ -26,7 +26,7 @@ public class JadxGUI {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} }
NLS.setLocale(settings.getLangLocale()); NLS.setLocale(settings.getLangLocale());
SwingUtilities.invokeLater(new MainWindow(settings)::open); SwingUtilities.invokeLater(new MainWindow(settings)::init);
} catch (Exception e) { } catch (Exception e) {
LOG.error("Error: {}", e.getMessage(), e); LOG.error("Error: {}", e.getMessage(), e);
System.exit(1); System.exit(1);
......
...@@ -47,9 +47,7 @@ public class JadxWrapper { ...@@ -47,9 +47,7 @@ public class JadxWrapper {
} }
public void saveAll(final File dir, final ProgressMonitor progressMonitor) { public void saveAll(final File dir, final ProgressMonitor progressMonitor) {
Runnable save = new Runnable() { Runnable save = () -> {
@Override
public void run() {
try { try {
decompiler.getArgs().setRootDir(dir); decompiler.getArgs().setRootDir(dir);
ThreadPoolExecutor ex = (ThreadPoolExecutor) decompiler.getSaveExecutor(); ThreadPoolExecutor ex = (ThreadPoolExecutor) decompiler.getSaveExecutor();
...@@ -68,7 +66,6 @@ public class JadxWrapper { ...@@ -68,7 +66,6 @@ public class JadxWrapper {
LOG.error("Save interrupted", e); LOG.error("Save interrupted", e);
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
}
}; };
new Thread(save).start(); new Thread(save).start();
} }
......
...@@ -58,8 +58,7 @@ public class BackgroundWorker extends SwingWorker<Void, Void> { ...@@ -58,8 +58,7 @@ public class BackgroundWorker extends SwingWorker<Void, Void> {
if (searchIndex != null && searchIndex.getSkippedCount() > 0) { if (searchIndex != null && searchIndex.getSkippedCount() > 0) {
LOG.warn("Indexing of some classes skipped, count: {}, low memory: {}", LOG.warn("Indexing of some classes skipped, count: {}, low memory: {}",
searchIndex.getSkippedCount(), Utils.memoryInfo()); searchIndex.getSkippedCount(), Utils.memoryInfo());
String msg = NLS.str("message.indexingClassesSkipped"); String msg = NLS.str("message.indexingClassesSkipped", searchIndex.getSkippedCount());
msg = String.format(msg, searchIndex.getSkippedCount());
JOptionPane.showMessageDialog(null, msg); JOptionPane.showMessageDialog(null, msg);
} }
} catch (Exception e) { } catch (Exception e) {
......
package jadx.gui.settings;
import java.io.BufferedWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import jadx.gui.utils.PathTypeAdapter;
public class JadxProject {
private static final Logger LOG = LoggerFactory.getLogger(JadxProject.class);
private static final int CURRENT_SETTINGS_VERSION = 0;
public static final String PROJECT_EXTENSION = "jadx";
private static final Gson GSON = new GsonBuilder()
.registerTypeHierarchyAdapter(Path.class, PathTypeAdapter.singleton())
.create();
private transient JadxSettings settings;
private transient String name = "New Project";
private transient Path projectPath;
private List<Path> filesPath;
private transient boolean saved;
private transient boolean initial = true;
private int projectVersion = 0;
public JadxProject(JadxSettings settings) {
this.settings = settings;
}
public Path getProjectPath() {
return projectPath;
}
private void setProjectPath(Path projectPath) {
this.projectPath = projectPath;
if (projectVersion != CURRENT_SETTINGS_VERSION) {
upgradeSettings(projectVersion);
}
name = projectPath.getFileName().toString();
name = name.substring(0, name.lastIndexOf('.'));
changed();
}
public Path getFilePath() {
return filesPath == null ? null : filesPath.get(0);
}
public void setFilePath(Path filePath) {
if (!filePath.equals(getFilePath())) {
this.filesPath = Arrays.asList(filePath);
changed();
}
}
private void changed() {
if (settings.isAutoSaveProject()) {
save();
}
else {
saved = false;
}
initial = false;
}
public String getName() {
return name;
}
public boolean isSaved() {
return saved;
}
public boolean isInitial() {
return initial;
}
public void saveAs(Path path) {
setProjectPath(path);
save();
}
public void save() {
try (BufferedWriter writer = Files.newBufferedWriter(getProjectPath())) {
writer.write(GSON.toJson(this));
saved = true;
} catch (Exception e) {
LOG.error("Error saving project", e);
}
}
public static JadxProject from(Path path, JadxSettings settings) {
try {
List<String> lines = Files.readAllLines(path);
if (!lines.isEmpty()) {
JadxProject project = GSON.fromJson(lines.get(0), JadxProject.class);
project.settings = settings;
project.setProjectPath(path);
project.saved = true;
return project;
}
} catch (Exception e) {
LOG.error("Error loading project", e);
}
return null;
}
private void upgradeSettings(int fromVersion) {
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
if (fromVersion == 0) {
fromVersion++;
}
projectVersion = CURRENT_SETTINGS_VERSION;
save();
}
}
...@@ -4,6 +4,8 @@ import java.awt.Font; ...@@ -4,6 +4,8 @@ import java.awt.Font;
import java.awt.GraphicsDevice; import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment; import java.awt.GraphicsEnvironment;
import java.awt.Window; import java.awt.Window;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
...@@ -27,25 +29,27 @@ import jadx.gui.utils.Utils; ...@@ -27,25 +29,27 @@ import jadx.gui.utils.Utils;
public class JadxSettings extends JadxCLIArgs { public class JadxSettings extends JadxCLIArgs {
private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class); private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class);
private static final String USER_HOME = System.getProperty("user.home"); private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));
private static final int RECENT_FILES_COUNT = 15; private static final int RECENT_PROJECTS_COUNT = 15;
private static final int CURRENT_SETTINGS_VERSION = 8; private static final int CURRENT_SETTINGS_VERSION = 9;
private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont(); private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont();
static final Set<String> SKIP_FIELDS = new HashSet<>(Arrays.asList( static final Set<String> SKIP_FIELDS = new HashSet<>(Arrays.asList(
"files", "input", "outDir", "outDirSrc", "outDirRes", "verbose", "printVersion", "printHelp" "files", "input", "outDir", "outDirSrc", "outDirRes", "verbose", "printVersion", "printHelp"
)); ));
private String lastOpenFilePath = USER_HOME; private Path lastSaveProjectPath = USER_HOME;
private String lastSaveFilePath = USER_HOME; private Path lastOpenFilePath = USER_HOME;
private Path lastSaveFilePath = USER_HOME;
private boolean flattenPackage = false; private boolean flattenPackage = false;
private boolean checkForUpdates = false; private boolean checkForUpdates = false;
private List<String> recentFiles = new ArrayList<>(); private List<Path> recentProjects = new ArrayList<>();
private String fontStr = ""; private String fontStr = "";
private String editorThemePath = ""; private String editorThemePath = "";
private LangLocale langLocale = NLS.defaultLocale(); private LangLocale langLocale = NLS.defaultLocale();
private boolean autoStartJobs = false; private boolean autoStartJobs = false;
protected String excludedPackages = ""; protected String excludedPackages = "";
private boolean autoSaveProject = false;
private boolean showHeapUsageBar = true; private boolean showHeapUsageBar = true;
...@@ -84,20 +88,29 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -84,20 +88,29 @@ public class JadxSettings extends JadxCLIArgs {
} }
} }
public String getLastOpenFilePath() { public Path getLastOpenFilePath() {
return lastOpenFilePath; return lastOpenFilePath;
} }
public void setLastOpenFilePath(String lastOpenFilePath) { public void setLastOpenFilePath(Path lastOpenFilePath) {
this.lastOpenFilePath = lastOpenFilePath; this.lastOpenFilePath = lastOpenFilePath;
partialSync(settings -> settings.lastOpenFilePath = JadxSettings.this.lastOpenFilePath); partialSync(settings -> settings.lastOpenFilePath = JadxSettings.this.lastOpenFilePath);
} }
public String getLastSaveFilePath() { public Path getLastSaveProjectPath() {
return lastSaveProjectPath;
}
public Path getLastSaveFilePath() {
return lastSaveFilePath; return lastSaveFilePath;
} }
public void setLastSaveFilePath(String lastSaveFilePath) { public void setLastSaveProjectPath(Path lastSaveProjectPath) {
this.lastSaveProjectPath = lastSaveProjectPath;
partialSync(settings -> settings.lastSaveProjectPath = JadxSettings.this.lastSaveProjectPath);
}
public void setLastSaveFilePath(Path lastSaveFilePath) {
this.lastSaveFilePath = lastSaveFilePath; this.lastSaveFilePath = lastSaveFilePath;
partialSync(settings -> settings.lastSaveFilePath = JadxSettings.this.lastSaveFilePath); partialSync(settings -> settings.lastSaveFilePath = JadxSettings.this.lastSaveFilePath);
} }
...@@ -120,18 +133,18 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -120,18 +133,18 @@ public class JadxSettings extends JadxCLIArgs {
sync(); sync();
} }
public Iterable<String> getRecentFiles() { public Iterable<Path> getRecentProjects() {
return recentFiles; return recentProjects;
} }
public void addRecentFile(String filePath) { public void addRecentProject(Path projectPath) {
recentFiles.remove(filePath); recentProjects.remove(projectPath);
recentFiles.add(0, filePath); recentProjects.add(0, projectPath);
int count = recentFiles.size(); int count = recentProjects.size();
if (count > RECENT_FILES_COUNT) { if (count > RECENT_PROJECTS_COUNT) {
recentFiles.subList(RECENT_FILES_COUNT, count).clear(); recentProjects.subList(RECENT_PROJECTS_COUNT, count).clear();
} }
partialSync(settings -> settings.recentFiles = recentFiles); partialSync(settings -> settings.recentProjects = recentProjects);
} }
public void saveWindowPos(Window window) { public void saveWindowPos(Window window) {
...@@ -265,6 +278,14 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -265,6 +278,14 @@ public class JadxSettings extends JadxCLIArgs {
this.autoStartJobs = autoStartJobs; this.autoStartJobs = autoStartJobs;
} }
public boolean isAutoSaveProject() {
return autoSaveProject;
}
public void setAutoSaveProject(boolean autoSaveProject) {
this.autoSaveProject = autoSaveProject;
}
public void setExportAsGradleProject(boolean exportAsGradleProject) { public void setExportAsGradleProject(boolean exportAsGradleProject) {
this.exportAsGradleProject = exportAsGradleProject; this.exportAsGradleProject = exportAsGradleProject;
} }
...@@ -343,6 +364,10 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -343,6 +364,10 @@ public class JadxSettings extends JadxCLIArgs {
outDir = null; outDir = null;
outDirSrc = null; outDirSrc = null;
outDirRes = null; outDirRes = null;
fromVersion++;
}
if (fromVersion == 8) {
fromVersion++;
} }
settingsVersion = CURRENT_SETTINGS_VERSION; settingsVersion = CURRENT_SETTINGS_VERSION;
sync(); sync();
......
package jadx.gui.settings; package jadx.gui.settings;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.nio.file.Path;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.ExclusionStrategy; import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes; import com.google.gson.FieldAttributes;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator; import com.google.gson.InstanceCreator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.JadxGUI; import jadx.gui.JadxGUI;
import jadx.gui.utils.PathTypeAdapter;
public class JadxSettingsAdapter { public class JadxSettingsAdapter {
...@@ -34,7 +37,10 @@ public class JadxSettingsAdapter { ...@@ -34,7 +37,10 @@ public class JadxSettingsAdapter {
return false; return false;
} }
}; };
private static final GsonBuilder GSON_BUILDER = new GsonBuilder().setExclusionStrategies(EXCLUDE_FIELDS); private static final GsonBuilder GSON_BUILDER = new GsonBuilder()
.setExclusionStrategies(EXCLUDE_FIELDS)
.registerTypeHierarchyAdapter(Path.class, PathTypeAdapter.singleton())
;
private static final Gson GSON = GSON_BUILDER.create(); private static final Gson GSON = GSON_BUILDER.create();
private JadxSettingsAdapter() { private JadxSettingsAdapter() {
......
...@@ -52,6 +52,7 @@ public class JadxSettingsWindow extends JDialog { ...@@ -52,6 +52,7 @@ public class JadxSettingsWindow extends JDialog {
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.add(makeDeobfuscationGroup()); panel.add(makeDeobfuscationGroup());
panel.add(makeDecompilationGroup()); panel.add(makeDecompilationGroup());
panel.add(makeProjectGroup());
panel.add(makeEditorGroup()); panel.add(makeEditorGroup());
panel.add(makeOtherGroup()); panel.add(makeOtherGroup());
...@@ -168,6 +169,18 @@ public class JadxSettingsWindow extends JDialog { ...@@ -168,6 +169,18 @@ public class JadxSettingsWindow extends JDialog {
connectedComponents.forEach(comp -> comp.setEnabled(enabled)); connectedComponents.forEach(comp -> comp.setEnabled(enabled));
} }
private SettingsGroup makeProjectGroup() {
JCheckBox autoSave = new JCheckBox();
autoSave.setSelected(settings.isAutoSaveProject());
autoSave.addItemListener(e ->
settings.setAutoSaveProject(e.getStateChange() == ItemEvent.SELECTED));
SettingsGroup group = new SettingsGroup(NLS.str("preferences.project"));
group.addRow(NLS.str("preferences.autoSave"), autoSave);
return group;
}
private SettingsGroup makeEditorGroup() { private SettingsGroup makeEditorGroup() {
JButton fontBtn = new JButton(NLS.str("preferences.select_font")); JButton fontBtn = new JButton(NLS.str("preferences.select_font"));
...@@ -186,9 +199,9 @@ public class JadxSettingsWindow extends JDialog { ...@@ -186,9 +199,9 @@ public class JadxSettingsWindow extends JDialog {
mainWindow.loadSettings(); mainWindow.loadSettings();
}); });
SettingsGroup other = new SettingsGroup(NLS.str("preferences.editor")); SettingsGroup group = new SettingsGroup(NLS.str("preferences.editor"));
JLabel fontLabel = other.addRow(getFontLabelStr(), fontBtn); JLabel fontLabel = group.addRow(getFontLabelStr(), fontBtn);
other.addRow(NLS.str("preferences.theme"), themesCbx); group.addRow(NLS.str("preferences.theme"), themesCbx);
fontBtn.addMouseListener(new MouseAdapter() { fontBtn.addMouseListener(new MouseAdapter() {
@Override @Override
...@@ -205,7 +218,7 @@ public class JadxSettingsWindow extends JDialog { ...@@ -205,7 +218,7 @@ public class JadxSettingsWindow extends JDialog {
} }
} }
}); });
return other; return group;
} }
private String getFontLabelStr() { private String getFontLabelStr() {
...@@ -263,7 +276,7 @@ public class JadxSettingsWindow extends JDialog { ...@@ -263,7 +276,7 @@ public class JadxSettingsWindow extends JDialog {
autoStartJobs.addItemListener(e -> settings.setAutoStartJobs(e.getStateChange() == ItemEvent.SELECTED)); autoStartJobs.addItemListener(e -> settings.setAutoStartJobs(e.getStateChange() == ItemEvent.SELECTED));
JCheckBox escapeUnicode = new JCheckBox(); JCheckBox escapeUnicode = new JCheckBox();
escapeUnicode.setSelected(settings.escapeUnicode()); escapeUnicode.setSelected(settings.isEscapeUnicode());
escapeUnicode.addItemListener(e -> { escapeUnicode.addItemListener(e -> {
settings.setEscapeUnicode(e.getStateChange() == ItemEvent.SELECTED); settings.setEscapeUnicode(e.getStateChange() == ItemEvent.SELECTED);
needReload(); needReload();
...@@ -333,12 +346,12 @@ public class JadxSettingsWindow extends JDialog { ...@@ -333,12 +346,12 @@ public class JadxSettingsWindow extends JDialog {
needReload(); needReload();
}); });
SettingsGroup other = new SettingsGroup(NLS.str("preferences.other")); SettingsGroup group = new SettingsGroup(NLS.str("preferences.other"));
other.addRow(NLS.str("preferences.language"), languageCbx); group.addRow(NLS.str("preferences.language"), languageCbx);
other.addRow(NLS.str("preferences.check_for_updates"), update); group.addRow(NLS.str("preferences.check_for_updates"), update);
other.addRow(NLS.str("preferences.cfg"), cfg); group.addRow(NLS.str("preferences.cfg"), cfg);
other.addRow(NLS.str("preferences.raw_cfg"), rawCfg); group.addRow(NLS.str("preferences.raw_cfg"), rawCfg);
return other; return group;
} }
private void needReload() { private void needReload() {
......
...@@ -77,15 +77,15 @@ public class ApkSignature extends JNode { ...@@ -77,15 +77,15 @@ public class ApkSignature extends JNode {
final String err = NLS.str("apkSignature.errors"); final String err = NLS.str("apkSignature.errors");
final String warn = NLS.str("apkSignature.warnings"); final String warn = NLS.str("apkSignature.warnings");
final String sigSucc = NLS.str("apkSignature.signatureSuccess"); final String sigSuccKey = "apkSignature.signatureSuccess";
final String sigFail = NLS.str("apkSignature.signatureFailed"); final String sigFailKey = "apkSignature.signatureFailed";
writeIssues(builder, err, result.getErrors()); writeIssues(builder, err, result.getErrors());
writeIssues(builder, warn, result.getWarnings()); writeIssues(builder, warn, result.getWarnings());
if (!result.getV1SchemeSigners().isEmpty()) { if (!result.getV1SchemeSigners().isEmpty()) {
builder.append("<h2>"); builder.append("<h2>");
builder.escape(String.format(result.isVerifiedUsingV1Scheme() ? sigSucc : sigFail, 1)); builder.escape(NLS.str(result.isVerifiedUsingV1Scheme() ? sigSuccKey : sigFailKey, 1));
builder.append("</h2>\n"); builder.append("</h2>\n");
builder.append("<blockquote>"); builder.append("<blockquote>");
...@@ -106,7 +106,7 @@ public class ApkSignature extends JNode { ...@@ -106,7 +106,7 @@ public class ApkSignature extends JNode {
} }
if (!result.getV2SchemeSigners().isEmpty()) { if (!result.getV2SchemeSigners().isEmpty()) {
builder.append("<h2>"); builder.append("<h2>");
builder.escape(String.format(result.isVerifiedUsingV2Scheme() ? sigSucc : sigFail, 2)); builder.escape(NLS.str(result.isVerifiedUsingV2Scheme() ? sigSuccKey : sigFailKey, 2));
builder.append("</h2>\n"); builder.append("</h2>\n");
builder.append("<blockquote>"); builder.append("<blockquote>");
......
...@@ -232,8 +232,7 @@ public abstract class CommonSearchDialog extends JDialog { ...@@ -232,8 +232,7 @@ public abstract class CommonSearchDialog extends JDialog {
} }
protected void updateProgressLabel() { protected void updateProgressLabel() {
String statusText = String.format( String statusText = NLS.str("search_dialog.info_label",
NLS.str("search_dialog.info_label"),
resultsModel.getDisplayedResultsStart(), resultsModel.getDisplayedResultsStart(),
resultsModel.getDisplayedResultsEnd(), resultsModel.getDisplayedResultsEnd(),
resultsModel.getResultCount() resultsModel.getResultCount()
......
...@@ -25,11 +25,9 @@ public class HeapUsageBar extends JProgressBar implements ActionListener { ...@@ -25,11 +25,9 @@ public class HeapUsageBar extends JProgressBar implements ActionListener {
private final transient Runtime runtime = Runtime.getRuntime(); private final transient Runtime runtime = Runtime.getRuntime();
private final transient Timer timer; private final transient Timer timer;
private final String textFormat;
private final double maxGB; private final double maxGB;
public HeapUsageBar() { public HeapUsageBar() {
this.textFormat = NLS.str("heapUsage.text");
setBorderPainted(false); setBorderPainted(false);
setStringPainted(true); setStringPainted(true);
setValue(10); setValue(10);
...@@ -54,7 +52,7 @@ public class HeapUsageBar extends JProgressBar implements ActionListener { ...@@ -54,7 +52,7 @@ public class HeapUsageBar extends JProgressBar implements ActionListener {
long used = runtime.totalMemory() - runtime.freeMemory(); long used = runtime.totalMemory() - runtime.freeMemory();
int usedKB = (int) (used / 1024); int usedKB = (int) (used / 1024);
setValue(usedKB); setValue(usedKB);
setString(String.format(textFormat, (usedKB / TWO_TO_20), maxGB)); setString(NLS.str("heapUsage.text", (usedKB / TWO_TO_20), maxGB));
if ((used + Utils.MIN_FREE_MEMORY) > runtime.maxMemory()) { if ((used + Utils.MIN_FREE_MEMORY) > runtime.maxMemory()) {
setForeground(RED); setForeground(RED);
......
...@@ -62,7 +62,7 @@ public class MainDropTarget implements DropTargetListener { ...@@ -62,7 +62,7 @@ public class MainDropTarget implements DropTargetListener {
if (!transferData.isEmpty()) { if (!transferData.isEmpty()) {
dtde.dropComplete(true); dtde.dropComplete(true);
// load first file // load first file
mainWindow.openFile(transferData.get(0)); mainWindow.open(transferData.get(0).toPath());
} }
} catch (Exception e) { } catch (Exception e) {
LOG.error("File drop operation failed", e); LOG.error("File drop operation failed", e);
......
...@@ -61,12 +61,14 @@ public class NLS { ...@@ -61,12 +61,14 @@ public class NLS {
i18nMessagesMap.put(locale, bundle); i18nMessagesMap.put(locale, bundle);
} }
public static String str(String key) { public static String str(String key, Object... parameters) {
String value;
try { try {
return localizedMessagesMap.getString(key); value = localizedMessagesMap.getString(key);
} catch (MissingResourceException e) { } catch (MissingResourceException e) {
return fallbackMessagesMap.getString(key); // definitely exists value = fallbackMessagesMap.getString(key); // definitely exists
} }
return String.format(value, parameters);
} }
public static String str(String key, LangLocale locale) { public static String str(String key, LangLocale locale) {
......
package jadx.gui.utils;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
public class PathTypeAdapter {
private static TypeAdapter<Path> SINGLETON;
public static TypeAdapter<Path> singleton() {
if (SINGLETON == null) {
SINGLETON = new TypeAdapter<Path>() {
@Override
public void write(JsonWriter out, Path value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(value.toAbsolutePath().toString());
}
}
@Override
public Path read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
return Paths.get(in.nextString());
}
};
}
return SINGLETON;
}
private PathTypeAdapter() {
}
}
...@@ -2,8 +2,8 @@ language.name=English ...@@ -2,8 +2,8 @@ language.name=English
menu.file=File menu.file=File
menu.view=View menu.view=View
menu.recent_files=Recent Files menu.recent_projects=Recent projects
menu.no_recent_files=No recent files menu.no_recent_projects=No recent projects
menu.preferences=Preferences menu.preferences=Preferences
menu.sync=Sync with editor menu.sync=Sync with editor
menu.flatten=Show flatten packages menu.flatten=Show flatten packages
...@@ -20,17 +20,18 @@ menu.update_label=New version %s available! ...@@ -20,17 +20,18 @@ menu.update_label=New version %s available!
file.open_action=Open file... file.open_action=Open file...
file.open_title=Open file file.open_title=Open file
file.new_project=New project
file.save_project=Save project
file.save_project_as=Save project as...
file.save_all=Save all file.save_all=Save all
file.export_gradle=Save as gradle project file.export_gradle=Save as gradle project
file.save_all_msg=Select directory for save decompiled sources file.save_all_msg=Select directory for save decompiled sources
file.select=Select
file.exit=Exit file.exit=Exit
tree.sources_title=Source code tree.sources_title=Source code
tree.resources_title=Resources tree.resources_title=Resources
tree.loading=Loading... tree.loading=Loading...
search=Search
search.previous=Previous search.previous=Previous
search.next=Next search.next=Next
search.mark_all=Mark All search.mark_all=Mark All
...@@ -79,6 +80,7 @@ preferences.title=Preferences ...@@ -79,6 +80,7 @@ preferences.title=Preferences
preferences.deobfuscation=Deobfuscation preferences.deobfuscation=Deobfuscation
preferences.editor=Editor preferences.editor=Editor
preferences.decompile=Decompilation preferences.decompile=Decompilation
preferences.project=Project
preferences.other=Other preferences.other=Other
preferences.language=Language preferences.language=Language
preferences.check_for_updates=Check for updates on startup preferences.check_for_updates=Check for updates on startup
...@@ -89,6 +91,7 @@ preferences.replaceConsts=Replace constants ...@@ -89,6 +91,7 @@ preferences.replaceConsts=Replace constants
preferences.respectBytecodeAccessModifiers=Respect bytecode access modifiers preferences.respectBytecodeAccessModifiers=Respect bytecode access modifiers
preferences.useImports=Use import statements preferences.useImports=Use import statements
preferences.skipResourcesDecode=Don't decode resources preferences.skipResourcesDecode=Don't decode resources
preferences.autoSave=Auto save
preferences.threads=Processing threads count preferences.threads=Processing threads count
preferences.excludedPackages=Excluded packages preferences.excludedPackages=Excluded packages
preferences.excludedPackages.tooltip=List of space separated package names that will not be decompiled or indexed (saves RAM) preferences.excludedPackages.tooltip=List of space separated package names that will not be decompiled or indexed (saves RAM)
...@@ -116,6 +119,8 @@ msg.saving_sources=Saving sources... ...@@ -116,6 +119,8 @@ msg.saving_sources=Saving sources...
msg.language_changed_title=Language changed msg.language_changed_title=Language changed
msg.language_changed=New language will be displayed the next time application starts. msg.language_changed=New language will be displayed the next time application starts.
msg.index_not_initialized=Index not initialized, search will be disabled! msg.index_not_initialized=Index not initialized, search will be disabled!
msg.project_error_title=Error
msg.project_error=Project could not be loaded
popup.undo=Undo popup.undo=Undo
popup.redo=Redo popup.redo=Redo
...@@ -127,11 +132,15 @@ popup.select_all=Select All ...@@ -127,11 +132,15 @@ popup.select_all=Select All
popup.find_usage=Find Usage popup.find_usage=Find Usage
popup.exclude=Exclude popup.exclude=Exclude
confirm.save_as_title=Confirm Save as
confirm.save_as_message=%s already exists.\nDo you want to replace it?
confirm.not_saved_title=Save project
confirm.not_saved_message=Save the current project before opening the new one?
certificate.title=Certificate certificate.title=Certificate
certificate.cert_type=Type certificate.cert_type=Type
certificate.serialSigVer=Version certificate.serialSigVer=Version
certificate.serialNumber=Serial number certificate.serialNumber=Serial number
certificate.cert_issuer=Issuer
certificate.cert_subject=Subject certificate.cert_subject=Subject
certificate.serialValidFrom=Valid from certificate.serialValidFrom=Valid from
certificate.serialValidUntil=Valid until certificate.serialValidUntil=Valid until
......
...@@ -2,8 +2,8 @@ language.name=Español ...@@ -2,8 +2,8 @@ language.name=Español
menu.file=Archivo menu.file=Archivo
menu.view=Vista menu.view=Vista
menu.recent_files=Archivos recientes #menu.recent_projects=
menu.no_recent_files=No hay archivos recientes #menu.no_recent_projects=
menu.preferences=Preferencias menu.preferences=Preferencias
menu.sync=Sincronizar con el editor menu.sync=Sincronizar con el editor
menu.flatten=Mostrar paquetes en vista plana menu.flatten=Mostrar paquetes en vista plana
...@@ -20,17 +20,18 @@ menu.update_label=¡Nueva versión %s disponible! ...@@ -20,17 +20,18 @@ menu.update_label=¡Nueva versión %s disponible!
file.open_action=Abrir archivo... file.open_action=Abrir archivo...
file.open_title=Abrir archivo file.open_title=Abrir archivo
#file.new_project=
#file.save_project=
#file.save_project_as=
file.save_all=Guardar todo file.save_all=Guardar todo
file.export_gradle=Guardar como proyecto Gradle file.export_gradle=Guardar como proyecto Gradle
file.save_all_msg=Seleccionar carpeta para guardar fuentes descompiladas file.save_all_msg=Seleccionar carpeta para guardar fuentes descompiladas
file.select=Seleccionar
file.exit=Salir file.exit=Salir
tree.sources_title=Código fuente tree.sources_title=Código fuente
tree.resources_title=Recursos tree.resources_title=Recursos
tree.loading=Cargando... tree.loading=Cargando...
search=Buscar
search.previous=Anterior search.previous=Anterior
search.next=Siguiente search.next=Siguiente
search.mark_all=Marcar todo search.mark_all=Marcar todo
...@@ -79,6 +80,7 @@ preferences.title=Preferencias ...@@ -79,6 +80,7 @@ preferences.title=Preferencias
preferences.deobfuscation=Desofuscación preferences.deobfuscation=Desofuscación
preferences.editor=Editor preferences.editor=Editor
preferences.decompile=Descompilación preferences.decompile=Descompilación
#preferences.project=
preferences.other=Otros preferences.other=Otros
preferences.language=Idioma preferences.language=Idioma
preferences.check_for_updates=Buscar actualizaciones al iniciar preferences.check_for_updates=Buscar actualizaciones al iniciar
...@@ -89,6 +91,7 @@ preferences.replaceConsts=Reemplazar constantes ...@@ -89,6 +91,7 @@ preferences.replaceConsts=Reemplazar constantes
#preferences.respectBytecodeAccessModifiers= #preferences.respectBytecodeAccessModifiers=
#preferences.useImports= #preferences.useImports=
preferences.skipResourcesDecode=No descodificar recursos preferences.skipResourcesDecode=No descodificar recursos
#preferences.autoSave=
preferences.threads=Número de hilos a procesar preferences.threads=Número de hilos a procesar
#preferences.excludedPackages= #preferences.excludedPackages=
#preferences.excludedPackages.tooltip= #preferences.excludedPackages.tooltip=
...@@ -116,6 +119,8 @@ msg.saving_sources=Guardando fuente... ...@@ -116,6 +119,8 @@ msg.saving_sources=Guardando fuente...
msg.language_changed_title=Idioma cambiado msg.language_changed_title=Idioma cambiado
msg.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicación se inicie. msg.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicación se inicie.
msg.index_not_initialized=Índice no inicializado, ¡la bósqueda se desactivará! msg.index_not_initialized=Índice no inicializado, ¡la bósqueda se desactivará!
#msg.project_error_title=
#msg.project_error=
popup.undo=Deshacer popup.undo=Deshacer
popup.redo=Rehacer popup.redo=Rehacer
...@@ -127,11 +132,15 @@ popup.select_all=Seleccionar todo ...@@ -127,11 +132,15 @@ popup.select_all=Seleccionar todo
#popup.find_usage= #popup.find_usage=
#popup.exclude= #popup.exclude=
#confirm.save_as_title=
#confirm.save_as_message=
#confirm.not_saved_title=
#confirm.not_saved_message=
certificate.title=Certificado certificate.title=Certificado
certificate.cert_type=Tipo certificate.cert_type=Tipo
certificate.serialSigVer=Versión certificate.serialSigVer=Versión
certificate.serialNumber=Número de serial certificate.serialNumber=Número de serial
certificate.cert_issuer=Issuer
certificate.cert_subject=Subject certificate.cert_subject=Subject
certificate.serialValidFrom=Válido desde certificate.serialValidFrom=Válido desde
certificate.serialValidUntil=Válido hasta certificate.serialValidUntil=Válido hasta
......
...@@ -2,8 +2,8 @@ language.name=中文(简体) ...@@ -2,8 +2,8 @@ language.name=中文(简体)
menu.file=文件 menu.file=文件
menu.view=视图 menu.view=视图
menu.recent_files=最近打开的文件 #menu.recent_projects=
menu.no_recent_files=无最近打开的文件 #menu.no_recent_projects=
menu.preferences=首选项 menu.preferences=首选项
menu.sync=与编辑器同步 menu.sync=与编辑器同步
menu.flatten=展开显示代码包 menu.flatten=展开显示代码包
...@@ -20,17 +20,18 @@ menu.update_label=发现新版本 %s! ...@@ -20,17 +20,18 @@ menu.update_label=发现新版本 %s!
file.open_action=打开文件... file.open_action=打开文件...
file.open_title=打开文件 file.open_title=打开文件
#file.new_project=
#file.save_project=
#file.save_project_as=
file.save_all=全部保存 file.save_all=全部保存
file.export_gradle=另存为 Gradle 项目 file.export_gradle=另存为 Gradle 项目
file.save_all_msg=选择反编译资源路径 file.save_all_msg=选择反编译资源路径
file.select=选择
file.exit=退出 file.exit=退出
tree.sources_title=源代码 tree.sources_title=源代码
tree.resources_title=资源文件 tree.resources_title=资源文件
tree.loading=稍等... tree.loading=稍等...
search=搜索
search.previous=上一个 search.previous=上一个
search.next=下一个 search.next=下一个
search.mark_all=标记全部 search.mark_all=标记全部
...@@ -79,6 +80,7 @@ preferences.title=首选项 ...@@ -79,6 +80,7 @@ preferences.title=首选项
preferences.deobfuscation=反混淆 preferences.deobfuscation=反混淆
preferences.editor=编辑器 preferences.editor=编辑器
preferences.decompile=反编译 preferences.decompile=反编译
#preferences.project=
preferences.other=其他 preferences.other=其他
preferences.language=语言 preferences.language=语言
preferences.check_for_updates=启动时检查更新 preferences.check_for_updates=启动时检查更新
...@@ -89,6 +91,7 @@ preferences.replaceConsts=替换常量 ...@@ -89,6 +91,7 @@ preferences.replaceConsts=替换常量
preferences.respectBytecodeAccessModifiers=遵守字节码访问修饰符 preferences.respectBytecodeAccessModifiers=遵守字节码访问修饰符
preferences.useImports=使用 import 语句 preferences.useImports=使用 import 语句
preferences.skipResourcesDecode=不反编译资源文件 preferences.skipResourcesDecode=不反编译资源文件
#preferences.autoSave=
preferences.threads=并行线程数 preferences.threads=并行线程数
preferences.excludedPackages=排除的包 preferences.excludedPackages=排除的包
preferences.excludedPackages.tooltip=将不被解压缩或索引的以空格分隔的包名称列表(节省 RAM) preferences.excludedPackages.tooltip=将不被解压缩或索引的以空格分隔的包名称列表(节省 RAM)
...@@ -116,6 +119,8 @@ msg.saving_sources=正在导出源代码... ...@@ -116,6 +119,8 @@ msg.saving_sources=正在导出源代码...
msg.language_changed_title=语言已更改 msg.language_changed_title=语言已更改
msg.language_changed=在下次启动时将会显示新的语言。 msg.language_changed=在下次启动时将会显示新的语言。
msg.index_not_initialized=索引尚未初始化,无法进行搜索! msg.index_not_initialized=索引尚未初始化,无法进行搜索!
#msg.project_error_title=
#msg.project_error=
popup.undo=撤销 popup.undo=撤销
popup.redo=重做 popup.redo=重做
...@@ -127,11 +132,15 @@ popup.select_all=全选 ...@@ -127,11 +132,15 @@ popup.select_all=全选
popup.find_usage=查找用例 popup.find_usage=查找用例
#popup.exclude= #popup.exclude=
#confirm.save_as_title=
#confirm.save_as_message=
#confirm.not_saved_title=
#confirm.not_saved_message=
certificate.title=证书 certificate.title=证书
certificate.cert_type=类型 certificate.cert_type=类型
certificate.serialSigVer=版本 certificate.serialSigVer=版本
certificate.serialNumber=序列号 certificate.serialNumber=序列号
certificate.cert_issuer=颁发者
certificate.cert_subject=主题 certificate.cert_subject=主题
certificate.serialValidFrom=有效期始 certificate.serialValidFrom=有效期始
certificate.serialValidUntil=有效期至 certificate.serialValidUntil=有效期至
......
package jadx.gui; package jadx.gui;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException; import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class TestI18n { public class TestI18n {
private static Path guiJavaPath;
private static Path i18nPath;
private List<String> reference; private List<String> reference;
private String referenceName; private String referenceName;
@BeforeAll
public static void init() {
i18nPath = Paths.get("src/main/resources/i18n");
assertTrue(Files.exists(i18nPath));
guiJavaPath = Paths.get("src/main/java");
assertTrue(Files.exists(guiJavaPath));
}
@Test @Test
public void filesExactlyMatch() throws IOException { public void filesExactlyMatch() throws IOException {
Path path = Paths.get("./src/main/resources/i18n"); Files.list(i18nPath).forEach(p -> {
assertTrue(Files.exists(path));
Files.list(path).forEach(p -> {
List<String> lines; List<String> lines;
try { try {
lines = Files.readAllLines(p); lines = Files.readAllLines(p);
...@@ -45,12 +64,12 @@ public class TestI18n { ...@@ -45,12 +64,12 @@ public class TestI18n {
if (p0 != -1) { if (p0 != -1) {
String prefix = line.substring(0, p0 + 1); String prefix = line.substring(0, p0 + 1);
if (i >= lines.size() || !trimComment(lines.get(i)).startsWith(prefix)) { if (i >= lines.size() || !trimComment(lines.get(i)).startsWith(prefix)) {
fail(path, i + 1); failLine(path, i + 1);
} }
} }
} }
if (lines.size() != reference.size()) { if (lines.size() != reference.size()) {
fail(path, reference.size()); failLine(path, reference.size());
} }
} }
...@@ -58,7 +77,37 @@ public class TestI18n { ...@@ -58,7 +77,37 @@ public class TestI18n {
return string.startsWith("#") ? string.substring(1) : string; return string.startsWith("#") ? string.substring(1) : string;
} }
private void fail(Path path, int line) { private void failLine(Path path, int line) {
Assertions.fail("I18n files " + path.getFileName() + " and " + referenceName + " differ in line " + line); fail("I18n files " + path.getFileName() + " and " + referenceName + " differ in line " + line);
}
@Test
public void keyIsUsed() throws IOException {
Properties properties = new Properties();
try (Reader reader = Files.newBufferedReader(i18nPath.resolve("Messages_en_US.properties"))) {
properties.load(reader);
}
Set<String> keys = new HashSet<>();
for (Object key : properties.keySet()) {
keys.add("\"" + key + '"');
}
Files.walk(guiJavaPath).filter(p -> Files.isRegularFile(p)).forEach(p -> {
try {
List<String> lines = Files.readAllLines(p);
for (String line : lines) {
for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
if (line.contains(it.next())) {
it.remove();
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
});
assertThat("keys not used", keys, empty());
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment