package the.bytecode.club.bytecodeviewer.gui;
import org.objectweb.asm.tree.ClassNode;
import the.bytecode.club.bytecodeviewer.*;
import javax.swing.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.util.*;
import java.util.Map.Entry;
/***************************************************************************
* Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite *
* Copyright (C) 2014 Kalen 'Konloch' Kinloch - http://bytecodeviewer.com *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
/**
* The file navigation pane.
*
* @author Konloch
* @author WaterWolf
* @author afffsdd
*/
@SuppressWarnings("serial")
public class FileNavigationPane extends VisibleComponent implements FileDrop.Listener {
private static final String quickSearchText = "Quick file search (no file extension)";
FileChangeNotifier fcn;
JCheckBox exact = new JCheckBox("Exact");
JButton open = new JButton("+");
JButton close = new JButton("-");
MyTreeNode treeRoot = new MyTreeNode("Loaded Files:");
MyTree tree = new MyTree(treeRoot);
final JTextField quickSearch = new JTextField(quickSearchText);
public transient KeyAdapter search = new KeyAdapter() {
@Override
public void keyPressed(final KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
final String qt = quickSearch.getText();
quickSearch.setText("");
String[] path = null;
if (qt.contains(".")) {
path = qt.split("\\.");
String[] path2 = new String[path.length];
for (int i = 0; i < path.length; i++) {
path2[i] = path[i];
if (i + 2 == path.length) {
path2[i + 1] = "." + path[i + 1];
}
}
} else {
path = new String[]{qt};
}
MyTreeNode curNode = treeRoot;
if (exact.isSelected()) {
pathLoop:
for (int i = 0; i < path.length; i++) {
final String pathName = path[i];
final boolean isLast = i == path.length - 1;
for (int c = 0; c < curNode.getChildCount(); c++) {
final MyTreeNode child = (MyTreeNode) curNode.getChildAt(c);
System.out.println(pathName + ":" + ((String) child.getUserObject()));
if (((String) child.getUserObject()).equals(pathName)) {
curNode = child;
if (isLast) {
final TreePath pathn = new TreePath(curNode.getPath());
tree.setSelectionPath(pathn);
tree.makeVisible(pathn);
tree.scrollPathToVisible(pathn);
openPath(pathn); //auto open
System.out.println("Found! " + curNode);
break pathLoop;
}
continue pathLoop;
}
}
System.out.println("Could not find " + pathName);
break;
}
} else {
{
@SuppressWarnings("unchecked")
Enumeration<MyTreeNode> enums = curNode.depthFirstEnumeration();
while (enums != null && enums.hasMoreElements()) {
MyTreeNode node = enums.nextElement();
if (node.isLeaf()) {
if (((String) (node.getUserObject())).contains(path[path.length - 1])) {
TreeNode pathArray[] = node.getPath();
int k = 0;
StringBuffer fullPath = new StringBuffer();
while (pathArray != null
&& k < pathArray.length) {
MyTreeNode n = (MyTreeNode) pathArray[k];
String s = (String) (n.getUserObject());
fullPath.append(s);
if (k++ != pathArray.length - 1) {
fullPath.append(".");
}
}
String fullPathString = fullPath.toString();
if (fullPathString != null && fullPathString.contains(qt)) {
System.out.println("Found! " + node);
final TreePath pathn = new TreePath(node.getPath());
tree.setSelectionPath(pathn.getParentPath());
tree.setSelectionPath(pathn);
tree.makeVisible(pathn);
tree.scrollPathToVisible(pathn);
}
}
}
}
}
}
} else if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) {
tree.grabFocus();
}
}
};
public FileNavigationPane(final FileChangeNotifier fcn) {
super("ClassNavigation");
this.fcn = fcn;
tree.setRootVisible(false);
tree.setShowsRootHandles(true);
quickSearch.setForeground(Color.gray);
setTitle("Files");
this.open.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final TreeNode root = (TreeNode) tree.getModel().getRoot();
expandAll(tree, new TreePath(root), true);
}
});
this.close.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final TreeNode root = (TreeNode) tree.getModel().getRoot();
expandAll(tree, new TreePath(root), false);
tree.expandPath(new TreePath(root));
}
});
this.tree.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
openPath(tree.getPathForLocation(e.getX(), e.getY()));
}
});
this.tree.addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent arg0) {
if (arg0.getKeyCode() == KeyEvent.VK_ENTER) {
if (arg0.getSource() instanceof MyTree) {
MyTree tree = (MyTree) arg0.getSource();
openPath(tree.getSelectionPath());
}
} else if (arg0.getKeyCode() == KeyEvent.VK_F && arg0.isControlDown()) {
quickSearch.grabFocus();
}
}
});
quickSearch.addKeyListener(search);
quickSearch.addFocusListener(new FocusListener() {
@Override
public void focusGained(final FocusEvent arg0) {
if (quickSearch.getText().equals(quickSearchText)) {
quickSearch.setText(null);
quickSearch.setForeground(Color.black);
}
}
@Override
public void focusLost(final FocusEvent arg0) {
if (quickSearch.getText().isEmpty()) {
quickSearch.setText(quickSearchText);
quickSearch.setForeground(Color.gray);
}
}
});
getContentPane().setLayout(new BorderLayout());
getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER);
JPanel p2 = new JPanel();
p2.setLayout(new BorderLayout());
p2.add(quickSearch, BorderLayout.NORTH);
JPanel p3 = new JPanel(new BorderLayout());
p3.add(exact, BorderLayout.WEST);
JPanel p4 = new JPanel(new BorderLayout());
p4.add(open, BorderLayout.EAST);
p4.add(close, BorderLayout.WEST);
p3.add(p4, BorderLayout.EAST);
p2.add(p3, BorderLayout.SOUTH);
getContentPane().add(p2, BorderLayout.SOUTH);
this.setVisible(true);
new FileDrop(this, this);
}
public void openClassFileToWorkSpace(final String name, final String container, final ClassNode node) {
fcn.openClassFile(name, container, node);
}
public void openFileToWorkSpace(String name, final String container, byte[] contents) {
fcn.openFile(name, container, contents);
}
@Override
public void filesDropped(final File[] files) {
if (files.length < 1)
return;
BytecodeViewer.openFiles(files, true);
}
public void updateTree() {
try {
treeRoot.removeAllChildren();
for (FileContainer container : BytecodeViewer.files) {
MyTreeNode root = new MyTreeNode(container.name);
treeRoot.add(root);
ImageRenderer renderer = new ImageRenderer();
tree.setCellRenderer(renderer);
if (!container.files.isEmpty()) {
for (final Entry<String, byte[]> entry : container.files.entrySet()) {
String name = entry.getKey();
final String[] spl = name.split("/");
if (spl.length < 2) {
root.add(new MyTreeNode(name));
} else {
MyTreeNode parent = root;
for (final String s : spl) {
MyTreeNode child = null;
for (int i = 0; i < parent.getChildCount(); i++) {
if (((MyTreeNode) parent.getChildAt(i)).getUserObject()
.equals(s)) {
child = (MyTreeNode) parent.getChildAt(i);
break;
}
}
if (child == null) {
child = new MyTreeNode(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
}
// expandAll(tree, true);
}
@SuppressWarnings("rawtypes")
private void expandAll(final JTree tree, final TreePath parent,
final boolean expand) {
// Traverse children
final TreeNode node = (TreeNode) parent.getLastPathComponent();
if (node.getChildCount() >= 0) {
for (final Enumeration e = node.children(); e.hasMoreElements(); ) {
final TreeNode n = (TreeNode) e.nextElement();
final TreePath path = parent.pathByAddingChild(n);
expandAll(tree, path, expand);
}
}
// Expansion or collapse must be done bottom-up
if (expand) {
tree.expandPath(parent);
} else {
tree.collapsePath(parent);
}
}
public class MyTree extends JTree {
private static final long serialVersionUID = -2355167326094772096L;
DefaultMutableTreeNode treeRoot;
public MyTree(final DefaultMutableTreeNode treeRoot) {
super(treeRoot);
this.treeRoot = treeRoot;
}
StringMetrics m = null;
@Override
public void paint(final Graphics g) {
try {
super.paint(g);
if (m == null) {
m = new StringMetrics((Graphics2D) g);
}
if (treeRoot.getChildCount() < 1) {
g.setColor(new Color(0, 0, 0, 100));
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.white);
String s = "Drag class/jar/zip/APK/DEX here";
g.drawString(s,
((int) ((getWidth() / 2) - (m.getWidth(s) / 2))),
getHeight() / 2);
}
} catch (java.lang.InternalError | java.lang.NullPointerException e) {
}
}
}
public class MyTreeNode extends DefaultMutableTreeNode {
private static final long serialVersionUID = -8817777566176729571L;
public MyTreeNode(final Object o) {
super(o);
}
@Override
public void insert(final MutableTreeNode newChild, final int childIndex) {
super.insert(newChild, childIndex);
}
public void sort() {
recursiveSort(this);
}
@SuppressWarnings("unchecked")
private void recursiveSort(final MyTreeNode node) {
Collections.sort(node.children, nodeComparator);
final Iterator<MyTreeNode> it = node.children.iterator();
while (it.hasNext()) {
final MyTreeNode nextNode = it.next();
if (nextNode.getChildCount() > 0) {
recursiveSort(nextNode);
}
}
}
protected Comparator<MyTreeNode> nodeComparator = new Comparator<MyTreeNode>() {
@Override
public int compare(final MyTreeNode o1, final MyTreeNode o2) {
// To make sure nodes with children are always on top
final int firstOffset = o1.getChildCount() > 0 ? -1000 : 0;
final int secondOffset = o2.getChildCount() > 0 ? 1000 : 0;
return o1.toString().compareToIgnoreCase(o2.toString())
+ firstOffset + secondOffset;
}
@Override
public boolean equals(final Object obj) {
return false;
}
@Override
public int hashCode() {
final int hash = 7;
return hash;
}
};
}
/**
* @author http://stackoverflow.com/a/18450804
*/
class StringMetrics {
Font font;
FontRenderContext context;
public StringMetrics(Graphics2D g2) {
font = g2.getFont();
context = g2.getFontRenderContext();
}
Rectangle2D getBounds(String message) {
return font.getStringBounds(message, context);
}
double getWidth(String message) {
Rectangle2D bounds = getBounds(message);
return bounds.getWidth();
}
double getHeight(String message) {
Rectangle2D bounds = getBounds(message);
return bounds.getHeight();
}
}
public void resetWorkspace() {
treeRoot.removeAllChildren();
tree.repaint();
tree.updateUI();
}
public void openPath(TreePath path) {
if (path == null)
return;
final StringBuffer nameBuffer = new StringBuffer();
for (int i = 2; i < path.getPathCount(); i++) {
nameBuffer.append(path.getPathComponent(i));
if (i < path.getPathCount() - 1) {
nameBuffer.append("/");
}
}
String name = nameBuffer.toString();
String containerName = path.getPathComponent(1).toString();
if (name.endsWith(".class")) {
final ClassNode cn = BytecodeViewer.getClassNode(containerName, name.substring(0, name.length() - ".class".length()));
if (cn != null) {
openClassFileToWorkSpace(nameBuffer.toString(), containerName, cn);
}
} else {
openFileToWorkSpace(nameBuffer.toString(), containerName, BytecodeViewer.getFileContents(nameBuffer.toString()));
}
}
/**
* @author http://stackoverflow.com/questions/14968005
* @author Konloch
*/
public class ImageRenderer extends DefaultTreeCellRenderer {
public Component getTreeCellRendererComponent(
JTree tree,
Object value,
boolean sel,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) { //called every time there is a pane update, I.E. whenever you expand a folder
Component ret = super.getTreeCellRendererComponent(tree, value,
selected, expanded, leaf, row, hasFocus);
if (value != null && value instanceof the.bytecode.club.bytecodeviewer.gui.FileNavigationPane.MyTreeNode) {
the.bytecode.club.bytecodeviewer.gui.FileNavigationPane.MyTreeNode node = (the.bytecode.club.bytecodeviewer.gui.FileNavigationPane.MyTreeNode) value;
String name = node.toString();
if (name.endsWith(".jar")) {
setIcon(Resources.jarIcon);
} else if (name.endsWith(".zip")) {
setIcon(Resources.zipIcon);
} else if (name.endsWith(".bat")) {
setIcon(Resources.batIcon);
} else if (name.endsWith(".sh")) {
setIcon(Resources.shIcon);
} else if (name.endsWith(".cs")) {
setIcon(Resources.csharpIcon);
} else if (name.endsWith(".c") || name.endsWith(".cpp") || name.endsWith(".h")) {
setIcon(Resources.cplusplusIcon);
} else if (name.endsWith(".apk") || name.endsWith(".dex")) {
setIcon(Resources.androidIcon);
} else if (name.endsWith(".png") || name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".bmp") || name.endsWith(".gif")) {
setIcon(Resources.imageIcon);
} else if (name.endsWith(".class")) {
setIcon(Resources.classIcon);
} else if (name.endsWith(".java")) {
setIcon(Resources.javaIcon);
} else if (name.endsWith(".txt") || name.endsWith(".md")) {
setIcon(Resources.textIcon);
} else if (name.equals("decoded resources")) {
setIcon(Resources.decodedIcon);
} else if (name.endsWith(".properties") || name.endsWith(".xml") || name.endsWith(".mf") || name.endsWith(".config") || name.endsWith(".cfg")) {
setIcon(Resources.configIcon);
} else if (node.getChildCount() <= 0) { //random file
setIcon(Resources.fileIcon);
} else { //folder
ArrayList<TreeNode> nodes = new ArrayList<TreeNode>();
ArrayList<TreeNode> totalNodes = new ArrayList<TreeNode>();
nodes.add(node);
totalNodes.add(node);
boolean isJava = false;
boolean finished = false;
while (!finished) { //may cause a clusterfuck with huge files
if (nodes.isEmpty())
finished = true;
else {
TreeNode treeNode = nodes.get(0);
nodes.remove(treeNode);
int children = treeNode.getChildCount();
if (children >= 1)
for (int i = 0; i < children; i++) {
TreeNode child = treeNode.getChildAt(i);
if (!totalNodes.contains(child)) {
nodes.add(child);
totalNodes.add(child);
}
if (child.toString().endsWith(".class"))
isJava = true;
}
if (isJava)
nodes.clear();
}
}
if (isJava)
setIcon(Resources.packagesIcon);
else {
setIcon(Resources.folderIcon);
}
}
}
return ret;
}
}
}