gui: add find usage feature, run decompilation and index jobs in background (#74, #75)

This commit is contained in:
Skylot 2015-07-26 18:06:26 +03:00
parent 2d8d416483
commit bc73010d4e
33 changed files with 1521 additions and 295 deletions

24
NOTICE
View File

@ -144,7 +144,8 @@ THE POSSIBILITY OF SUCH DAMAGE.
Jadx-gui components
===================
RSyntaxTextArea library licensed under modified BSD license:
RSyntaxTextArea library (https://github.com/bobbylight/RSyntaxTextArea)
licensed under modified BSD license:
*******************************************************************************
Copyright (c) 2012, Robert Futrell
@ -174,8 +175,27 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************
Concurrent Trees (https://code.google.com/p/concurrent-trees/)
licenced under Apache License 2.0:
*******************************************************************************
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*******************************************************************************
Icons copied from several places:
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed
under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/

View File

@ -7,7 +7,7 @@ import jadx.core.dex.nodes.MethodNode;
public class FieldInitAttr implements IAttribute {
public static FieldInitAttr NULL_VALUE = constValue(null);
public static final FieldInitAttr NULL_VALUE = constValue(null);
public enum InitType {
CONST,

View File

@ -5,7 +5,7 @@ mainClassName = 'jadx.gui.JadxGUI'
dependencies {
compile(project(":jadx-core"))
compile(project(":jadx-cli"))
compile 'com.fifesoft:rsyntaxtextarea:2.5.6'
compile 'com.fifesoft:rsyntaxtextarea:2.5.7'
compile 'com.google.code.gson:gson:2.3.1'
compile files('libs/jfontchooser-1.0.5.jar')
compile 'com.googlecode.concurrent-trees:concurrent-trees:2.4.0'

View File

@ -0,0 +1,87 @@
package jadx.gui.jobs;
import jadx.gui.JadxWrapper;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class BackgroundJob {
private static final Logger LOG = LoggerFactory.getLogger(DecompileJob.class);
protected final JadxWrapper wrapper;
private final ThreadPoolExecutor executor;
private Future<Boolean> future;
public BackgroundJob(JadxWrapper wrapper, int threadsCount) {
this.wrapper = wrapper;
this.executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
}
public synchronized Future<Boolean> process() {
if (future != null) {
return future;
}
ExecutorService shutdownExecutor = Executors.newSingleThreadExecutor();
FutureTask<Boolean> task = new ShutdownTask();
shutdownExecutor.execute(task);
shutdownExecutor.shutdown();
future = task;
return future;
}
private class ShutdownTask extends FutureTask<Boolean> {
public ShutdownTask() {
super(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
runJob();
executor.shutdown();
return executor.awaitTermination(5, TimeUnit.MINUTES);
}
});
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
executor.shutdownNow();
return super.cancel(mayInterruptIfRunning);
}
}
protected abstract void runJob();
public abstract String getInfoString();
protected void addTask(Runnable runnable) {
executor.execute(runnable);
}
public void processAndWait() {
try {
process().get();
} catch (Exception e) {
LOG.error("BackgroundJob.processAndWait failed", e);
}
}
public synchronized boolean isComplete() {
try {
return future != null && future.isDone();
} catch (Exception e) {
LOG.error("BackgroundJob.isComplete failed", e);
return false;
}
}
public int getProgress() {
return (int) (executor.getCompletedTaskCount() * 100 / (double) executor.getTaskCount());
}
}

View File

@ -0,0 +1,99 @@
package jadx.gui.jobs;
import jadx.gui.ui.ProgressPanel;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.TextSearchIndex;
import jadx.gui.utils.Utils;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BackgroundWorker extends SwingWorker<Void, Void> {
private static final Logger LOG = LoggerFactory.getLogger(BackgroundWorker.class);
private final CacheObject cache;
private final ProgressPanel progressPane;
public BackgroundWorker(CacheObject cacheObject, ProgressPanel progressPane) {
this.cache = cacheObject;
this.progressPane = progressPane;
}
public void exec() {
if (isDone()) {
return;
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressPane.setVisible(true);
}
});
addPropertyChangeListener(progressPane);
execute();
}
public void stop() {
if (isDone()) {
return;
}
LOG.debug("Canceling background jobs ...");
cancel(false);
}
@Override
protected Void doInBackground() throws Exception {
try {
System.gc();
LOG.debug("Memory usage: Before decompile: {}", Utils.memoryInfo());
runJob(cache.getDecompileJob());
LOG.debug("Memory usage: Before index: {}", Utils.memoryInfo());
runJob(cache.getIndexJob());
LOG.debug("Memory usage: After index: {}", Utils.memoryInfo());
System.gc();
LOG.debug("Memory usage: After gc: {}", Utils.memoryInfo());
TextSearchIndex searchIndex = cache.getTextIndex();
if (cache.getIndexJob().isUseFastSearch()
&& searchIndex != null
&& searchIndex.getSkippedCount() > 0) {
LOG.warn("Indexing of some classes skipped, count: {}, low memory: {}",
searchIndex.getSkippedCount(), Utils.memoryInfo());
}
} catch (Exception e) {
LOG.error("Exception in background worker", e);
}
return null;
}
private void runJob(BackgroundJob job) {
if (isCancelled()) {
return;
}
progressPane.changeLabel(this, job.getInfoString());
Future<Boolean> future = job.process();
while (!future.isDone()) {
try {
setProgress(job.getProgress());
if (isCancelled()) {
future.cancel(false);
}
Thread.sleep(500);
} catch (Exception e) {
LOG.error("Background worker error", e);
}
}
}
@Override
protected void done() {
progressPane.setVisible(false);
}
}

View File

@ -0,0 +1,28 @@
package jadx.gui.jobs;
import jadx.api.JavaClass;
import jadx.gui.JadxWrapper;
public class DecompileJob extends BackgroundJob {
public DecompileJob(JadxWrapper wrapper, int threadsCount) {
super(wrapper, threadsCount);
}
protected void runJob() {
for (final JavaClass cls : wrapper.getClasses()) {
addTask(new Runnable() {
@Override
public void run() {
cls.decompile();
}
});
}
}
@Override
public String getInfoString() {
return "Decompiling: ";
}
}

View File

@ -0,0 +1,76 @@
package jadx.gui.jobs;
import jadx.api.JavaClass;
import jadx.core.codegen.CodeWriter;
import jadx.gui.JadxWrapper;
import jadx.gui.settings.JadxSettings;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.CodeUsageInfo;
import jadx.gui.utils.TextSearchIndex;
import jadx.gui.utils.Utils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class IndexJob extends BackgroundJob {
private static final Logger LOG = LoggerFactory.getLogger(IndexJob.class);
private final CacheObject cache;
private final boolean useFastSearch;
public IndexJob(JadxWrapper wrapper, JadxSettings settings, CacheObject cache) {
super(wrapper, settings.getThreadsCount());
this.useFastSearch = settings.isUseFastSearch();
this.cache = cache;
}
protected void runJob() {
final TextSearchIndex index = new TextSearchIndex();
final CodeUsageInfo usageInfo = new CodeUsageInfo();
cache.setTextIndex(index);
cache.setUsageInfo(usageInfo);
for (final JavaClass cls : wrapper.getClasses()) {
addTask(new Runnable() {
@Override
public void run() {
try {
index.indexNames(cls);
CodeLinesInfo linesInfo = new CodeLinesInfo(cls);
String[] lines = splitIntoLines(cls);
usageInfo.processClass(cls, linesInfo, lines);
if (useFastSearch && Utils.isFreeMemoryAvailable()) {
index.indexCode(cls, linesInfo, lines);
} else {
index.classCodeIndexSkipped(cls);
}
} catch (Exception e) {
LOG.error("Index error in class: {}", cls.getFullName(), e);
}
}
});
}
}
@NotNull
protected String[] splitIntoLines(JavaClass cls) {
String[] lines = cls.getCode().split(CodeWriter.NL);
int count = lines.length;
for (int i = 0; i < count; i++) {
lines[i] = lines[i].trim();
}
return lines;
}
@Override
public String getInfoString() {
return "Indexing: ";
}
public boolean isUseFastSearch() {
return useFastSearch;
}
}

View File

@ -2,7 +2,6 @@ package jadx.gui.settings;
import jadx.cli.JadxCLIArgs;
import javax.swing.JLabel;
import java.awt.Font;
import java.util.ArrayList;
import java.util.Arrays;
@ -10,12 +9,14 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
public class JadxSettings extends JadxCLIArgs {
private static final String USER_HOME = System.getProperty("user.home");
private static final int RECENT_FILES_COUNT = 15;
private static final Font DEFAULT_FONT = new JLabel().getFont();
private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont();
static final Set<String> SKIP_FIELDS = new HashSet<String>(Arrays.asList(
"files", "input", "outputDir", "verbose", "printHelp"
@ -27,6 +28,7 @@ public class JadxSettings extends JadxCLIArgs {
private boolean checkForUpdates = true;
private List<String> recentFiles = new ArrayList<String>();
private String fontStr = "";
private boolean useFastSearch = false;
public void sync() {
JadxSettingsAdapter.store(this);
@ -73,10 +75,8 @@ public class JadxSettings extends JadxCLIArgs {
}
public void addRecentFile(String filePath) {
if (recentFiles.contains(filePath)) {
return;
}
recentFiles.add(filePath);
recentFiles.remove(filePath);
recentFiles.add(0, filePath);
int count = recentFiles.size();
if (count > RECENT_FILES_COUNT) {
recentFiles.subList(0, count - RECENT_FILES_COUNT).clear();
@ -136,6 +136,14 @@ public class JadxSettings extends JadxCLIArgs {
this.deobfuscationUseSourceNameAsAlias = useSourceNameAsAlias;
}
public boolean isUseFastSearch() {
return useFastSearch;
}
public void setUseFastSearch(boolean useFastSearch) {
this.useFastSearch = useFastSearch;
}
public Font getFont() {
if (fontStr.isEmpty()) {
return DEFAULT_FONT;

View File

@ -2,6 +2,7 @@ package jadx.gui.settings;
import jadx.gui.JadxGUI;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.prefs.Preferences;
@ -25,7 +26,9 @@ public class JadxSettingsAdapter {
private static ExclusionStrategy EXCLUDE_FIELDS = new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return JadxSettings.SKIP_FIELDS.contains(f.getName());
return JadxSettings.SKIP_FIELDS.contains(f.getName())
|| f.hasModifier(Modifier.PUBLIC)
|| f.hasModifier(Modifier.TRANSIENT);
}
@Override

View File

@ -239,6 +239,14 @@ public class JadxSettingsWindow extends JDialog {
}
});
JCheckBox fastSearch = new JCheckBox();
fastSearch.setSelected(settings.isUseFastSearch());
fastSearch.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
settings.setUseFastSearch(e.getStateChange() == ItemEvent.SELECTED);
}
});
SettingsGroup other = new SettingsGroup(NLS.str("preferences.other"));
other.addRow(NLS.str("preferences.check_for_updates"), update);
other.addRow(NLS.str("preferences.threads"), threadsCount);
@ -248,6 +256,7 @@ public class JadxSettingsWindow extends JDialog {
other.addRow(NLS.str("preferences.cfg"), cfg);
other.addRow(NLS.str("preferences.raw_cfg"), rawCfg);
other.addRow(NLS.str("preferences.font"), fontBtn);
other.addRow(NLS.str("preferences.fast_search"), fastSearch);
return other;
}

View File

@ -1,27 +1,50 @@
package jadx.gui.treemodel;
import jadx.api.JavaClass;
import jadx.gui.utils.Utils;
import jadx.api.JavaNode;
import javax.swing.Icon;
import javax.swing.ImageIcon;
public class CodeNode extends JClass {
public class CodeNode extends JNode {
private static final ImageIcon ICON = Utils.openIcon("file_obj");
private static final long serialVersionUID = 1658650786734966545L;
private final JNode jNode;
private final JClass jParent;
private final String line;
private final int lineNum;
public CodeNode(JavaClass javaClass, int lineNum, String line) {
super(javaClass, (JClass) makeFrom(javaClass.getDeclaringClass()));
public CodeNode(JavaNode javaNode, int lineNum, String line) {
this.jNode = makeFrom(javaNode);
this.jParent = jNode.getJParent();
this.line = line;
this.lineNum = lineNum;
}
@Override
public Icon getIcon() {
return ICON;
return jNode.getIcon();
}
@Override
public JavaNode getJavaNode() {
return jNode.getJavaNode();
}
@Override
public JClass getJParent() {
return getRootClass();
}
@Override
public JClass getRootClass() {
JClass parent = jParent;
if (parent != null) {
return parent.getRootClass();
}
if (jNode instanceof JClass) {
return (JClass) jNode;
}
return null;
}
@Override
@ -29,18 +52,23 @@ public class CodeNode extends JClass {
return lineNum;
}
@Override
public String makeDescString() {
return line;
}
@Override
public boolean hasDescString() {
return true;
}
@Override
public String makeString() {
return getCls().getFullName() + ":" + lineNum + " " + line;
return jNode.makeLongString();
}
@Override
public String makeLongString() {
return makeString();
}
@Override
public String toString() {
return makeString();
}
}

View File

@ -3,6 +3,7 @@ package jadx.gui.treemodel;
import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.core.dex.info.AccessInfo;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
@ -103,6 +104,11 @@ public class JClass extends JNode {
return ICON_CLASS_DEFAULT;
}
@Override
public JavaNode getJavaNode() {
return cls;
}
@Override
public JClass getJParent() {
return jParent;
@ -116,6 +122,11 @@ public class JClass extends JNode {
return jParent.getRootClass();
}
@Override
public String getName() {
return cls.getName();
}
public String getFullName() {
return cls.getFullName();
}

View File

@ -1,6 +1,7 @@
package jadx.gui.treemodel;
import jadx.api.JavaField;
import jadx.api.JavaNode;
import jadx.core.dex.info.AccessInfo;
import jadx.gui.utils.OverlayIcon;
import jadx.gui.utils.Utils;
@ -27,6 +28,11 @@ public class JField extends JNode {
this.jParent = jClass;
}
@Override
public JavaNode getJavaNode() {
return field;
}
@Override
public JClass getJParent() {
return jParent;
@ -64,4 +70,14 @@ public class JField extends JNode {
public String makeLongString() {
return Utils.typeFormat(field.getFullName(), field.getType());
}
@Override
public int hashCode() {
return field.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof JField && field.equals(((JField) o).field);
}
}

View File

@ -1,6 +1,7 @@
package jadx.gui.treemodel;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.gui.utils.OverlayIcon;
@ -29,11 +30,20 @@ public class JMethod extends JNode {
this.jParent = jClass;
}
@Override
public JavaNode getJavaNode() {
return mth;
}
@Override
public JClass getJParent() {
return jParent;
}
public ArgType getReturnType() {
return mth.getReturnType();
}
@Override
public JClass getRootClass() {
return jParent.getRootClass();
@ -57,7 +67,7 @@ public class JMethod extends JNode {
return icon;
}
private String makeBaseString() {
String makeBaseString() {
if (mth.isClassInit()) {
return "{...}";
}
@ -80,12 +90,22 @@ public class JMethod extends JNode {
@Override
public String makeString() {
return Utils.typeFormat(makeBaseString(), mth.getReturnType());
return Utils.typeFormat(makeBaseString(), getReturnType());
}
@Override
public String makeLongString() {
String name = mth.getDeclaringClass().getFullName() + "." + makeBaseString();
return Utils.typeFormat(name, mth.getReturnType());
return Utils.typeFormat(name, getReturnType());
}
@Override
public int hashCode() {
return mth.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof JMethod && mth.equals(((JMethod) o).mth);
}
}

View File

@ -13,20 +13,20 @@ import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
public abstract class JNode extends DefaultMutableTreeNode {
public static JNode makeFrom(JavaNode node) {
if (node == null) {
return null;
}
if (node instanceof JavaClass) {
JClass p = (JClass) makeFrom(node.getDeclaringClass());
return new JClass((JavaClass) node, p);
}
if (node instanceof JavaMethod) {
JavaMethod mth = (JavaMethod) node;
return new JMethod(mth, new JClass(mth.getDeclaringClass()));
return new JMethod(mth, (JClass) makeFrom(mth.getDeclaringClass()));
}
if (node instanceof JavaField) {
JavaField fld = (JavaField) node;
return new JField(fld, new JClass(fld.getDeclaringClass()));
}
if (node == null) {
return null;
return new JField(fld, (JClass) makeFrom(fld.getDeclaringClass()));
}
throw new JadxRuntimeException("Unknown type for JavaNode: " + node.getClass());
}
@ -40,6 +40,10 @@ public abstract class JNode extends DefaultMutableTreeNode {
return null;
}
public JavaNode getJavaNode() {
return null;
}
public String getContent() {
return null;
}
@ -58,8 +62,24 @@ public abstract class JNode extends DefaultMutableTreeNode {
public abstract Icon getIcon();
public String getName() {
JavaNode javaNode = getJavaNode();
if (javaNode == null) {
return null;
}
return javaNode.getName();
}
public abstract String makeString();
public String makeDescString() {
return null;
}
public boolean hasDescString() {
return false;
}
public String makeLongString() {
return makeString();
}

View File

@ -9,6 +9,8 @@ import javax.swing.ImageIcon;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
public class JPackage extends JNode implements Comparable<JPackage> {
private static final long serialVersionUID = -4120718634156839804L;
@ -45,6 +47,7 @@ public class JPackage extends JNode implements Comparable<JPackage> {
}
}
@Override
public String getName() {
return name;
}
@ -77,7 +80,7 @@ public class JPackage extends JNode implements Comparable<JPackage> {
}
@Override
public int compareTo(JPackage o) {
public int compareTo(@NotNull JPackage o) {
return name.compareTo(o.name);
}

View File

@ -24,7 +24,7 @@ public class JResource extends JNode implements Comparable<JResource> {
private static final ImageIcon JAVA_ICON = Utils.openIcon("java_ovr");
private static final ImageIcon ERROR_ICON = Utils.openIcon("error_co");
public static enum JResType {
public enum JResType {
ROOT,
DIR,
FILE

View File

@ -0,0 +1,394 @@
package jadx.gui.ui;
import jadx.gui.jobs.BackgroundJob;
import jadx.gui.jobs.DecompileJob;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.TextNode;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Position;
import jadx.gui.utils.TextSearchIndex;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingWorker;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.SearchContext;
import org.fife.ui.rtextarea.SearchEngine;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class CommonSearchDialog extends JDialog {
private static final Logger LOG = LoggerFactory.getLogger(CommonSearchDialog.class);
private static final long serialVersionUID = 8939332306115370276L;
public static final int MAX_RESULTS_COUNT = 1000;
protected final TabbedPane tabbedPane;
protected final CacheObject cache;
protected final MainWindow mainWindow;
protected final Font codeFont;
protected ResultsModel resultsModel;
protected ResultsTable resultsTable;
protected JLabel warnLabel;
protected ProgressPanel progressPane;
protected String highlightText;
public CommonSearchDialog(MainWindow mainWindow) {
super(mainWindow);
this.mainWindow = mainWindow;
this.tabbedPane = mainWindow.getTabbedPane();
this.cache = mainWindow.getCacheObject();
this.codeFont = mainWindow.getSettings().getFont();
}
public void prepare() {
if (cache.getIndexJob().isComplete()) {
loadFinishedCommon();
loadFinished();
return;
}
LoadTask task = new LoadTask();
task.addPropertyChangeListener(progressPane);
task.execute();
}
protected void openSelectedItem() {
int selectedId = resultsTable.getSelectedRow();
if (selectedId == -1) {
return;
}
JNode node = (JNode) resultsModel.getValueAt(selectedId, 0);
tabbedPane.codeJump(new Position(node.getRootClass(), node.getLine()));
dispose();
}
protected void initCommon() {
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
getRootPane().registerKeyboardAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dispose();
}
}, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
}
@NotNull
protected JPanel initButtonsPanel() {
progressPane = new ProgressPanel(mainWindow, false);
JButton cancelButton = new JButton(NLS.str("search_dialog.cancel"));
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
dispose();
}
});
JButton openBtn = new JButton(NLS.str("search_dialog.open"));
openBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
openSelectedItem();
}
});
getRootPane().setDefaultButton(openBtn);
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
buttonPane.add(progressPane);
buttonPane.add(Box.createRigidArea(new Dimension(5, 0)));
buttonPane.add(Box.createHorizontalGlue());
buttonPane.add(openBtn);
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
buttonPane.add(cancelButton);
return buttonPane;
}
protected JPanel initResultsTable() {
resultsModel = new ResultsModel();
resultsTable = new ResultsTable(resultsModel);
resultsTable.setShowHorizontalLines(false);
// resultsTable.setAutoCreateColumnsFromModel(true);
resultsTable.setDragEnabled(false);
resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
resultsTable.setBackground(ContentArea.BACKGROUND);
resultsTable.setColumnSelectionAllowed(false);
resultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
resultsTable.setAutoscrolls(false);
resultsTable.setDefaultRenderer(Object.class, new ResultsTableCellRenderer());
resultsTable.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent evt) {
if (evt.getClickCount() == 2) {
openSelectedItem();
}
}
});
resultsTable.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
openSelectedItem();
}
}
});
warnLabel = new JLabel();
warnLabel.setForeground(Color.RED);
warnLabel.setVisible(false);
JPanel resultsPanel = new JPanel();
resultsPanel.setLayout(new BoxLayout(resultsPanel, BoxLayout.PAGE_AXIS));
resultsPanel.add(warnLabel);
resultsPanel.add(new JScrollPane(resultsTable,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED));
resultsPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
return resultsPanel;
}
protected static class ResultsTable extends JTable {
private static final long serialVersionUID = 3901184054736618969L;
public ResultsTable(ResultsModel resultsModel) {
super(resultsModel);
}
public void updateTable() {
ResultsModel model = (ResultsModel) getModel();
TableColumnModel columnModel = getColumnModel();
int width = getParent().getWidth();
int firstColMaxWidth = (int) (width * 0.5);
int rowCount = getRowCount();
int columnCount = getColumnCount();
if (!model.isAddDescColumn()) {
firstColMaxWidth = width;
}
Component codeComp = null;
for (int col = 0; col < columnCount; col++) {
int colWidth = 50;
for (int row = 0; row < rowCount; row++) {
TableCellRenderer renderer = getCellRenderer(row, col);
Component comp = prepareRenderer(renderer, row, col);
if (comp == null) {
continue;
}
colWidth = Math.max(comp.getPreferredSize().width, colWidth);
if (codeComp == null && col == 1) {
codeComp = comp;
}
}
colWidth += 10;
if (col == 0) {
colWidth = Math.min(colWidth, firstColMaxWidth);
} else {
colWidth = Math.max(colWidth, width - columnModel.getColumn(0).getPreferredWidth());
}
TableColumn column = columnModel.getColumn(col);
column.setPreferredWidth(colWidth);
}
if (codeComp != null) {
setRowHeight(codeComp.getPreferredSize().height + 4);
}
updateUI();
}
}
protected static class ResultsModel extends AbstractTableModel {
private static final long serialVersionUID = -7821286846923903208L;
private static final String[] COLUMN_NAMES = {"Node", "Code"};
private final List<JNode> rows = new ArrayList<JNode>();
private boolean addDescColumn;
protected void addAll(Iterable<? extends JNode> nodes) {
for (JNode node : nodes) {
int size = getRowCount();
if (size >= MAX_RESULTS_COUNT) {
if (size == MAX_RESULTS_COUNT) {
add(new TextNode("Search results truncated (limit: " + MAX_RESULTS_COUNT + ")"));
}
return;
}
add(node);
}
}
private void add(JNode node) {
if (node.hasDescString()) {
addDescColumn = true;
}
rows.add(node);
}
public void clear() {
addDescColumn = false;
rows.clear();
}
public boolean isAddDescColumn() {
return addDescColumn;
}
@Override
public int getRowCount() {
return rows.size();
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public String getColumnName(int index) {
return COLUMN_NAMES[index];
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return rows.get(rowIndex);
}
}
protected class ResultsTableCellRenderer implements TableCellRenderer {
private final Color selectedBackground;
private final Color selectedForeground;
ResultsTableCellRenderer() {
UIDefaults defaults = UIManager.getDefaults();
selectedBackground = defaults.getColor("List.selectionBackground");
selectedForeground = defaults.getColor("List.selectionForeground");
}
@Override
public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected,
boolean hasFocus, int row, int column) {
if (!(obj instanceof JNode)) {
return null;
}
JNode node = (JNode) obj;
if (column == 0) {
JLabel label = new JLabel();
label.setOpaque(true);
if (isSelected) {
label.setBackground(selectedBackground);
label.setForeground(selectedForeground);
} else {
label.setBackground(ContentArea.BACKGROUND);
}
label.setIcon(node.getIcon());
label.setText(node.makeLongString() + " ");
return label;
}
if (node.hasDescString()) {
RSyntaxTextArea textArea = new RSyntaxTextArea();
textArea.setFont(codeFont);
textArea.setEditable(false);
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
textArea.setBackground(isSelected ? selectedBackground : ContentArea.BACKGROUND);
textArea.setText(" " + node.makeDescString());
textArea.setRows(1);
textArea.setColumns(textArea.getText().length());
if (highlightText != null) {
SearchEngine.markAll(textArea, new SearchContext(highlightText));
}
return textArea;
}
return null;
}
}
private class LoadTask extends SwingWorker<Void, Void> {
public LoadTask() {
loadStartCommon();
loadStart();
}
@Override
public Void doInBackground() {
try {
mainWindow.getBackgroundWorker().exec();
DecompileJob decompileJob = cache.getDecompileJob();
progressPane.changeLabel(this, decompileJob.getInfoString());
decompileJob.processAndWait();
BackgroundJob indexJob = cache.getIndexJob();
progressPane.changeLabel(this, indexJob.getInfoString());
indexJob.processAndWait();
} catch (Exception e) {
LOG.error("Waiting background tasks failed", e);
}
return null;
}
@Override
public void done() {
loadFinishedCommon();
loadFinished();
}
}
protected void loadStartCommon() {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
progressPane.setIndeterminate(true);
progressPane.setVisible(true);
resultsTable.setEnabled(false);
warnLabel.setVisible(false);
}
private void loadFinishedCommon() {
setCursor(null);
resultsTable.setEnabled(true);
progressPane.setVisible(false);
TextSearchIndex textIndex = cache.getTextIndex();
if (textIndex == null) {
warnLabel.setText("Index not initialized, search will be disabled!");
warnLabel.setVisible(true);
}
}
protected abstract void loadFinished();
protected abstract void loadStart();
}

View File

@ -1,15 +1,21 @@
package jadx.gui.ui;
import jadx.api.CodePosition;
import jadx.api.JavaNode;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.utils.Position;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JPopupMenu;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.DefaultCaret;
@ -17,6 +23,7 @@ import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import org.fife.ui.rsyntaxtextarea.LinkGenerator;
import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult;
@ -63,11 +70,23 @@ class ContentArea extends RSyntaxTextArea {
CodeLinkGenerator codeLinkProcessor = new CodeLinkGenerator((JClass) node);
setLinkGenerator(codeLinkProcessor);
addHyperlinkListener(codeLinkProcessor);
addMenuItems(this, (JClass) node);
}
setText(node.getContent());
}
private void addMenuItems(ContentArea contentArea, JClass jCls) {
Action findUsage = new FindUsageAction(contentArea, jCls);
// TODO: hotkey works only when popup menu is shown
// findUsage.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_F7, KeyEvent.ALT_DOWN_MASK));
JPopupMenu popup = getPopupMenu();
popup.addSeparator();
popup.add(findUsage);
popup.addPopupMenuListener((PopupMenuListener) findUsage);
}
public void loadSettings() {
JadxSettings settings = contentPanel.getTabbedPane().getMainWindow().getSettings();
setFont(settings.getFont());
@ -83,7 +102,7 @@ class ContentArea extends RSyntaxTextArea {
}
}
if (node instanceof JClass) {
Position pos = getPosition((JClass) node, this, token.getOffset());
Position pos = getDefPosition((JClass) node, this, token.getOffset());
if (pos != null) {
return true;
}
@ -100,21 +119,30 @@ class ContentArea extends RSyntaxTextArea {
return super.getForegroundForToken(t);
}
static Position getPosition(JClass jCls, RSyntaxTextArea textArea, int offset) {
static Position getDefPosition(JClass jCls, RSyntaxTextArea textArea, int offset) {
JavaNode node = getJavaNodeAtOffset(jCls, textArea, offset);
if (node == null) {
return null;
}
CodePosition pos = jCls.getCls().getDefinitionPosition(node);
if (pos == null) {
return null;
}
return new Position(pos);
}
static JavaNode getJavaNodeAtOffset(JClass jCls, RSyntaxTextArea textArea, int offset) {
try {
int line = textArea.getLineOfOffset(offset);
int lineOffset = offset - textArea.getLineStartOffset(line);
CodePosition pos = jCls.getCls().getDefinitionPosition(line + 1, lineOffset + 1);
if (pos != null && pos.isSet()) {
return new Position(pos);
}
return jCls.getCls().getJavaNodeAtPosition(line + 1, lineOffset + 1);
} catch (BadLocationException e) {
LOG.error("Can't get line by offset", e);
LOG.error("Can't get java node by offset", e);
}
return null;
}
Position getCurrentPosition() {
public Position getCurrentPosition() {
return new Position(node, getCaretLineNumber() + 1);
}
@ -166,6 +194,52 @@ class ContentArea extends RSyntaxTextArea {
}
}
private class FindUsageAction extends AbstractAction implements PopupMenuListener {
private static final long serialVersionUID = 4692546569977976384L;
private final ContentArea contentArea;
private final JClass jCls;
private JavaNode node;
public FindUsageAction(ContentArea contentArea, JClass jCls) {
super("Find Usage");
this.contentArea = contentArea;
this.jCls = jCls;
}
@Override
public void actionPerformed(ActionEvent e) {
if (node == null) {
return;
}
MainWindow mainWindow = contentPanel.getTabbedPane().getMainWindow();
UsageDialog usageDialog = new UsageDialog(mainWindow, JNode.makeFrom(node));
usageDialog.setVisible(true);
}
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
node = null;
Point pos = contentArea.getMousePosition();
if (pos != null) {
Token token = contentArea.viewToToken(pos);
if (token != null) {
node = getJavaNodeAtOffset(jCls, contentArea, token.getOffset());
}
}
setEnabled(node != null);
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
}
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
}
}
private class CodeLinkGenerator implements LinkGenerator, HyperlinkListener {
private final JClass jCls;
@ -181,7 +255,7 @@ class ContentArea extends RSyntaxTextArea {
return null;
}
final int sourceOffset = token.getOffset();
final Position defPos = getPosition(jCls, textArea, sourceOffset);
final Position defPos = getDefPosition(jCls, textArea, sourceOffset);
if (defPos == null) {
return null;
}
@ -207,12 +281,7 @@ class ContentArea extends RSyntaxTextArea {
public void hyperlinkUpdate(HyperlinkEvent e) {
Object obj = e.getSource();
if (obj instanceof Position) {
Position pos = (Position) obj;
LOG.debug("Code jump to: {}", pos);
TabbedPane tabbedPane = contentPanel.getTabbedPane();
tabbedPane.getJumpManager().addPosition(getCurrentPosition());
tabbedPane.getJumpManager().addPosition(pos);
tabbedPane.showCode(pos);
contentPanel.getTabbedPane().codeJump((Position) obj);
}
}
}

View File

@ -23,7 +23,6 @@ public class MainDropTarget implements DropTargetListener {
private final MainWindow mainWindow;
public MainDropTarget(MainWindow mainWindow) {
super();
this.mainWindow = mainWindow;
}
@ -50,6 +49,7 @@ public class MainDropTarget implements DropTargetListener {
}
@Override
@SuppressWarnings("unchecked")
public void drop(DropTargetDropEvent dtde) {
if (!dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
dtde.rejectDrop();
@ -57,7 +57,6 @@ public class MainDropTarget implements DropTargetListener {
}
dtde.acceptDrop(dtde.getDropAction());
try {
Transferable transferable = dtde.getTransferable();
List<File> transferData = (List<File>) transferable.getTransferData(DataFlavor.javaFileListFlavor);
if (transferData != null && transferData.size() > 0) {
@ -65,7 +64,6 @@ public class MainDropTarget implements DropTargetListener {
// load first file
mainWindow.openFile(transferData.get(0));
}
} catch (Exception e) {
LOG.error("File drop operation failed", e);
}
@ -73,7 +71,5 @@ public class MainDropTarget implements DropTargetListener {
@Override
public void dragExit(DropTargetEvent dte) {
}
}

View File

@ -1,6 +1,9 @@
package jadx.gui.ui;
import jadx.gui.JadxWrapper;
import jadx.gui.jobs.BackgroundWorker;
import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsWindow;
import jadx.gui.treemodel.JClass;
@ -48,7 +51,6 @@ import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.DisplayMode;
@ -66,6 +68,8 @@ import java.awt.event.MouseEvent;
import java.io.File;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -111,7 +115,9 @@ public class MainWindow extends JFrame {
private JToggleButton deobfToggleBtn;
private boolean isFlattenPackage;
private Link updateLink;
private ProgressPanel progressPane;
private BackgroundWorker backgroundWorker;
private DropTarget dropTarget;
public MainWindow(JadxSettings settings) {
@ -119,6 +125,7 @@ public class MainWindow extends JFrame {
this.settings = settings;
this.cacheObject = new CacheObject();
resetCache();
initUI();
initMenuAndToolbar();
checkForUpdate();
@ -175,18 +182,45 @@ public class MainWindow extends JFrame {
}
public void openFile(File file) {
cacheObject.reset();
tabbedPane.closeAllTabs();
resetCache();
wrapper.openFile(file);
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
settings.addRecentFile(file.getAbsolutePath());
initTree();
setTitle(DEFAULT_TITLE + " - " + file.getName());
runBackgroundJobs();
}
protected void resetCache() {
cacheObject.reset();
int threadsCount = settings.getThreadsCount();
cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount));
cacheObject.setIndexJob(new IndexJob(wrapper, settings, cacheObject));
}
private synchronized void runBackgroundJobs() {
cancelBackgroundJobs();
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
backgroundWorker.exec();
}
}, 1000);
}
public synchronized void cancelBackgroundJobs() {
if (backgroundWorker != null) {
backgroundWorker.stop();
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
resetCache();
}
}
public void reOpenFile() {
File openedFile = wrapper.getOpenFile();
if (openedFile != null) {
tabbedPane.closeAllTabs();
openFile(openedFile);
}
}
@ -247,14 +281,14 @@ public class MainWindow extends JFrame {
if (obj instanceof JResource) {
JResource res = (JResource) obj;
if (res.getContent() != null) {
tabbedPane.showCode(new Position(res, res.getLine()));
tabbedPane.codeJump(new Position(res, res.getLine()));
}
}
if (obj instanceof JNode) {
JNode node = (JNode) obj;
JClass cls = node.getRootClass();
if (cls != null) {
tabbedPane.showCode(new Position(cls, node.getLine()));
tabbedPane.codeJump(new Position(cls, node.getLine()));
}
}
} catch (Exception e) {
@ -539,14 +573,18 @@ public class MainWindow extends JFrame {
}
});
JScrollPane treeScrollPane = new JScrollPane(tree);
splitPane.setLeftComponent(treeScrollPane);
progressPane = new ProgressPanel(this, true);
JPanel leftPane = new JPanel(new BorderLayout());
leftPane.add(new JScrollPane(tree), BorderLayout.CENTER);
leftPane.add(progressPane, BorderLayout.PAGE_END);
splitPane.setLeftComponent(leftPane);
tabbedPane = new TabbedPane(this);
splitPane.setRightComponent(tabbedPane);
dropTarget = new DropTarget(this, DnDConstants.ACTION_COPY, new MainDropTarget(this));
setContentPane(mainPanel);
setTitle(DEFAULT_TITLE);
}
@ -565,6 +603,12 @@ public class MainWindow extends JFrame {
tabbedPane.loadSettings();
}
@Override
public void dispose() {
cancelBackgroundJobs();
super.dispose();
}
public JadxWrapper getWrapper() {
return wrapper;
}
@ -581,6 +625,10 @@ public class MainWindow extends JFrame {
return cacheObject;
}
public BackgroundWorker getBackgroundWorker() {
return backgroundWorker;
}
private class RecentFilesMenuListener implements MenuListener {
private final JMenu recentFiles;
@ -619,4 +667,5 @@ public class MainWindow extends JFrame {
public void menuCanceled(MenuEvent e) {
}
}
}

View File

@ -0,0 +1,82 @@
package jadx.gui.ui;
import jadx.gui.utils.Utils;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class ProgressPanel extends JPanel implements PropertyChangeListener {
private static final long serialVersionUID = -3238438119672015733L;
private static final Icon ICON_CANCEL = Utils.openIcon("cross");
private final JProgressBar progressBar;
private final JLabel progressLabel;
public ProgressPanel(final MainWindow mainWindow, boolean showCancelButton) {
progressLabel = new JLabel();
progressBar = new JProgressBar(0, 100);
progressBar.setIndeterminate(true);
progressBar.setStringPainted(false);
progressLabel.setLabelFor(progressBar);
setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
setVisible(false);
add(progressLabel);
add(progressBar);
if (showCancelButton) {
JButton cancelButton = new JButton(ICON_CANCEL);
cancelButton.setPreferredSize(new Dimension(ICON_CANCEL.getIconWidth(), ICON_CANCEL.getIconHeight()));
cancelButton.setToolTipText("Cancel background jobs");
cancelButton.setBorderPainted(false);
cancelButton.setFocusPainted(false);
cancelButton.setContentAreaFilled(false);
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
mainWindow.cancelBackgroundJobs();
}
});
add(cancelButton);
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
int progress = (Integer) evt.getNewValue();
progressBar.setIndeterminate(false);
progressBar.setValue(progress);
progressBar.setString(progress + "%");
progressBar.setStringPainted(true);
} else if ("label".equals(evt.getPropertyName())) {
setLabel((String) evt.getNewValue());
}
}
public void setLabel(String label) {
progressLabel.setText(label);
}
public void setIndeterminate(boolean newValue) {
progressBar.setIndeterminate(newValue);
}
public void changeLabel(SwingWorker<?, ?> task, String label) {
task.firePropertyChange("label", null, label);
}
}

View File

@ -1,53 +1,28 @@
package jadx.gui.ui;
import jadx.api.JavaClass;
import jadx.gui.JadxWrapper;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.TextNode;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Position;
import jadx.gui.utils.TextSearchIndex;
import jadx.gui.utils.TextStandardActions;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.EnumSet;
@ -56,12 +31,11 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SearchDialog extends JDialog {
public class SearchDialog extends CommonSearchDialog {
private static final long serialVersionUID = -5105405456969134105L;
private static final Logger LOG = LoggerFactory.getLogger(SearchDialog.class);
private static final int MAX_RESULTS_COUNT = 500;
enum SearchOptions {
CLASS,
@ -72,67 +46,43 @@ public class SearchDialog extends JDialog {
private Set<SearchOptions> options = EnumSet.allOf(SearchOptions.class);
private final TabbedPane tabbedPane;
private final JadxWrapper wrapper;
private final CacheObject cache;
private JTextField searchField;
private ResultsModel resultsModel;
private JList resultsList;
private JProgressBar busyBar;
public SearchDialog(MainWindow mainWindow, Set<SearchOptions> options) {
super(mainWindow);
this.tabbedPane = mainWindow.getTabbedPane();
this.wrapper = mainWindow.getWrapper();
this.cache = mainWindow.getCacheObject();
this.options = options;
initUI();
addWindowListener(new WindowAdapter() {
@Override
public void windowActivated(WindowEvent e) {
public void windowOpened(WindowEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
prepare();
searchField.requestFocus();
openInit();
}
});
}
});
}
public void prepare() {
TextSearchIndex index = cache.getTextIndex();
if (index != null) {
return;
protected void openInit() {
prepare();
String lastSearch = cache.getLastSearch();
if (lastSearch != null) {
searchField.setText(lastSearch);
searchField.selectAll();
}
LoadTask task = new LoadTask();
task.execute();
}
private void loadData() {
TextSearchIndex index = cache.getTextIndex();
if (index != null) {
return;
}
index = new TextSearchIndex();
for (JavaClass cls : wrapper.getClasses()) {
index.indexNames(cls);
}
for (JavaClass cls : wrapper.getClasses()) {
index.indexCode(cls);
}
cache.setTextIndex(index);
searchField.requestFocus();
}
private synchronized void performSearch() {
resultsModel.removeAllElements();
resultsModel.clear();
String text = searchField.getText();
if (text == null || text.isEmpty() || options.isEmpty()) {
return;
}
cache.setLastSearch(text);
TextSearchIndex index = cache.getTextIndex();
if (index == null) {
return;
@ -149,92 +99,9 @@ public class SearchDialog extends JDialog {
if (options.contains(SearchOptions.CODE)) {
resultsModel.addAll(index.searchCode(text));
}
LOG.info("Search returned {} results", resultsModel.size());
highlightText = text;
resultsTable.updateTable();
}
private void openSelectedItem() {
int selectedId = resultsList.getSelectedIndex();
if (selectedId == -1) {
return;
}
JNode node = (JNode) resultsModel.get(selectedId);
tabbedPane.showCode(new Position(node.getRootClass(), node.getLine()));
dispose();
}
private class LoadTask extends SwingWorker<Void, Void> {
public LoadTask() {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
busyBar.setVisible(true);
searchField.setEnabled(false);
resultsList.setEnabled(false);
}
@Override
public Void doInBackground() {
loadData();
return null;
}
@Override
public void done() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setCursor(null);
searchField.setEnabled(true);
resultsList.setEnabled(true);
busyBar.setVisible(false);
}
});
}
}
private static class ResultsModel extends DefaultListModel {
private static final long serialVersionUID = -7821286846923903208L;
private void addAll(Iterable<? extends JNode> nodes) {
for (JNode node : nodes) {
if (size() >= MAX_RESULTS_COUNT) {
if (size() == MAX_RESULTS_COUNT) {
addElement(new TextNode("Search results truncated (limit: " + MAX_RESULTS_COUNT + ")"));
}
return;
}
addElement(node);
}
}
}
private static class ResultsCellRenderer implements ListCellRenderer {
private final Color selectedBackground;
private final Color selectedForeground;
ResultsCellRenderer() {
UIDefaults defaults = UIManager.getDefaults();
selectedBackground = defaults.getColor("List.selectionBackground");
selectedForeground = defaults.getColor("List.selectionForeground");
}
@Override
public Component getListCellRendererComponent(JList list,
Object obj, int index, boolean isSelected, boolean cellHasFocus) {
if (!(obj instanceof JNode)) {
return null;
}
JNode value = (JNode) obj;
JLabel label = new JLabel();
label.setOpaque(true);
label.setIcon(value.getIcon());
label.setText(value.makeLongString());
if (isSelected) {
label.setBackground(selectedBackground);
label.setForeground(selectedForeground);
}
return label;
}
}
private class SearchFieldListener implements DocumentListener {
public void changedUpdate(DocumentEvent e) {
@ -263,17 +130,6 @@ public class SearchDialog extends JDialog {
JCheckBox fldChBox = makeOptionsCheckBox(NLS.str("search_dialog.field"), SearchOptions.FIELD);
JCheckBox codeChBox = makeOptionsCheckBox(NLS.str("search_dialog.code"), SearchOptions.CODE);
resultsModel = new ResultsModel();
resultsList = new JList(resultsModel);
resultsList.setCellRenderer(new ResultsCellRenderer());
resultsList.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent evt) {
if (evt.getClickCount() == 2) {
openSelectedItem();
}
}
});
JPanel searchOptions = new JPanel(new FlowLayout(FlowLayout.LEFT));
searchOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.search_in")));
searchOptions.add(clsChBox);
@ -292,68 +148,30 @@ public class SearchDialog extends JDialog {
searchPane.add(searchOptions);
searchPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
JPanel listPane = new JPanel();
listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS));
listPane.add(new JScrollPane(resultsList));
listPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
busyBar = new JProgressBar();
busyBar.setIndeterminate(true);
busyBar.setVisible(false);
//Create and initialize the buttons.
JButton cancelButton = new JButton(NLS.str("search_dialog.cancel"));
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
dispose();
}
});
JButton openBtn = new JButton(NLS.str("search_dialog.open"));
openBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
openSelectedItem();
}
});
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
buttonPane.add(busyBar);
buttonPane.add(Box.createRigidArea(new Dimension(5, 0)));
buttonPane.add(Box.createHorizontalGlue());
buttonPane.add(openBtn);
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
buttonPane.add(cancelButton);
initCommon();
JPanel resultsPanel = initResultsTable();
JPanel buttonPane = initButtonsPanel();
Container contentPane = getContentPane();
contentPane.add(searchPane, BorderLayout.PAGE_START);
contentPane.add(listPane, BorderLayout.CENTER);
contentPane.add(resultsPanel, BorderLayout.CENTER);
contentPane.add(buttonPane, BorderLayout.PAGE_END);
getRootPane().setDefaultButton(openBtn);
searchField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
resultsList.requestFocus();
if (!resultsModel.isEmpty()) {
resultsList.setSelectedIndex(0);
if (resultsModel.getRowCount() != 0) {
resultsTable.setRowSelectionInterval(0, 0);
}
resultsTable.requestFocus();
}
}
});
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
getRootPane().registerKeyboardAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dispose();
}
}, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
setTitle(NLS.str("menu.text_search"));
pack();
setSize(700, 500);
setSize(800, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setModalityType(ModalityType.MODELESS);
@ -375,4 +193,14 @@ public class SearchDialog extends JDialog {
});
return chBox;
}
@Override
protected void loadFinished() {
searchField.setEnabled(true);
}
@Override
protected void loadStart() {
searchField.setEnabled(false);
}
}

