mirror of
https://github.com/skylot/jadx.git
synced 2024-11-23 04:39:46 +00:00
This commit is contained in:
parent
2d8d416483
commit
bc73010d4e
24
NOTICE
24
NOTICE
@ -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'
|
||||
|
87
jadx-gui/src/main/java/jadx/gui/jobs/BackgroundJob.java
Normal file
87
jadx-gui/src/main/java/jadx/gui/jobs/BackgroundJob.java
Normal 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());
|
||||
}
|
||||
}
|
99
jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java
Normal file
99
jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
28
jadx-gui/src/main/java/jadx/gui/jobs/DecompileJob.java
Normal file
28
jadx-gui/src/main/java/jadx/gui/jobs/DecompileJob.java
Normal 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: ";
|
||||
}
|
||||
|
||||
}
|
76
jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java
Normal file
76
jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
394
jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java
Normal file
394
jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java
Normal 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();
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
82
jadx-gui/src/main/java/jadx/gui/ui/ProgressPanel.java
Normal file
82
jadx-gui/src/main/java/jadx/gui/ui/ProgressPanel.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
101
jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java
Normal file
101
jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
36
jadx-gui/src/main/java/jadx/gui/utils/CodeLinesInfo.java
Normal file
36
jadx-gui/src/main/java/jadx/gui/utils/CodeLinesInfo.java
Normal 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();
|
||||
}
|
||||
}
|
57
jadx-gui/src/main/java/jadx/gui/utils/CodeUsageInfo.java
Normal file
57
jadx-gui/src/main/java/jadx/gui/utils/CodeUsageInfo.java
Normal 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();
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ public class LogCollector extends CyclicBufferAppender<ILoggingEvent> {
|
||||
|
||||
public LogCollector() {
|
||||
setName("LogCollector");
|
||||
setMaxSize(50000);
|
||||
setMaxSize(5000);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
28
jadx-gui/src/main/java/jadx/gui/utils/SuffixTree.java
Normal file
28
jadx-gui/src/main/java/jadx/gui/utils/SuffixTree.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user