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:
parent
130eddc7cc
commit
995158a8d2
23 changed files with 167 additions and 147 deletions
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -78,7 +78,7 @@ public class GlobalHotKeys
|
|||
{
|
||||
Configuration.lastHotKeyExecuted = System.currentTimeMillis();
|
||||
|
||||
if (BytecodeViewer.promptIfNoLoadedClasses())
|
||||
if (BytecodeViewer.promptIfNoLoadedResources())
|
||||
return;
|
||||
|
||||
Thread resourceExport = new Thread(() ->
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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." +
|
||||
|
|
|
@ -44,7 +44,7 @@ public abstract class Plugin extends Thread
|
|||
|
||||
try
|
||||
{
|
||||
if (BytecodeViewer.promptIfNoLoadedClasses())
|
||||
if (BytecodeViewer.promptIfNoLoadedResources())
|
||||
return;
|
||||
|
||||
executeContainer();
|
||||
|
|
|
@ -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!";
|
||||
|
||||
|
|
|
@ -79,7 +79,8 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File
|
|||
|
||||
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;
|
||||
|
||||
|
@ -87,16 +88,20 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File
|
|||
|
||||
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);
|
||||
}
|
||||
|
@ -152,108 +157,109 @@ public class ResourceListPane extends TranslatedVisibleComponent implements File
|
|||
BytecodeViewer.openFiles(files, true);
|
||||
}
|
||||
|
||||
public void updateTree()
|
||||
public void addResourceContainer(ResourceContainer container)
|
||||
{
|
||||
try
|
||||
{
|
||||
//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);
|
||||
ResourceTreeNode root = container.treeNode = new ResourceTreeNode(container.name);
|
||||
|
||||
ResourceListIconRenderer renderer = new ResourceListIconRenderer();
|
||||
tree.setCellRenderer(renderer);
|
||||
treeRoot.add(root);
|
||||
tree.setCellRenderer(new ResourceListIconRenderer());
|
||||
|
||||
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];
|
||||
buildTree(container, root);
|
||||
|
||||
if (i1 == spl.length - 1)
|
||||
s += ".class";
|
||||
treeRoot.sort();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
treeRoot.sort();
|
||||
tree.expandPath(new TreePath(tree.getModel().getRoot()));
|
||||
tree.updateUI();
|
||||
} catch (java.util.ConcurrentModificationException e) {
|
||||
//ignore, the last file will reset everything
|
||||
}
|
||||
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())
|
||||
{
|
||||
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")
|
||||
private void expandAll(final JTree tree, final TreePath parent,
|
||||
final boolean expand) {
|
||||
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -25,7 +25,7 @@ public class DexExport implements Exporter
|
|||
@Override
|
||||
public void promptForExport()
|
||||
{
|
||||
if (BytecodeViewer.promptIfNoLoadedClasses())
|
||||
if (BytecodeViewer.promptIfNoLoadedResources())
|
||||
return;
|
||||
|
||||
Thread exportThread = new Thread(() ->
|
||||
|
|
|
@ -19,7 +19,7 @@ public class RunnableJarExporter implements Exporter
|
|||
@Override
|
||||
public void promptForExport()
|
||||
{
|
||||
if (BytecodeViewer.promptIfNoLoadedClasses())
|
||||
if (BytecodeViewer.promptIfNoLoadedResources())
|
||||
return;
|
||||
|
||||
Thread exportThread = new Thread(() ->
|
||||
|
|
|
@ -19,7 +19,7 @@ public class ZipExport implements Exporter
|
|||
@Override
|
||||
public void promptForExport()
|
||||
{
|
||||
if (BytecodeViewer.promptIfNoLoadedClasses())
|
||||
if (BytecodeViewer.promptIfNoLoadedResources())
|
||||
return;
|
||||
|
||||
Thread exportThread = new Thread(() ->
|
||||
|
|
|
@ -70,14 +70,6 @@ public class ImportResource implements Runnable
|
|||
finally
|
||||
{
|
||||
BytecodeViewer.updateBusyStatus(false);
|
||||
SwingUtilities.invokeLater(()->
|
||||
{
|
||||
try
|
||||
{
|
||||
BytecodeViewer.viewer.resourcePane.updateTree();
|
||||
}
|
||||
catch (NullPointerException ignored) { }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,6 @@ public class DirectoryResourceImporter implements Importer
|
|||
|
||||
container.resourceClasses.putAll(allDirectoryClasses);
|
||||
container.resourceFiles = allDirectoryFiles;
|
||||
BytecodeViewer.resourceContainers.add(container);
|
||||
BytecodeViewer.addResourceContainer(container);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue