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
*
* TODO BUGS:
* + Switching from Dark to System theme crashes
* + 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)
* 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
*
* 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 right-click tab menu detection
* + Fix hook inject for EZ-Injection
@ -274,6 +275,22 @@ public class BytecodeViewer
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
*/
@ -571,6 +588,20 @@ public class BytecodeViewer
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
*/

View file

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

View file

@ -222,7 +222,7 @@ public class SettingsSerializer
//line 129 is used normal loading
Configuration.language = Language.valueOf(asString(130));
}
catch (ArrayIndexOutOfBoundsException e)
catch (IndexOutOfBoundsException e)
{
//ignore because errors are expected, first start up and outdated settings.
}
@ -389,7 +389,7 @@ public class SettingsSerializer
Configuration.python2Extra = asString(135);
Configuration.python3Extra = asString(136);
}
catch (ArrayIndexOutOfBoundsException e)
catch (IndexOutOfBoundsException e)
{
//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)
{
String fatJar = FAT_JAR ? " [FatJar] " : "";
String fatJar = FAT_JAR ? " [FatJar]" : "";
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." +

View file

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

View file

@ -153,7 +153,10 @@ public class KrakatauDecompiler extends InternalDecompiler
}
@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())
return "You need to set your Python 2.7 (or PyPy 2.7 for speed) executable path!";

View file

@ -76,52 +76,57 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File
public final JTextField quickSearch = new TranslatedJTextField("Quick file search (no file extension)", Translation.QUICK_FILE_SEARCH_NO_FILE_EXTENSION);
public final FileDrop fileDrop;
public boolean cancel = false;
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)
return;
rightClickMenu.removeAll();
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
public void actionPerformed(ActionEvent e) {
public void actionPerformed(ActionEvent e)
{
TreePath selPath = ResourceListPane.this.tree.getPathForLocation(x, y);
expandAll(tree, Objects.requireNonNull(selPath), true);
}
});
rightClickMenu.add(new AbstractAction("Collapse", IconResources.ExpandedIcon.createExpandedIcon()) {
rightClickMenu.add(new AbstractAction("Collapse", IconResources.ExpandedIcon.createExpandedIcon())
{
@Override
public void actionPerformed(ActionEvent e) {
public void actionPerformed(ActionEvent e)
{
TreePath selPath = ResourceListPane.this.tree.getPathForLocation(x, y);
expandAll(tree, Objects.requireNonNull(selPath), false);
}
});
rightClickMenu.show(this.tree, x, y);
}
//used to remove resources from the resource list
public void removeFile(ResourceContainer resourceContainer)
{
BytecodeViewer.resourceContainers.remove(resourceContainer);
LazyNameUtil.removeName(resourceContainer.name);
}
public ResourceListPane()
{
super("Files", Translation.FILES);
tree.setRootVisible(false);
tree.setShowsRootHandles(true);
quickSearch.setForeground(Color.gray);
attachTreeListeners();
attachQuickSearchListeners();
getContentPane().setLayout(new BorderLayout());
getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER);
@ -136,13 +141,13 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File
buttonPanel.add(close, BorderLayout.WEST);
exactPanel.add(buttonPanel, BorderLayout.EAST);
quickSearchPanel.add(exactPanel, BorderLayout.SOUTH);
getContentPane().add(quickSearchPanel, BorderLayout.SOUTH);
this.setVisible(true);
fileDrop = new FileDrop(this, this);
}
@Override
public void filesDropped(final File[] files)
{
@ -151,107 +156,108 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File
BytecodeViewer.openFiles(files, true);
}
public void updateTree()
public void addResourceContainer(ResourceContainer container)
{
try
ResourceTreeNode root = container.treeNode = new ResourceTreeNode(container.name);
treeRoot.add(root);
tree.setCellRenderer(new ResourceListIconRenderer());
buildTree(container, root);
treeRoot.sort();
tree.expandPath(new TreePath(tree.getModel().getRoot()));
tree.updateUI();
//TODO add a setting for this
// expandAll(tree, true);
}
public void removeResource(ResourceContainer container)
{
container.treeNode.removeFromParent();
tree.updateUI();
}
private void buildTree(ResourceContainer container, ResourceTreeNode root)
{
if (!container.resourceClasses.isEmpty())
{
//TODO refresh while preserving the opened files from before the refresh
treeRoot.removeAllChildren();
for (ResourceContainer container : BytecodeViewer.resourceContainers)
for (String name : container.resourceClasses.keySet())
{
ResourceTreeNode root = new ResourceTreeNode(container.name);
treeRoot.add(root);
ResourceListIconRenderer renderer = new ResourceListIconRenderer();
tree.setCellRenderer(renderer);
if (!container.resourceClasses.isEmpty())
final String[] spl = name.split("/");
if (spl.length < 2)
{
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;
}
}
}
root.add(new ResourceTreeNode(name + ".class"));
}
if (!container.resourceFiles.isEmpty())
else
{
for (final Entry<String, byte[]> entry : container.resourceFiles.entrySet())
ResourceTreeNode parent = root;
for (int i1 = 0; i1 < spl.length; i1++)
{
String name = entry.getKey();
final String[] spl = name.split("/");
if (spl.length < 2)
String s = spl[i1];
if (i1 == spl.length - 1)
s += ".class";
ResourceTreeNode child = null;
for (int i = 0; i < parent.getChildCount(); i++)
{
root.add(new ResourceTreeNode(name));
}
else
{
ResourceTreeNode parent = root;
for (final String s : spl)
if (((ResourceTreeNode) parent.getChildAt(i)).getUserObject().equals(s))
{
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;
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
// expandAll(tree, true);
}
@SuppressWarnings("rawtypes")
@ -339,14 +345,6 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File
try
{
imp.getImporter().open(tempFile);
SwingUtilities.invokeLater(()->
{
try
{
updateTree();
}
catch (NullPointerException ignored) { }
});
}
catch (Exception e)
{

View file

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

View file

@ -45,18 +45,12 @@ public class CodeSequenceDiagram extends Plugin
{
public static void open()
{
if (BytecodeViewer.promptIfNoLoadedClasses())
return;
PluginManager.runPlugin(new CodeSequenceDiagram());
}
@Override
public void execute(ArrayList<ClassNode> classNodeList)
{
if (BytecodeViewer.promptIfNoLoadedClasses())
return;
if (!BytecodeViewer.isActiveResourceClass())
{
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.objectweb.asm.tree.ClassNode;
import the.bytecode.club.bytecodeviewer.api.ASMUtil;
import the.bytecode.club.bytecodeviewer.gui.resourcelist.ResourceTreeNode;
import the.bytecode.club.bytecodeviewer.util.LazyNameUtil;
/***************************************************************************
@ -38,7 +39,8 @@ public class ResourceContainer
{
public File file;
public String name;
public File APKToolContents = null;
public File APKToolContents;
public ResourceTreeNode treeNode;
public LinkedHashMap<String, byte[]> resourceFiles = new LinkedHashMap<>();
public LinkedHashMap<String, byte[]> resourceClassBytes = new LinkedHashMap<>();

View file

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

View file

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

View file

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

View file

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

View file

@ -70,14 +70,6 @@ public class ImportResource implements Runnable
finally
{
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(
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
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(
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.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
importer.importAsFile();
//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
BytecodeViewer.viewer.clearBusyStatus(); //clear errant busy signals from failed APK imports
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)

View file

@ -23,6 +23,6 @@ public class ZipResourceImporter implements Importer
//import the file as zip into the resource container
importer.importAsZip();
//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();
container.resourceFiles = files;
BytecodeViewer.resourceContainers.add(container);
BytecodeViewer.addResourceContainer(container);
}
@ -152,7 +152,7 @@ public class JarUtils
}
container.resourceFiles = files;
BytecodeViewer.resourceContainers.add(container);
BytecodeViewer.addResourceContainer(container);
}
public static ArrayList<ClassNode> loadClasses(final File jarFile) throws IOException