Commit bc73010d authored by Skylot's avatar Skylot

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

parent 2d8d4164
......@@ -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/
......@@ -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,
......
......@@ -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'
......
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());
}
}
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);
}
}
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: ";
}
}
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;
}
}
......@@ -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;
......
......@@ -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
......
......@@ -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;
}
......
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
......@@ -30,17 +53,22 @@ public class CodeNode extends JClass {
}
@Override
public String makeString() {
return getCls().getFullName() + ":" + lineNum + " " + line;
public String makeDescString() {
return line;
}
@Override
public String makeLongString() {
return makeString();
public boolean hasDescString() {
return true;
}
@Override
public String makeString() {
return jNode.makeLongString();
}
@Override
public String toString() {
public String makeLongString() {
return makeString();
}
}
......@@ -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;
......@@ -104,6 +105,11 @@ public class JClass extends JNode {
}
@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();
}
......
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;
......@@ -28,6 +29,11 @@ public class JField extends JNode {
}
@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);
}
}
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;
......@@ -30,10 +31,19 @@ public class JMethod extends JNode {
}
@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);
}
}
......@@ -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();
}
......
......@@ -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);
}
......
......@@ -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
......
This diff is collapsed.
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);
}
}
}
......
......@@ -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) {
}
}
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) {
}
}
}
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);
}
}
......@@ -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();
}
......
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);
}
}
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;
}
}
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();
}
}
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();
}
}
......@@ -49,7 +49,7 @@ public class LogCollector extends CyclicBufferAppender<ILoggingEvent> {
public LogCollector() {
setName("LogCollector");
setMaxSize(50000);
setMaxSize(5000);
}
@Override
......
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();
}
}
......@@ -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();
}
}
......@@ -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";
}
}
......@@ -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
......
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