JTree Rewrite

This is a better implementation of the managing the JTree. Instead of destroying and creating the tree each time a resource is imported. It's now preserved and the tree nodes are managed by the resource container
This commit is contained in:
Konloch 2021-07-16 13:55:03 -07:00
parent 130eddc7cc
commit 995158a8d2
23 changed files with 167 additions and 147 deletions

View file

@ -67,6 +67,7 @@ import static the.bytecode.club.bytecodeviewer.util.MiscUtils.guessLanguage;
* http://the.bytecode.club * http://the.bytecode.club
* *
* TODO BUGS: * TODO BUGS:
* + Switching from Dark to System theme crashes
* + View>Visual Settings>Show Class Methods * + View>Visual Settings>Show Class Methods
* + Spam-clicking the refresh button will cause the swing thread to deadlock (Quickly opening resources used to also do this) * + Spam-clicking the refresh button will cause the swing thread to deadlock (Quickly opening resources used to also do this)
* This is caused by the ctrlMouseWheelZoom code, a temporary patch is just removing it worst case * This is caused by the ctrlMouseWheelZoom code, a temporary patch is just removing it worst case
@ -80,7 +81,7 @@ import static the.bytecode.club.bytecodeviewer.util.MiscUtils.guessLanguage;
* + Anything using blindlySearchForClassNode() should instead search through the resource container search function * + Anything using blindlySearchForClassNode() should instead search through the resource container search function
* *
* TODO IN-PROGRESS: * TODO IN-PROGRESS:
* + Resource Exporter/Save/Decompile As Zip needs to be rewrittern * + Resource Exporter/Save/Decompile As Zip needs to be rewritten
* + Finish dragging code * + Finish dragging code
* + Finish right-click tab menu detection * + Finish right-click tab menu detection
* + Fix hook inject for EZ-Injection * + Fix hook inject for EZ-Injection
@ -274,6 +275,22 @@ public class BytecodeViewer
openFiles(new File[]{new File(s)}, true); openFiles(new File[]{new File(s)}, true);
} }
/**
* Adds a resource container to BCVs resource container list
*/
public static void addResourceContainer(ResourceContainer container)
{
resourceContainers.add(container);
SwingUtilities.invokeLater(()->
{
try {
viewer.resourcePane.addResourceContainer(container);
} catch (Exception e) {
e.printStackTrace();
}
});
}
/** /**
* Returns true if there is at least one file resource loaded * Returns true if there is at least one file resource loaded
*/ */
@ -571,6 +588,20 @@ public class BytecodeViewer
return false; return false;
} }
/**
* Returns true if there are no loaded resource classes
*/
public static boolean promptIfNoLoadedResources()
{
if (BytecodeViewer.resourceContainers.isEmpty())
{
BytecodeViewer.showMessage("First open a resource such as class, jar, zip or apk file.");
return true;
}
return false;
}
/** /**
* Handle the exception by creating a new window for bug reporting * Handle the exception by creating a new window for bug reporting
*/ */

View file

@ -78,7 +78,7 @@ public class GlobalHotKeys
{ {
Configuration.lastHotKeyExecuted = System.currentTimeMillis(); Configuration.lastHotKeyExecuted = System.currentTimeMillis();
if (BytecodeViewer.promptIfNoLoadedClasses()) if (BytecodeViewer.promptIfNoLoadedResources())
return; return;
Thread resourceExport = new Thread(() -> Thread resourceExport = new Thread(() ->

View file

@ -222,7 +222,7 @@ public class SettingsSerializer
//line 129 is used normal loading //line 129 is used normal loading
Configuration.language = Language.valueOf(asString(130)); Configuration.language = Language.valueOf(asString(130));
} }
catch (ArrayIndexOutOfBoundsException e) catch (IndexOutOfBoundsException e)
{ {
//ignore because errors are expected, first start up and outdated settings. //ignore because errors are expected, first start up and outdated settings.
} }
@ -389,7 +389,7 @@ public class SettingsSerializer
Configuration.python2Extra = asString(135); Configuration.python2Extra = asString(135);
Configuration.python3Extra = asString(136); Configuration.python3Extra = asString(136);
} }
catch (ArrayIndexOutOfBoundsException e) catch (IndexOutOfBoundsException e)
{ {
//ignore because errors are expected, first start up and outdated settings. //ignore because errors are expected, first start up and outdated settings.
} }