View File

@ -16,6 +16,7 @@ import javax.swing.JPopupMenu;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.text.BadLocationException;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
@ -29,8 +30,13 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class TabbedPane extends JTabbedPane {
private static final Logger LOG = LoggerFactory.getLogger(TabbedPane.class);
private static final long serialVersionUID = -8833600618794570904L;
private static final ImageIcon ICON_CLOSE = Utils.openIcon("cross");
@ -65,19 +71,46 @@ class TabbedPane extends JTabbedPane {
return mainWindow;
}
void showCode(final Position pos) {
private void showCode(final Position pos) {
final ContentPanel contentPanel = getCodePanel(pos.getNode());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setSelectedComponent(contentPanel);
ContentArea contentArea = contentPanel.getContentArea();
contentArea.scrollToLine(pos.getLine());
int line = pos.getLine();
if (line < 0) {
try {
line = 1 + contentArea.getLineOfOffset(-line);
} catch (BadLocationException e) {
LOG.error("Can't get line for: {}", pos, e);
line = pos.getNode().getLine();
}
}
contentArea.scrollToLine(line);
contentArea.requestFocus();
}
});
}
public void codeJump(Position pos) {
Position curPos = getCurrentPosition();
if (curPos != null) {
jumps.addPosition(curPos);
jumps.addPosition(pos);
}
showCode(pos);
}
@Nullable
private Position getCurrentPosition() {
ContentPanel selectedCodePanel = getSelectedCodePanel();
if (selectedCodePanel == null) {
return null;
}
return selectedCodePanel.getContentArea().getCurrentPosition();
}
public void navBack() {
Position pos = jumps.getPrev();
if (pos != null) {
@ -116,6 +149,7 @@ class TabbedPane extends JTabbedPane {
return panel;
}
@Nullable
ContentPanel getSelectedCodePanel() {
return (ContentPanel) getSelectedComponent();
}

View File

@ -0,0 +1,101 @@
package jadx.gui.ui;
import jadx.gui.treemodel.JNode;
import jadx.gui.utils.CodeUsageInfo;
import jadx.gui.utils.NLS;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UsageDialog extends CommonSearchDialog {
private static final long serialVersionUID = -5105405789969134105L;
private static final Logger LOG = LoggerFactory.getLogger(UsageDialog.class);
private final JNode node;
public UsageDialog(MainWindow mainWindow, JNode node) {
super(mainWindow);
this.node = node;
initUI();
addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
openInit();
}
});
}
});
}
protected void openInit() {
prepare();
}
@Override
protected void loadFinished() {
performSearch();
}
@Override
protected void loadStart() {
}
private synchronized void performSearch() {
resultsModel.clear();
CodeUsageInfo usageInfo = cache.getUsageInfo();
if (usageInfo == null) {
return;
}
resultsModel.addAll(usageInfo.getUsageList(node));
// TODO: highlight only needed node usage
highlightText = null;
resultsTable.updateTable();
}
private void initUI() {
JLabel lbl = new JLabel(NLS.str("usage_dialog.label"));
JLabel nodeLabel = new JLabel(this.node.makeLongString(), this.node.getIcon(), SwingConstants.LEFT);
lbl.setLabelFor(nodeLabel);
JPanel searchPane = new JPanel();
searchPane.setLayout(new FlowLayout(FlowLayout.LEFT));
searchPane.add(lbl);
searchPane.add(nodeLabel);
searchPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
initCommon();
JPanel resultsPanel = initResultsTable();
JPanel buttonPane = initButtonsPanel();
Container contentPane = getContentPane();
contentPane.add(searchPane, BorderLayout.PAGE_START);
contentPane.add(resultsPanel, BorderLayout.CENTER);
contentPane.add(buttonPane, BorderLayout.PAGE_END);
setTitle(NLS.str("usage_dialog.title"));
pack();
setSize(800, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setModalityType(ModalityType.MODELESS);
}
}

