package eu.bibl.cfide.ui.tree;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
import eu.bibl.banalysis.asm.ClassNode;
import eu.bibl.banalysis.storage.classes.ClassContainer;
import eu.bibl.bio.jfile.out.CompleteJarDumper;
import eu.bibl.cfide.context.CFIDEContext;
import eu.bibl.cfide.engine.decompiler.PrefixedStringBuilder;
import eu.bibl.cfide.io.config.CFIDEConfig;
import eu.bibl.cfide.ui.editor.EditorTabbedPane;
import eu.bibl.cfide.ui.editor.EditorTextTab;
public class ClassViewerTree extends JTree implements TreeSelectionListener, MouseListener {
private static final long serialVersionUID = -1731401270496103799L;
private static final Icon JAR_ICON = new ImageIcon("res/jar.png");
protected CFIDEContext context;
protected PackageTreeNode root;
public ClassViewerTree(String jarName, CFIDEContext context) {
super(new DefaultPackageTreeNode(jarName));
this.context = context;
setRootVisible(true);
populateTree();
setComponentPopupMenu(createPopupMenu());
setCellRenderer(new DefaultPackageTreeNodeRenderer());
addTreeSelectionListener(this);
addMouseListener(this);
expandPath(new TreePath(root)); // automatically opens the root node.
}
protected JPopupMenu createPopupMenu() {
JPopupMenu menu = new JPopupMenu() {
private static final long serialVersionUID = 7505457795696357320L;
@Override
public void setVisible(boolean b) {
if (b) {
if (selectedNode instanceof ClassTreeNode) {
super.setVisible(true);// only show if a node is selected
TreePath path = new TreePath(selectedNode);
ClassViewerTree.this.expandPath(path);// want to highlight the jtree leaf but that wont work,
ClassViewerTree.this.setSelectionPath(path);// instead just to fire the open event to make sure the class is decompiled.
}
} else {
super.setVisible(false);
}
}
};
JMenuItem saveClassItem = new JMenuItem("Save Class");
saveClassItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (selectedNode instanceof ClassTreeNode) {
ClassTreeNode ctn = (ClassTreeNode) selectedNode;
try {
JFileChooser fileChooser = new JFileChooser();
FileNameExtensionFilter filter = new FileNameExtensionFilter("Jar Files", "jar");
fileChooser.setFileFilter(filter);
int returnValue = fileChooser.showSaveDialog(ClassViewerTree.this);
if (returnValue == JFileChooser.APPROVE_OPTION) {
final File file = fileChooser.getSelectedFile();
final ClassNode[] cns = context.compiler.compile(context.projectPanel.getText(ctn.getClassName()));
new Thread() {
@Override
public void run() {
ClassContainer cc = new ClassContainer(cns);
new CompleteJarDumper(cc).dump(file);
}
}.start();
}
} catch (Exception e1) {
JOptionPane.showMessageDialog(ClassViewerTree.this, e1.getMessage(), "Compiler error", JOptionPane.ERROR_MESSAGE);
e1.printStackTrace();
}
}
}
});
menu.add(saveClassItem);
return menu;
}
protected Map<String, PackageTreeNode> packages;
private void populateTree() {
packages = new HashMap<String, PackageTreeNode>();
root = (PackageTreeNode) getModel().getRoot();
Map<ClassTreeNode, PackageTreeNode> classesToAdd = new HashMap<ClassTreeNode, PackageTreeNode>();
Map<PackageTreeNode, PackageTreeNode> packagesToAdd = new HashMap<PackageTreeNode, PackageTreeNode>();
boolean listInnerClasses = false;
try {
listInnerClasses = context.config.getProperty(CFIDEConfig.TREE_LIST_INNER_CLASSES_KEY, false);
} catch (Exception e) {
/* ignored */
}
for (ClassNode cn : context.jarDownloader.getContents().getNodes().values()) {
if (!listInnerClasses && cn.name.contains("$"))
continue;// don't list inner classes, have them decompiled in the class they came from
String[] nameParts = cn.name.split("/");
PackageTreeNode lastNode = root;
StringBuilder packageDepthName = new StringBuilder();
for (int i = 0; i < (nameParts.length - 1); i++) { // loop through just package names
String packageName = nameParts[i];
packageDepthName.append(packageName);
packageDepthName.append("/");
if (packages.containsKey(packageDepthName.toString())) {// if the package is already mapped, just set as last visited.
lastNode = packages.get(packageDepthName.toString());
} else {// if it's not mapped, create it, save it and set it as last visited
PackageTreeNode packageTreeNode = new PackageTreeNode(packageName);
packages.put(packageDepthName.toString(), packageTreeNode);
packagesToAdd.put(packageTreeNode, lastNode); // add after to ensure alphabetical name ordering
lastNode = packageTreeNode;
}
}
ClassTreeNode classTreeNode = new ClassTreeNode(cn); // add class after for alphabetical name ordering
classesToAdd.put(classTreeNode, lastNode);
}
// order the packages alphabetically
List<PackageTreeNode> packageKeys = new ArrayList<PackageTreeNode>(packagesToAdd.keySet());
Collections.sort(packageKeys, new Comparator<PackageTreeNode>() {
@Override
public int compare(PackageTreeNode o1, PackageTreeNode o2) {
return o1.getPackageName().compareToIgnoreCase(o2.getPackageName());
}
});
for (PackageTreeNode ptn : packageKeys) {
PackageTreeNode ptn1 = packagesToAdd.get(ptn);
ptn1.add(ptn);
}
// order the classes alphabetically
List<ClassTreeNode> classKeys = new ArrayList<ClassTreeNode>(classesToAdd.keySet());
Collections.sort(classKeys, new Comparator<ClassTreeNode>() {
@Override
public int compare(ClassTreeNode o1, ClassTreeNode o2) {
return o1.getClassName().compareToIgnoreCase(o2.getClassName());
}
});
for (ClassTreeNode ctn : classKeys) { // add classes after so we have that package list, then class list effect
PackageTreeNode ptn = classesToAdd.get(ctn);
ptn.add(ctn);
}
}
@Override
public void valueChanged(TreeSelectionEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) getLastSelectedPathComponent();
if (node == null)
return;
if (node instanceof ClassTreeNode) {
decompile(((ClassTreeNode) node).getClassNode());
}
}
protected void decompile(ClassNode cn) {
// String simpleName = ClassTreeNode.getClassName(cn.name);
// ISSUE #1: https://github.com/TheBiblMan/CFIDE/issues/1
EditorTabbedPane etp = context.editorTabbedPane;
EditorTextTab textTab = etp.getTextTab(cn.name);
if (textTab != null) {
if (!textTab.isShowing()) {
etp.addTab(cn.name, textTab);
textTab.setupFinal();
}
etp.setSelectedComponent(textTab);
return;
}
textTab = etp.createTextTab(cn.name, context);
etp.setSelectedComponent(textTab);
PrefixedStringBuilder sb = context.decompiler.decompile(new PrefixedStringBuilder(), cn);
textTab.setText(sb.toString());
}
@Override
public void mouseClicked(MouseEvent e) {
}
protected DefaultMutableTreeNode selectedNode;
@Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
TreePath pathForLocation = getPathForLocation(e.getPoint().x, e.getPoint().y);
if (pathForLocation != null) {
selectedNode = (DefaultMutableTreeNode) pathForLocation.getLastPathComponent();
} else {
selectedNode = null;
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
class DefaultPackageTreeNodeRenderer extends DefaultTreeCellRenderer {
private static final long serialVersionUID = -7238675790138337723L;
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
if (value.equals(root)) // make sure the root node has the jar icon
setIcon(JAR_ICON);
return this;
}
}
}