View file

@ -123,7 +123,7 @@ public class ExceptionUI extends JFrame
*/ */
public static String buildErrorLogHeader(String author) public static String buildErrorLogHeader(String author)
{ {
String fatJar = FAT_JAR ? " [FatJar] " : ""; String fatJar = FAT_JAR ? " [FatJar]" : "";
return "Please send this error log to " + author + return "Please send this error log to " + author +
"\nIf you hold appropriate legal rights to the relevant class/jar/apk file please include that as well." + "\nIf you hold appropriate legal rights to the relevant class/jar/apk file please include that as well." +

View file

@ -44,7 +44,7 @@ public abstract class Plugin extends Thread
try try
{ {
if (BytecodeViewer.promptIfNoLoadedClasses()) if (BytecodeViewer.promptIfNoLoadedResources())
return; return;
executeContainer(); executeContainer();

View file

@ -153,7 +153,10 @@ public class KrakatauDecompiler extends InternalDecompiler
} }
@Override @Override
public String decompileClassNode(ClassNode cn, byte[] b) { public String decompileClassNode(ClassNode cn, byte[] b)
{
//TODO look into transforming through krakatau as a zip rather than direct classfile
if(!ExternalResources.getSingleton().hasSetPython2Command()) if(!ExternalResources.getSingleton().hasSetPython2Command())
return "You need to set your Python 2.7 (or PyPy 2.7 for speed) executable path!"; return "You need to set your Python 2.7 (or PyPy 2.7 for speed) executable path!";

View file

@ -79,7 +79,8 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File
public final KeyAdapter search = new SearchKeyAdapter(this); public final KeyAdapter search = new SearchKeyAdapter(this);
private void showPopMenu(ResourceTree tree, TreePath selPath, int x, int y) { private void showPopMenu(ResourceTree tree, TreePath selPath, int x, int y)
{
if (selPath == null) if (selPath == null)
return; return;
@ -87,16 +88,20 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File
rightClickMenu.add(new ResourceListRightClickRemove(this, x, y, tree)); rightClickMenu.add(new ResourceListRightClickRemove(this, x, y, tree));
rightClickMenu.add(new AbstractAction("Expand", IconResources.CollapsedIcon.createCollapsedIcon()) { rightClickMenu.add(new AbstractAction("Expand", IconResources.CollapsedIcon.createCollapsedIcon())
{
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e)
{
TreePath selPath = ResourceListPane.this.tree.getPathForLocation(x, y); TreePath selPath = ResourceListPane.this.tree.getPathForLocation(x, y);
expandAll(tree, Objects.requireNonNull(selPath), true); expandAll(tree, Objects.requireNonNull(selPath), true);
} }
}); });
rightClickMenu.add(new AbstractAction("Collapse", IconResources.ExpandedIcon.createExpandedIcon()) { rightClickMenu.add(new AbstractAction("Collapse", IconResources.ExpandedIcon.createExpandedIcon())
{
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e)
{
TreePath selPath = ResourceListPane.this.tree.getPathForLocation(x, y); TreePath selPath = ResourceListPane.this.tree.getPathForLocation(x, y);
expandAll(tree, Objects.requireNonNull(selPath), false); expandAll(tree, Objects.requireNonNull(selPath), false);
} }
@ -152,108 +157,109 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File
BytecodeViewer.openFiles(files, true); BytecodeViewer.openFiles(files, true);
} }
public void updateTree() public void addResourceContainer(ResourceContainer container)
{ {
try ResourceTreeNode root = container.treeNode = new ResourceTreeNode(container.name);
{
//TODO refresh while preserving the opened files from before the refresh
treeRoot.removeAllChildren();
for (ResourceContainer container : BytecodeViewer.resourceContainers)
{
ResourceTreeNode root = new ResourceTreeNode(container.name);
treeRoot.add(root);
ResourceListIconRenderer renderer = new ResourceListIconRenderer(); treeRoot.add(root);
tree.setCellRenderer(renderer); tree.setCellRenderer(new ResourceListIconRenderer());
if (!container.resourceClasses.isEmpty()) buildTree(container, root);
{
for (String name : container.resourceClasses.keySet())
{
final String[] spl = name.split("/");
if (spl.length < 2)
{
root.add(new ResourceTreeNode(name + ".class"));
}
else
{
ResourceTreeNode parent = root;
for (int i1 = 0; i1 < spl.length; i1++)
{
String s = spl[i1];
if (i1 == spl.length - 1) treeRoot.sort();
s += ".class";
ResourceTreeNode child = null; tree.expandPath(new TreePath(tree.getModel().getRoot()));
for (int i = 0; i < parent.getChildCount(); i++) tree.updateUI();
{
if (((ResourceTreeNode) parent.getChildAt(i)).getUserObject().equals(s))
{
child = (ResourceTreeNode) parent.getChildAt(i);
break;
}
}
if (child == null)
{
child = new ResourceTreeNode(s);
parent.add(child);
}
parent = child;
}
}
}
}
if (!container.resourceFiles.isEmpty())
{
for (final Entry<String, byte[]> entry : container.resourceFiles.entrySet())
{
String name = entry.getKey();
final String[] spl = name.split("/");
if (spl.length < 2)
{
root.add(new ResourceTreeNode(name));
}
else
{
ResourceTreeNode parent = root;
for (final String s : spl)
{
ResourceTreeNode child = null;
for (int i = 0; i < parent.getChildCount(); i++)
{
if (((ResourceTreeNode) parent.getChildAt(i)).getUserObject().equals(s))
{
child = (ResourceTreeNode) parent.getChildAt(i);
break;
}
}
if (child == null)
{
child = new ResourceTreeNode(s);
parent.add(child);
}
parent = child;
}
}
}
}
}
treeRoot.sort();
tree.expandPath(new TreePath(tree.getModel().getRoot()));
tree.updateUI();
} catch (java.util.ConcurrentModificationException e) {
//ignore, the last file will reset everything
}
//TODO add a setting for this //TODO add a setting for this
// expandAll(tree, true); // expandAll(tree, true);
} }
public void removeResource(ResourceContainer container)
{
container.treeNode.removeFromParent();
tree.updateUI();
}
private void buildTree(ResourceContainer container, ResourceTreeNode root)
{
if (!container.resourceClasses.isEmpty())
{
for (String name : container.resourceClasses.keySet())
{
final String[] spl = name.split("/");
if (spl.length < 2)
{
root.add(new ResourceTreeNode(name + ".class"));
}
else
{
ResourceTreeNode parent = root;
for (int i1 = 0; i1 < spl.length; i1++)
{
String s = spl[i1];
if (i1 == spl.length - 1)
s += ".class";
ResourceTreeNode child = null;
for (int i = 0; i < parent.getChildCount(); i++)
{
if (((ResourceTreeNode) parent.getChildAt(i)).getUserObject().equals(s))
{
child = (ResourceTreeNode) parent.getChildAt(i);
break;
}
}
if (child == null)
{
child = new ResourceTreeNode(s);
parent.add(child);
}
parent = child;
}
}
}
}
if (!container.resourceFiles.isEmpty())
{
for (final Entry<String, byte[]> entry : container.resourceFiles.entrySet())
{
String name = entry.getKey();
final String[] spl = name.split("/");
if (spl.length < 2)
{
root.add(new ResourceTreeNode(name));
}
else
{
ResourceTreeNode parent = root;
for (final String s : spl)
{
ResourceTreeNode child = null;
for (int i = 0; i < parent.getChildCount(); i++)
{
if (((ResourceTreeNode) parent.getChildAt(i)).getUserObject().equals(s))
{
child = (ResourceTreeNode) parent.getChildAt(i);
break;
}
}
if (child == null)
{
child = new ResourceTreeNode(s);
parent.add(child);
}
parent = child;
}
}
}
}
}
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private void expandAll(final JTree tree, final TreePath parent, private void expandAll(final JTree tree, final TreePath parent,
final boolean expand) { final boolean expand) {
@ -339,14 +345,6 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File
try try
{ {
imp.getImporter().open(tempFile); imp.getImporter().open(tempFile);
SwingUtilities.invokeLater(()->
{
try
{
updateTree();
}
catch (NullPointerException ignored) { }
});
} }
catch (Exception e) catch (Exception e)
{ {

View file

@ -48,12 +48,12 @@ public class ResourceListRightClickRemove extends AbstractAction
{ {
if (resourceContainer.name.equals(selectNode.toString())) if (resourceContainer.name.equals(selectNode.toString()))
{ {
resourceListPane.removeResource(resourceContainer);
resourceListPane.removeFile(resourceContainer); resourceListPane.removeFile(resourceContainer);
break; break;
} }
} }
resourceListPane.updateTree();
return; return;
} }
} }

View file

@ -45,18 +45,12 @@ public class CodeSequenceDiagram extends Plugin
{ {
public static void open() public static void open()
{ {
if (BytecodeViewer.promptIfNoLoadedClasses())
return;
PluginManager.runPlugin(new CodeSequenceDiagram()); PluginManager.runPlugin(new CodeSequenceDiagram());
} }
@Override @Override
public void execute(ArrayList<ClassNode> classNodeList) public void execute(ArrayList<ClassNode> classNodeList)
{ {
if (BytecodeViewer.promptIfNoLoadedClasses())
return;
if (!BytecodeViewer.isActiveResourceClass()) if (!BytecodeViewer.isActiveResourceClass())
{ {
BytecodeViewer.showMessage("First open a class file."); BytecodeViewer.showMessage("First open a class file.");

View file

@ -7,6 +7,7 @@ import org.apache.commons.compress.compressors.FileNameUtil;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.ClassNode;
import the.bytecode.club.bytecodeviewer.api.ASMUtil; import the.bytecode.club.bytecodeviewer.api.ASMUtil;
import the.bytecode.club.bytecodeviewer.gui.resourcelist.ResourceTreeNode;
import the.bytecode.club.bytecodeviewer.util.LazyNameUtil; import the.bytecode.club.bytecodeviewer.util.LazyNameUtil;
/*************************************************************************** /***************************************************************************
@ -38,7 +39,8 @@ public class ResourceContainer
{ {
public File file; public File file;
public String name; public String name;
public File APKToolContents = null; public File APKToolContents;
public ResourceTreeNode treeNode;
public LinkedHashMap<String, byte[]> resourceFiles = new LinkedHashMap<>(); public LinkedHashMap<String, byte[]> resourceFiles = new LinkedHashMap<>();
public LinkedHashMap<String, byte[]> resourceClassBytes = new LinkedHashMap<>(); public LinkedHashMap<String, byte[]> resourceClassBytes = new LinkedHashMap<>();

View file

@ -25,7 +25,7 @@ public class APKExport implements Exporter
@Override @Override
public void promptForExport() public void promptForExport()
{ {
if (BytecodeViewer.promptIfNoLoadedClasses()) if (BytecodeViewer.promptIfNoLoadedResources())
return; return;
List<ResourceContainer> containers = BytecodeViewer.getResourceContainers(); List<ResourceContainer> containers = BytecodeViewer.getResourceContainers();

View file

@ -25,7 +25,7 @@ public class DexExport implements Exporter
@Override @Override
public void promptForExport() public void promptForExport()
{ {
if (BytecodeViewer.promptIfNoLoadedClasses()) if (BytecodeViewer.promptIfNoLoadedResources())
return; return;
Thread exportThread = new Thread(() -> Thread exportThread = new Thread(() ->

View file

@ -19,7 +19,7 @@ public class RunnableJarExporter implements Exporter
@Override @Override
public void promptForExport() public void promptForExport()
{ {
if (BytecodeViewer.promptIfNoLoadedClasses()) if (BytecodeViewer.promptIfNoLoadedResources())
return; return;
Thread exportThread = new Thread(() -> Thread exportThread = new Thread(() ->

View file

@ -19,7 +19,7 @@ public class ZipExport implements Exporter
@Override @Override
public void promptForExport() public void promptForExport()
{ {
if (BytecodeViewer.promptIfNoLoadedClasses()) if (BytecodeViewer.promptIfNoLoadedResources())
return; return;
Thread exportThread = new Thread(() -> Thread exportThread = new Thread(() ->

View file

@ -70,14 +70,6 @@ public class ImportResource implements Runnable
finally finally
{ {
BytecodeViewer.updateBusyStatus(false); BytecodeViewer.updateBusyStatus(false);
SwingUtilities.invokeLater(()->
{
try
{
BytecodeViewer.viewer.resourcePane.updateTree();
}
catch (NullPointerException ignored) { }
});
} }
} }

View file

@ -50,6 +50,6 @@ public class APKResourceImporter implements Importer
container.copy(new ResourceContainerImporter( container.copy(new ResourceContainerImporter(
new ResourceContainer(output)).importAsZip().getContainer()); new ResourceContainer(output)).importAsZip().getContainer());
BytecodeViewer.resourceContainers.add(container); BytecodeViewer.addResourceContainer(container);
} }
} }

View file

@ -38,6 +38,6 @@ public class ClassResourceImporter implements Importer
//TODO double check this //TODO double check this
container.resourceFiles.put(name, bytes); container.resourceFiles.put(name, bytes);
} }
BytecodeViewer.resourceContainers.add(container); BytecodeViewer.addResourceContainer(container);
} }
} }

View file

@ -39,6 +39,6 @@ public class DEXResourceImporter implements Importer
container.copy(new ResourceContainerImporter( container.copy(new ResourceContainerImporter(
new ResourceContainer(output)).importAsZip().getContainer()); new ResourceContainer(output)).importAsZip().getContainer());
BytecodeViewer.resourceContainers.add(container); BytecodeViewer.addResourceContainer(container);
} }
} }

View file

@ -86,6 +86,6 @@ public class DirectoryResourceImporter implements Importer
container.resourceClasses.putAll(allDirectoryClasses); container.resourceClasses.putAll(allDirectoryClasses);
container.resourceFiles = allDirectoryFiles; container.resourceFiles = allDirectoryFiles;
BytecodeViewer.resourceContainers.add(container); BytecodeViewer.addResourceContainer(container);
} }
} }

View file

@ -23,6 +23,6 @@ public class FileResourceImporter implements Importer
//import the file into the resource container //import the file into the resource container
importer.importAsFile(); importer.importAsFile();
//add the resource container to BCV's total loaded files //add the resource container to BCV's total loaded files
BytecodeViewer.resourceContainers.add(container); BytecodeViewer.addResourceContainer(container);
} }
} }

View file

@ -72,7 +72,7 @@ public class XAPKResourceImporter implements Importer
Configuration.silenceExceptionGUI--; //turn exceptions back on Configuration.silenceExceptionGUI--; //turn exceptions back on
BytecodeViewer.viewer.clearBusyStatus(); //clear errant busy signals from failed APK imports BytecodeViewer.viewer.clearBusyStatus(); //clear errant busy signals from failed APK imports
container.resourceFiles = allDirectoryFiles; //store the file resource container.resourceFiles = allDirectoryFiles; //store the file resource
BytecodeViewer.resourceContainers.add(container); //add the resource container to BCV's total loaded files BytecodeViewer.addResourceContainer(container); //add the resource container to BCV's total loaded files
} }
public File exportTo(File original, String extension, byte[] bytes) public File exportTo(File original, String extension, byte[] bytes)

View file

@ -23,6 +23,6 @@ public class ZipResourceImporter implements Importer
//import the file as zip into the resource container //import the file as zip into the resource container
importer.importAsZip(); importer.importAsZip();
//add the resource container to BCV's total loaded files //add the resource container to BCV's total loaded files
BytecodeViewer.resourceContainers.add(container); BytecodeViewer.addResourceContainer(container);
} }
} }

View file

@ -103,7 +103,7 @@ public class JarUtils
} }
jis.close(); jis.close();
container.resourceFiles = files; container.resourceFiles = files;
BytecodeViewer.resourceContainers.add(container); BytecodeViewer.addResourceContainer(container);
} }
@ -152,7 +152,7 @@ public class JarUtils
} }
container.resourceFiles = files; container.resourceFiles = files;
BytecodeViewer.resourceContainers.add(container); BytecodeViewer.addResourceContainer(container);
} }
public static ArrayList<ClassNode> loadClasses(final File jarFile) throws IOException public static ArrayList<ClassNode> loadClasses(final File jarFile) throws IOException