fix(gui): new implementation for tree state save/load (#2399)

This commit is contained in:
Skylot
2025-02-01 19:31:24 +00:00
parent 801890f0a8
commit b18604071a
18 changed files with 434 additions and 149 deletions
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
@@ -213,4 +214,14 @@ public class ListUtils {
return false;
}
public static <T> List<T> enumerationToList(Enumeration<T> enumeration) {
if (enumeration == null || enumeration == Collections.emptyEnumeration()) {
return Collections.emptyList();
}
List<T> list = new ArrayList<>();
while (enumeration.hasMoreElements()) {
list.add(enumeration.nextElement());
}
return list;
}
}
@@ -0,0 +1,90 @@
package jadx.gui.jobs;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import jadx.api.utils.tasks.ITaskExecutor;
/**
* Add additional `onFinish` action to the existing task
*/
public class TaskWithExtraOnFinish implements IBackgroundTask {
private final IBackgroundTask task;
private final Consumer<TaskStatus> extraOnFinish;
public TaskWithExtraOnFinish(IBackgroundTask task, Consumer<TaskStatus> extraOnFinish) {
this.task = task;
this.extraOnFinish = extraOnFinish;
}
public TaskWithExtraOnFinish(IBackgroundTask task, Runnable extraOnFinish) {
this(task, s -> extraOnFinish.run());
}
@Override
public void onFinish(ITaskInfo taskInfo) {
task.onFinish(taskInfo);
extraOnFinish.accept(taskInfo.getStatus());
}
@Override
public String getTitle() {
return task.getTitle();
}
@Override
public ITaskExecutor scheduleTasks() {
return task.scheduleTasks();
}
@Override
public void onDone(ITaskInfo taskInfo) {
task.onDone(taskInfo);
}
@Override
public @Nullable Consumer<ITaskProgress> getProgressListener() {
return task.getProgressListener();
}
@Override
public @Nullable ITaskProgress getTaskProgress() {
return task.getTaskProgress();
}
@Override
public boolean canBeCanceled() {
return task.canBeCanceled();
}
@Override
public boolean isCanceled() {
return task.isCanceled();
}
@Override
public void cancel() {
task.cancel();
}
@Override
public int timeLimit() {
return task.timeLimit();
}
@Override
public boolean checkMemoryUsage() {
return task.checkMemoryUsage();
}
@Override
public int getCancelTimeoutMS() {
return task.getCancelTimeoutMS();
}
@Override
public int getShutdownTimeoutMS() {
return task.getShutdownTimeoutMS();
}
}
@@ -5,15 +5,12 @@ import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -51,7 +48,6 @@ import static jadx.core.utils.GsonUtils.interfaceReplace;
public class JadxProject {
private static final Logger LOG = LoggerFactory.getLogger(JadxProject.class);
private static final int CURRENT_PROJECT_VERSION = 1;
public static final String PROJECT_EXTENSION = "jadx";
private static final int SEARCH_HISTORY_LIMIT = 30;
@@ -136,25 +132,13 @@ public class JadxProject {
changed();
}
public List<String[]> getTreeExpansions() {
return data.getTreeExpansions();
public void setTreeExpansions(List<String> list) {
data.setTreeExpansionsV2(list);
changed();
}
public void addTreeExpansion(String[] expansion) {
data.getTreeExpansions().removeIf(arr -> Arrays.equals(arr, expansion));
data.getTreeExpansions().add(expansion);
}
public void removeTreeExpansion(String[] expansion) {
data.getTreeExpansions().removeIf(strings -> isParentOfExpansion(expansion, strings));
}
private void reduceTreeExpansions() {
// remove same entries before a project file save
// this is mostly needed for old projects ('add' guard don't work for existed entries)
Set<String[]> set = new TreeSet<>((a, b) -> Arrays.equals(a, b) ? 0 : Integer.compare(Arrays.hashCode(a), Arrays.hashCode(b)));
set.addAll(data.getTreeExpansions());
data.setTreeExpansions(new ArrayList<>(set));
public List<String> getTreeExpansions() {
return data.getTreeExpansionsV2();
}
private boolean isParentOfExpansion(String[] parent, String[] child) {
@@ -287,10 +271,6 @@ public class JadxProject {
mainWindow.updateProject(this);
}
private void onSave() {
reduceTreeExpansions();
}
public String getName() {
return name;
}
@@ -314,7 +294,6 @@ public class JadxProject {
}
public void save() {
onSave();
Path savePath = getProjectPath();
if (savePath != null) {
Path basePath = savePath.toAbsolutePath().getParent();
@@ -333,7 +312,6 @@ public class JadxProject {
project.data = loadProjectData(path);
project.saved = true;
project.setProjectPath(path);
project.upgrade();
return project;
} catch (Exception e) {
LOG.error("Error loading project", e);
@@ -359,20 +337,4 @@ public class JadxProject {
.registerTypeAdapter(IJavaCodeRef.class, interfaceReplace(JadxCodeRef.class))
.create();
}
private void upgrade() {
int fromVersion = data.getProjectVersion();
if (fromVersion == CURRENT_PROJECT_VERSION) {
return;
}
LOG.debug("upgrade project settings from version: {} to {}", fromVersion, CURRENT_PROJECT_VERSION);
if (fromVersion == 0) {
fromVersion++;
}
if (fromVersion != CURRENT_PROJECT_VERSION) {
throw new JadxRuntimeException("Project update failed");
}
data.setProjectVersion(CURRENT_PROJECT_VERSION);
save();
}
}
@@ -13,10 +13,9 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.data.impl.JadxCodeData;
public class ProjectData {
private int projectVersion = 1;
private int projectVersion = 2;
private List<Path> files = new ArrayList<>();
private List<String[]> treeExpansions = new ArrayList<>();
private List<String> treeExpansionsV2 = new ArrayList<>();
private JadxCodeData codeData = new JadxCodeData();
private List<TabViewState> openTabs = Collections.emptyList();
private @Nullable Path mappingsPath;
@@ -33,12 +32,12 @@ public class ProjectData {
this.files = Objects.requireNonNull(files);
}
public List<String[]> getTreeExpansions() {
return treeExpansions;
public List<String> getTreeExpansionsV2() {
return treeExpansionsV2;
}
public void setTreeExpansions(List<String[]> treeExpansions) {
this.treeExpansions = treeExpansions;
public void setTreeExpansionsV2(List<String> treeExpansionsV2) {
this.treeExpansionsV2 = treeExpansionsV2;
}
public JadxCodeData getCodeData() {
@@ -0,0 +1,212 @@
package jadx.gui.tree;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JPackage;
import jadx.gui.treemodel.JRoot;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
public class TreeExpansionService {
private static final Logger LOG = LoggerFactory.getLogger(TreeExpansionService.class);
private static final boolean DEBUG = UiUtils.JADX_GUI_DEBUG;
private static final Comparator<TreePath> PATH_LENGTH_REVERSE = Comparator.comparingInt(p -> -p.getPathCount());
private final MainWindow mainWindow;
private final JTree tree;
private final JNodeCache nodeCache;
public TreeExpansionService(MainWindow mainWindow, JTree tree) {
this.mainWindow = mainWindow;
this.tree = tree;
this.nodeCache = mainWindow.getCacheObject().getNodeCache();
}
public List<String> save() {
if (tree.getRowCount() == 0 || mainWindow.getWrapper().getCurrentDecompiler().isEmpty()) {
return Collections.emptyList();
}
List<TreePath> expandedPaths = collectExpandedPaths(tree);
List<String> list = new ArrayList<>();
for (TreePath expandedPath : expandedPaths) {
list.add(savePath(expandedPath));
}
if (DEBUG) {
LOG.debug("Saving tree expansions:\n {}", Utils.listToString(list, "\n "));
}
return list;
}
public void load(List<String> treeExpansions) {
List<TreePath> expandedPaths = new ArrayList<>();
mainWindow.getBackgroundExecutor().execute(NLS.str("progress.load"),
() -> {
loadPaths(treeExpansions, expandedPaths);
// send expand event to load sub-nodes and wait for completion
UiUtils.uiRunAndWait(() -> expandedPaths.forEach(path -> {
try {
tree.fireTreeWillExpand(path);
} catch (Exception e) {
throw new JadxRuntimeException("Tree expand error", e);
}
}));
},
s -> {
// expand paths after a loading task is finished
expandedPaths.forEach(tree::expandPath);
});
}
private void loadPaths(List<String> treeExpansions, List<TreePath> expandedPaths) {
if (DEBUG) {
LOG.debug("Restoring tree expansions:\n {}", Utils.listToString(treeExpansions, "\n "));
}
for (String treeExpansion : treeExpansions) {
try {
TreePath treePath = loadPath(treeExpansion);
if (treePath != null) {
expandedPaths.add(treePath);
}
} catch (Exception e) {
LOG.warn("Failed to load tree expansion entry: {}", treeExpansion, e);
}
}
if (DEBUG) {
LOG.debug("Restored expanded tree paths:\n {}", Utils.listToString(expandedPaths, "\n "));
}
}
private String savePath(TreePath path) {
JNode node = (JNode) path.getLastPathComponent();
if (node instanceof JPackage) {
return "p:" + ((JPackage) node).getPkg().getRawFullName();
}
if (node instanceof JClass) {
return "c:" + ((JClass) node).getCls().getRawName();
}
return Arrays.stream(path.getPath())
.map(p -> ((JNode) p).getID())
.skip(1) // skip root
.collect(Collectors.joining("//", "t:", ""));
}
private @Nullable TreePath loadPath(String pathStr) {
String pathData = pathStr.substring(2);
switch (pathStr.charAt(0)) {
case 'c':
return getTreePathForRef(getRoot().resolveRawClass(pathData));
case 'p':
return getTreePathForRef(getRoot().resolvePackage(pathData));
case 't':
return resolveTreePath(pathData.split("//"));
default:
throw new JadxRuntimeException("Unknown tree expansion path type: " + pathStr);
}
}
private @Nullable TreePath resolveTreePath(String[] pathArr) {
JNode current = (JNode) tree.getModel().getRoot();
for (String nodeStr : pathArr) {
JNode node = current.searchNode(n -> n.getID().equals(nodeStr));
if (node == null) {
if (DEBUG) {
List<String> children = current.childrenList().stream()
.map(n -> ((JNode) n).getID())
.collect(Collectors.toList());
LOG.warn("Failed to restore path: {}, node '{}' not found in '{}' children: {}",
Arrays.toString(pathArr), nodeStr, current, children);
}
return null;
}
current = node;
}
return new TreePath(current.getPath());
}
private @Nullable TreePath getTreePathForRef(@Nullable ICodeNodeRef ref) {
if (ref == null) {
return null;
}
JNode node = nodeCache.makeFrom(ref);
if (node.getParent() == null) {
if (DEBUG) {
LOG.warn("Resolving node not from tree: {}", node);
}
JNode treeNode = ((JRoot) tree.getModel().getRoot()).searchNode(node);
if (treeNode == null) {
if (DEBUG) {
LOG.error("Node not found in tree: {}", node);
}
return null;
}
node = treeNode;
}
TreeNode[] pathNodes = ((DefaultTreeModel) tree.getModel()).getPathToRoot(node);
if (pathNodes == null) {
return null;
}
return new TreePath(pathNodes);
}
private static List<TreePath> collectExpandedPaths(JTree tree) {
TreePath root = tree.getPathForRow(0);
Enumeration<TreePath> expandedDescendants = tree.getExpandedDescendants(root);
if (expandedDescendants == null) {
return Collections.emptyList();
}
List<TreePath> expandedPaths = new ArrayList<>();
while (expandedDescendants.hasMoreElements()) {
TreePath path = expandedDescendants.nextElement();
if (path.getPathCount() > 1) {
expandedPaths.add(path);
}
}
// filter out sub-paths
expandedPaths.sort(PATH_LENGTH_REVERSE); // put the longest paths before sub-paths
List<TreePath> result = new ArrayList<>();
for (TreePath path : expandedPaths) {
if (!isSubPath(result, path)) {
result.add(path);
}
}
return result;
}
private static boolean isSubPath(List<TreePath> paths, TreePath path) {
for (TreePath addedPath : paths) {
if (path.isDescendant(addedPath)) {
return true;
}
}
return false;
}
private RootNode getRoot() {
return mainWindow.getWrapper().getDecompiler().getRoot();
}
}
@@ -38,6 +38,11 @@ public class JInputFiles extends JNode {
return INPUT_FILES_ICON;
}
@Override
public String getID() {
return "JInputFiles";
}
@Override
public String makeString() {
return NLS.str("tree.input_files");
@@ -39,6 +39,11 @@ public class JInputScripts extends JNode {
return INPUT_SCRIPTS_ICON;
}
@Override
public String getID() {
return "JInputScripts";
}
@Override
public String makeString() {
return NLS.str("tree.input_scripts");
@@ -45,6 +45,11 @@ public class JInputs extends JNode {
return INPUTS_ICON;
}
@Override
public String getID() {
return "JInputs";
}
@Override
public String makeString() {
return NLS.str("tree.inputs_title");
@@ -1,5 +1,9 @@
package jadx.gui.treemodel;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import jadx.gui.jobs.IBackgroundTask;
public abstract class JLoadableNode extends JNode {
@@ -7,5 +11,17 @@ public abstract class JLoadableNode extends JNode {
public abstract void loadNode();
public abstract IBackgroundTask getLoadTask();
public abstract @Nullable IBackgroundTask getLoadTask();
@Override
public @Nullable JNode searchNode(Predicate<JNode> filter) {
loadNode();
return super.searchNode(filter);
}
@Override
public @Nullable JNode removeNode(Predicate<JNode> filter) {
loadNode();
return super.removeNode(filter);
}
}
@@ -2,11 +2,13 @@ package jadx.gui.treemodel;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
import java.util.function.Predicate;
import javax.swing.Icon;
import javax.swing.JPopupMenu;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.jetbrains.annotations.NotNull;
@@ -15,6 +17,7 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeInfo;
import jadx.api.JavaNode;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.utils.ListUtils;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.ui.tab.TabbedPane;
@@ -48,7 +51,6 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable
return SyntaxConstants.SYNTAX_STYLE_NONE;
}
@NotNull
public ICodeInfo getCodeInfo() {
return ICodeInfo.EMPTY;
}
@@ -75,6 +77,15 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable
return null;
}
/**
* JNode identifier.
* Should be locale independent.
* TODO: implement list or enum of custom tree nodes to allow extension from plugins
*/
public String getID() {
return makeString();
}
public abstract String makeString();
public String makeStringHtml() {
@@ -139,6 +150,10 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable
return null;
}
public List<TreeNode> childrenList() {
return ListUtils.enumerationToList(this.children());
}
private static final Comparator<JNode> COMPARATOR = Comparator
.comparing(JNode::makeLongString)
.thenComparingInt(JNode::getPos);
@@ -20,7 +20,6 @@ import jadx.api.impl.SimpleCodeInfo;
import jadx.core.utils.ListUtils;
import jadx.core.utils.Utils;
import jadx.core.xmlgen.ResContainer;
import jadx.gui.jobs.IBackgroundTask;
import jadx.gui.jobs.SimpleTask;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.BinaryContentPanel;
@@ -63,7 +62,7 @@ public class JResource extends JLoadableNode {
private transient volatile boolean loaded;
private transient List<JResource> subNodes = Collections.emptyList();
private transient ICodeInfo content;
private transient ICodeInfo content = ICodeInfo.EMPTY;
public JResource(ResourceFile resFile, String name, JResType type) {
this(resFile, name, name, type);
@@ -77,7 +76,7 @@ public class JResource extends JLoadableNode {
this.loaded = false;
}
public final void update() {
public synchronized void update() {
removeAllChildren();
if (Utils.isEmpty(subNodes)) {
if (type == JResType.DIR || type == JResType.ROOT
@@ -91,6 +90,10 @@ public class JResource extends JLoadableNode {
res.update();
add(res);
}
if (type != JResType.FILE) {
// no content, nothing to load
loaded = true;
}
}
}
@@ -101,7 +104,10 @@ public class JResource extends JLoadableNode {
}
@Override
public synchronized IBackgroundTask getLoadTask() {
public synchronized SimpleTask getLoadTask() {
if (loaded) {
return null;
}
return new SimpleTask(NLS.str("progress.load"), this::getCodeInfo, this::update);
}
@@ -316,6 +322,14 @@ public class JResource extends JLoadableNode {
return null;
}
@Override
public String getID() {
if (type == JResType.ROOT) {
return "JResources";
}
return makeString();
}
@Override
public String makeString() {
return shortName;
@@ -161,6 +161,11 @@ public class JRoot extends JNode {
return null;
}
@Override
public String getID() {
return "JRoot";
}
@Override
public String makeString() {
JadxProject project = wrapper.getProject();
@@ -44,6 +44,11 @@ public class JSources extends JNode {
return null;
}
@Override
public String getID() {
return "JSources";
}
@Override
public String makeString() {
return NLS.str("tree.sources_title");
@@ -27,7 +27,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
@@ -102,7 +101,9 @@ import jadx.gui.events.types.JadxGuiEventsImpl;
import jadx.gui.jobs.BackgroundExecutor;
import jadx.gui.jobs.DecompileTask;
import jadx.gui.jobs.ExportTask;
import jadx.gui.jobs.IBackgroundTask;
import jadx.gui.jobs.TaskStatus;
import jadx.gui.jobs.TaskWithExtraOnFinish;
import jadx.gui.logs.LogCollector;
import jadx.gui.logs.LogOptions;
import jadx.gui.logs.LogPanel;
@@ -113,8 +114,8 @@ import jadx.gui.settings.JadxProject;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.ui.JadxSettingsWindow;
import jadx.gui.settings.ui.plugins.PluginSettings;
import jadx.gui.tree.TreeExpansionService;
import jadx.gui.treemodel.ApkSignature;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JLoadableNode;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JPackage;
@@ -199,6 +200,7 @@ public class MainWindow extends JFrame implements ExportProjectDialog.ExportProj
private final transient CacheManager cacheManager;
private final transient BackgroundExecutor backgroundExecutor;
private final transient JadxGuiEventsImpl events = new JadxGuiEventsImpl();
private final transient TreeExpansionService treeExpansionService;
private final TabsController tabsController;
private final NavigationController navController;
@@ -271,6 +273,7 @@ public class MainWindow extends JFrame implements ExportProjectDialog.ExportProj
initUI();
this.editorSyncManager = new EditorSyncManager(this, tabbedPane);
this.backgroundExecutor = new BackgroundExecutor(settings, progressPane);
this.treeExpansionService = new TreeExpansionService(this, tree);
initMenuAndToolbar();
UiUtils.setWindowIcons(this);
this.shortcutsController.registerMouseEventListener(this);
@@ -603,6 +606,7 @@ public class MainWindow extends JFrame implements ExportProjectDialog.ExportProj
private void saveAll() {
saveOpenTabs();
project.setTreeExpansions(treeExpansionService.save());
BreakpointManager.saveAndExit();
}
@@ -645,7 +649,7 @@ public class MainWindow extends JFrame implements ExportProjectDialog.ExportProj
initTree();
updateLiveReload(project.isEnableLiveReload());
BreakpointManager.init(project.getFilePaths().get(0).toAbsolutePath().getParent());
treeExpansionService.load(project.getTreeExpansions());
List<EditorViewState> openTabs = project.getOpenTabs(this);
backgroundExecutor.execute(NLS.str("progress.load"),
() -> preLoadOpenTabs(openTabs),
@@ -839,41 +843,14 @@ public class MainWindow extends JFrame implements ExportProjectDialog.ExportProj
public void reloadTree() {
treeReloading = true;
treeUpdateListener.forEach(listener -> listener.accept(treeRoot));
treeModel.reload();
List<String[]> treeExpansions = project.getTreeExpansions();
if (!treeExpansions.isEmpty()) {
expand(treeRoot, treeExpansions);
} else {
tree.expandRow(1);
}
treeReloading = false;
}
public void rebuildPackagesTree() {
cacheObject.setPackageHelper(null);
treeRoot.update();
}
private void expand(TreeNode node, List<String[]> treeExpansions) {
TreeNode[] pathNodes = treeModel.getPathToRoot(node);
if (pathNodes == null) {
return;
}
TreePath path = new TreePath(pathNodes);
String[] pathExpansion = getPathExpansion(path);
for (String[] expansion : treeExpansions) {
if (Arrays.equals(expansion, pathExpansion)) {
tree.expandPath(path);
break;
}
}
for (int i = node.getChildCount() - 1; i >= 0; i--) {
expand(node.getChildAt(i), treeExpansions);
}
}
private void toggleFlattenPackage() {
setFlattenPackage(!isFlattenPackage);
}
@@ -1216,6 +1193,8 @@ public class MainWindow extends JFrame implements ExportProjectDialog.ExportProj
ExceptionDialog.throwTestException();
}
});
}
if (UiUtils.JADX_GUI_DEBUG) {
JCheckBoxMenuItem uiWatchDog = new JCheckBoxMenuItem(new ActionHandler("UI WatchDog", UIWatchDog::toggle));
uiWatchDog.setState(UIWatchDog.onStart());
help.add(uiWatchDog);
@@ -1374,19 +1353,14 @@ public class MainWindow extends JFrame implements ExportProjectDialog.ExportProj
Object node = path.getLastPathComponent();
if (node instanceof JLoadableNode) {
JLoadableNode treeNode = (JLoadableNode) node;
backgroundExecutor.execute(treeNode.getLoadTask());
// schedule update for expanded nodes in a tree
backgroundExecutor.execute(NLS.str("progress.load"),
UiUtils.EMPTY_RUNNABLE,
IBackgroundTask loadTask = treeNode.getLoadTask();
if (loadTask != null) {
backgroundExecutor.execute(new TaskWithExtraOnFinish(loadTask,
status -> {
if (!treeReloading) {
treeModel.nodeStructureChanged(treeNode);
project.addTreeExpansion(getPathExpansion(event.getPath()));
}
});
} else {
if (!treeReloading) {
project.addTreeExpansion(getPathExpansion(event.getPath()));
}));
}
}
}
@@ -1394,7 +1368,6 @@ public class MainWindow extends JFrame implements ExportProjectDialog.ExportProj
@Override
public void treeWillCollapse(TreeExpansionEvent event) {
if (!treeReloading) {
project.removeTreeExpansion(getPathExpansion(event.getPath()));
update();
}
}
@@ -1444,35 +1417,6 @@ public class MainWindow extends JFrame implements ExportProjectDialog.ExportProj
setTitle(DEFAULT_TITLE);
}
private static String[] getPathExpansion(TreePath path) {
List<String> pathList = new ArrayList<>();
while (path != null) {
Object node = path.getLastPathComponent();
String name;
if (node instanceof JClass) {
name = ((JClass) node).getCls().getClassNode().getClassInfo().getFullName();
} else {
name = node.toString();
}
pathList.add(name);
path = path.getParentPath();
}
return pathList.toArray(new String[0]);
}
public static void getExpandedPaths(JTree tree, TreePath path, List<TreePath> list) {
if (tree.isExpanded(path)) {
list.add(path);
TreeNode node = (TreeNode) path.getLastPathComponent();
for (int i = node.getChildCount() - 1; i >= 0; i--) {
TreeNode n = node.getChildAt(i);
TreePath child = path.pathByAddingChild(n);
getExpandedPaths(tree, child, list);
}
}
}
public void setLocationAndPosition() {
if (settings.loadWindowPos(this)) {
return;
@@ -5,7 +5,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
@@ -17,12 +16,13 @@ import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.gui.jobs.SimpleTask;
import jadx.gui.jobs.TaskStatus;
import jadx.gui.jobs.TaskWithExtraOnFinish;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.EditorViewState;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.UiUtils;
public class TabsController {
private static final Logger LOG = LoggerFactory.getLogger(TabsController.class);
@@ -113,16 +113,12 @@ public class TabsController {
private void loadCodeWithUIAction(JClass cls, Runnable action) {
SimpleTask loadTask = cls.getLoadTask();
mainWindow.getBackgroundExecutor().execute(
new SimpleTask(loadTask.getTitle(),
loadTask.getJobs(),
status -> {
Consumer<TaskStatus> onFinish = loadTask.getOnFinish();
if (onFinish != null) {
onFinish.accept(status);
if (loadTask == null) {
// already loaded
UiUtils.uiRun(action);
return;
}
action.run();
}));
mainWindow.getBackgroundExecutor().execute(new TaskWithExtraOnFinish(loadTask, action));
}
/**
@@ -6,7 +6,6 @@ import jadx.core.Jadx
import jadx.core.plugins.versions.VersionComparator
import jadx.core.utils.GsonUtils.buildGson
import jadx.gui.settings.JadxUpdateChannel
import org.jetbrains.kotlin.konan.file.use
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URI
@@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
import com.formdev.flatlaf.extras.FlatSVGIcon;
import jadx.commons.app.JadxCommonEnv;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.StringUtils;
@@ -51,6 +52,8 @@ import jadx.gui.ui.codearea.AbstractCodeArea;
public class UiUtils {
private static final Logger LOG = LoggerFactory.getLogger(UiUtils.class);
public static final boolean JADX_GUI_DEBUG = JadxCommonEnv.getBool("JADX_GUI_DEBUG", false);
/**
* The minimum about of memory in bytes we are trying to keep free, otherwise the application may
* run out of heap
@@ -20,17 +20,12 @@ import jadx.gui.utils.UiUtils;
public class UIWatchDog {
private static final Logger LOG = LoggerFactory.getLogger(UIWatchDog.class);
private static final boolean RUN_ON_START = true;
private static final int UI_MAX_DELAY_MS = 200;
private static final int CHECK_INTERVAL_MS = 50;
public static boolean onStart() {
if (RUN_ON_START) {
UiUtils.uiRun(UIWatchDog::toggle);
return true;
}
return false;
UiUtils.uiRunAndWait(UIWatchDog::toggle);
return INSTANCE.isEnabled();
}
public static synchronized void toggle() {
@@ -69,6 +64,10 @@ public class UIWatchDog {
}
}
private boolean isEnabled() {
return enabled.get();
}
@SuppressWarnings("BusyWait")
private void start(Thread uiThread) {
LOG.debug("UI watchdog started");