View File

@ -1,13 +1,31 @@
package jadx.gui.utils;
import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob;
import org.jetbrains.annotations.Nullable;
public class CacheObject {
@Nullable
private DecompileJob decompileJob;
private IndexJob indexJob;
private TextSearchIndex textIndex;
private CodeUsageInfo usageInfo;
private String lastSearch;
public void reset() {
textIndex = null;
lastSearch = null;
usageInfo = null;
}
public DecompileJob getDecompileJob() {
return decompileJob;
}
public void setDecompileJob(DecompileJob decompileJob) {
this.decompileJob = decompileJob;
}
@Nullable
@ -15,7 +33,33 @@ public class CacheObject {
return textIndex;
}
public void setTextIndex(@Nullable TextSearchIndex textIndex) {
public void setTextIndex(TextSearchIndex textIndex) {
this.textIndex = textIndex;
}
@Nullable
public String getLastSearch() {
return lastSearch;
}
public void setLastSearch(String lastSearch) {
this.lastSearch = lastSearch;
}
@Nullable
public CodeUsageInfo getUsageInfo() {
return usageInfo;
}
public void setUsageInfo(@Nullable CodeUsageInfo usageInfo) {
this.usageInfo = usageInfo;
}
public IndexJob getIndexJob() {
return indexJob;
}
public void setIndexJob(IndexJob indexJob) {
this.indexJob = indexJob;
}
}

View File

@ -0,0 +1,36 @@
package jadx.gui.utils;
import jadx.api.JavaClass;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
public class CodeLinesInfo {
private NavigableMap<Integer, JavaNode> map = new TreeMap<Integer, JavaNode>();
public CodeLinesInfo(JavaClass cls) {
addClass(cls);
}
public void addClass(JavaClass cls) {
map.put(cls.getDecompiledLine(), cls);
for (JavaClass innerCls : cls.getInnerClasses()) {
map.put(innerCls.getDecompiledLine(), innerCls);
addClass(innerCls);
}
for (JavaMethod mth : cls.getMethods()) {
map.put(mth.getDecompiledLine(), mth);
}
}
public JavaNode getJavaNodeByLine(int line) {
Map.Entry<Integer, JavaNode> entry = map.floorEntry(line);
if (entry == null) {
return null;
}
return entry.getValue();
}
}

View File

@ -0,0 +1,57 @@
package jadx.gui.utils;
import jadx.api.CodePosition;
import jadx.api.JavaClass;
import jadx.api.JavaNode;
import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CodeUsageInfo {
public static class UsageInfo {
private final List<CodeNode> usageList = new ArrayList<CodeNode>();
public List<CodeNode> getUsageList() {
return usageList;
}
}
private final Map<JNode, UsageInfo> usageMap = new HashMap<JNode, UsageInfo>();
public void processClass(JavaClass javaClass, CodeLinesInfo linesInfo, String[] lines) {
Map<CodePosition, JavaNode> usage = javaClass.getUsageMap();
for (Map.Entry<CodePosition, JavaNode> entry : usage.entrySet()) {
CodePosition codePosition = entry.getKey();
JavaNode javaNode = entry.getValue();
addUsage(JNode.makeFrom(javaNode), javaClass, linesInfo, codePosition, lines);
}
}
private void addUsage(JNode jNode, JavaClass javaClass,
CodeLinesInfo linesInfo, CodePosition codePosition, String[] lines) {
UsageInfo usageInfo = usageMap.get(jNode);
if (usageInfo == null) {
usageInfo = new UsageInfo();
usageMap.put(jNode, usageInfo);
}
int line = codePosition.getLine();
JavaNode javaNodeByLine = linesInfo.getJavaNodeByLine(line);
String codeLine = lines[line - 1].trim();
CodeNode codeNode = new CodeNode(javaNodeByLine == null ? javaClass : javaNodeByLine, line, codeLine);
usageInfo.getUsageList().add(codeNode);
}
public List<CodeNode> getUsageList(JNode node) {
UsageInfo usageInfo = usageMap.get(node);
if (usageInfo == null) {
return Collections.emptyList();
}
return usageInfo.getUsageList();
}
}

View File

@ -49,7 +49,7 @@ public class LogCollector extends CyclicBufferAppender<ILoggingEvent> {
public LogCollector() {
setName("LogCollector");
setMaxSize(50000);
setMaxSize(5000);
}
@Override

View File

@ -0,0 +1,28 @@
package jadx.gui.utils;
import com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory;
import com.googlecode.concurrenttrees.suffix.ConcurrentSuffixTree;
public class SuffixTree<V> {
private final ConcurrentSuffixTree<V> tree;
public SuffixTree() {
this.tree = new ConcurrentSuffixTree<V>(new DefaultCharArrayNodeFactory());
}
public void put(String str, V value) {
if (str == null || str.isEmpty()) {
return;
}
tree.putIfAbsent(str, value);
}
public Iterable<V> getValuesForKeysContaining(String str) {
return tree.getValuesForKeysContaining(str);
}
public int size() {
return tree.size();
}
}

View File

@ -3,19 +3,18 @@ package jadx.gui.utils;
import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.core.codegen.CodeWriter;
import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.CommonSearchDialog;
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory;
import com.googlecode.concurrenttrees.suffix.ConcurrentSuffixTree;
import com.googlecode.concurrenttrees.suffix.SuffixTree;
public class TextSearchIndex {
private static final Logger LOG = LoggerFactory.getLogger(TextSearchIndex.class);
@ -25,15 +24,16 @@ public class TextSearchIndex {
private SuffixTree<JNode> fldNamesTree;
private SuffixTree<CodeNode> codeTree;
private List<JavaClass> skippedClasses = new ArrayList<JavaClass>();
public TextSearchIndex() {
clsNamesTree = new ConcurrentSuffixTree<JNode>(new DefaultCharArrayNodeFactory());
mthNamesTree = new ConcurrentSuffixTree<JNode>(new DefaultCharArrayNodeFactory());
fldNamesTree = new ConcurrentSuffixTree<JNode>(new DefaultCharArrayNodeFactory());
codeTree = new ConcurrentSuffixTree<CodeNode>(new DefaultCharArrayNodeFactory());
clsNamesTree = new SuffixTree<JNode>();
mthNamesTree = new SuffixTree<JNode>();
fldNamesTree = new SuffixTree<JNode>();
codeTree = new SuffixTree<CodeNode>();
}
public void indexNames(JavaClass cls) {
cls.decompile();
clsNamesTree.put(cls.getFullName(), JNode.makeFrom(cls));
for (JavaMethod mth : cls.getMethods()) {
mthNamesTree.put(mth.getFullName(), JNode.makeFrom(mth));
@ -46,18 +46,15 @@ public class TextSearchIndex {
}
}
public void indexCode(JavaClass cls) {
public void indexCode(JavaClass cls, CodeLinesInfo linesInfo, String[] lines) {
try {
String code = cls.getCode();
BufferedReader bufReader = new BufferedReader(new StringReader(code));
String line;
int lineNum = 0;
while ((line = bufReader.readLine()) != null) {
lineNum++;
line = line.trim();
int count = lines.length;
for (int i = 0; i < count; i++) {
String line = lines[i];
if (!line.isEmpty()) {
CodeNode node = new CodeNode(cls, lineNum, line);
codeTree.put(line, node);
int lineNum = i + 1;
JavaNode node = linesInfo.getJavaNodeByLine(lineNum);
codeTree.put(line, new CodeNode(node == null ? cls : node, lineNum, line));
}
}
} catch (Exception e) {
@ -78,6 +75,59 @@ public class TextSearchIndex {
}
public Iterable<CodeNode> searchCode(String text) {
return codeTree.getValuesForKeysContaining(text);
Iterable<CodeNode> items;
if (codeTree.size() > 0) {
items = codeTree.getValuesForKeysContaining(text);
if (skippedClasses.isEmpty()) {
return items;
}
} else {
items = null;
}
List<CodeNode> list = new ArrayList<CodeNode>();
if (items != null) {
for (CodeNode item : items) {
list.add(item);
}
}
addSkippedClasses(list, text);
return list;
}
private void addSkippedClasses(List<CodeNode> list, String text) {
for (JavaClass javaClass : skippedClasses) {
String code = javaClass.getCode();
int pos = 0;
while (pos != -1) {
pos = searchNext(list, text, javaClass, code, pos);
}
if (list.size() > CommonSearchDialog.MAX_RESULTS_COUNT) {
return;
}
}
}
private int searchNext(List<CodeNode> list, String text, JavaNode javaClass, String code, int startPos) {
int pos = code.indexOf(text, startPos);
if (pos == -1) {
return -1;
}
int lineStart = 1 + code.lastIndexOf(CodeWriter.NL, pos);
int lineEnd = code.indexOf(CodeWriter.NL, pos + text.length());
String line = code.substring(lineStart, lineEnd == -1 ? code.length() : lineEnd);
list.add(new CodeNode(javaClass, -pos, line.trim()));
return lineEnd;
}
public void classCodeIndexSkipped(JavaClass cls) {
this.skippedClasses.add(cls);
}
public List<JavaClass> getSkippedClasses() {
return skippedClasses;
}
public int getSkippedCount() {
return skippedClasses.size();
}
}

View File

@ -84,4 +84,30 @@ public class Utils {
}
return overIcon;
}
public static boolean isFreeMemoryAvailable() {
Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory();
long totalFree = runtime.freeMemory() + maxMemory - runtime.totalMemory();
return totalFree > maxMemory * 0.2;
}
public static String memoryInfo() {
Runtime runtime = Runtime.getRuntime();
StringBuilder sb = new StringBuilder();
long maxMemory = runtime.maxMemory();
long allocatedMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
sb.append("free: ").append(format(freeMemory));
sb.append(", allocated: ").append(format(allocatedMemory));
sb.append(", max: ").append(format(maxMemory));
sb.append(", total free: ").append(format(freeMemory + maxMemory - allocatedMemory));
return sb.toString();
}
private static String format(long mem) {
return Long.toString((long) (mem / 1024. / 1024.)) + "MB";
}
}

View File

@ -47,6 +47,9 @@ search_dialog.method=Method
search_dialog.field=Field
search_dialog.code=Code
usage_dialog.title=Usage search
usage_dialog.label=Usage for:
preferences.title=Preferences
preferences.deobfuscation=Deobfuscation
preferences.other=Other
@ -58,6 +61,7 @@ preferences.threads=Processing threads count
preferences.cfg=Generate methods CFG graphs (in 'dot' format)
preferences.raw_cfg=Generate RAW CFG graphs
preferences.font=Editor font
preferences.fast_search=Fast search (uses more memory)
preferences.select_font=Select
preferences.deobfuscation_on=Enable deobfuscation
preferences.deobfuscation_force=Force rewrite deobfuscation map file