de.rub.syssec.saaf.gui.editor.FileTree.java Source code

Java tutorial

Introduction

Here is the source code for de.rub.syssec.saaf.gui.editor.FileTree.java

Source

/*
 * Copyright (c) Ian F. Darwin, http://www.darwinsys.com/, 1996-2002.
 * All rights reserved. Software written by Ian F. Darwin and others.
 * $Id: LICENSE,v 1.8 2004/02/09 03:33:38 ian Exp $
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Java, the Duke mascot, and all variants of Sun's Java "steaming coffee
 * cup" logo are trademarks of Sun Microsystems. Sun's, and James Gosling's,
 * pioneering role in inventing and promulgating (and standardizing) the Java
 * language and environment is gratefully acknowledged.
 * 
 * The pioneering role of Dennis Ritchie and Bjarne Stroustrup, of AT&T, for
 * inventing predecessor languages C and C++ is also gratefully acknowledged.
 * 
 * Potential source:
 * - http://www.java2s.com/Code/Java/Swing-JFC/DisplayafilesysteminaJTreeview.htm
 */
package de.rub.syssec.saaf.gui.editor;

import java.awt.Color;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.Vector;

import javax.swing.JInternalFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.NotFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.log4j.Logger;

import de.rub.syssec.saaf.gui.MainWindow;
import de.rub.syssec.saaf.gui.OpenAnalysis;
import de.rub.syssec.saaf.gui.ViewerStarter;
import de.rub.syssec.saaf.misc.config.ConfigKeys;
import de.rub.syssec.saaf.model.application.ApplicationInterface;
import de.rub.syssec.saaf.model.application.ClassInterface;
import de.rub.syssec.saaf.model.application.MethodInterface;

/**
 * Display a file system in a JTree view (extended with lots of SAAF stuff).
 * 
 * <p>
 * Window to browse in the Android-App, viewing smali and java code. Called by
 * {@link OpenApp#showOrOpenNewFrame}.
 * </p>
 * 
 * This class is based on FileTree.java from Ian Darwin.
 * 
 * - Martin Ussath / 12.2011: Initial version. - Johannes Hoffmann / 01.2012:
 * Refactored and some improvements.
 * 
 * 
 * @version $Id: FileTree.java,v 1.9 2004/02/23 03:39:22 ian Exp $
 * @author Ian Darwin
 * @see OpenApp
 * 
 */
public class FileTree extends JInternalFrame implements PropertyChangeListener {

    private class FileCellRenderer extends DefaultTreeCellRenderer {

        private static final long serialVersionUID = 462485888657862971L;

        @Override
        public Component getTreeCellRendererComponent(JTree arg0, Object arg1, boolean selected, boolean expanded,
                boolean leaf, int arg5, boolean arg6) {
            Component c = super.getTreeCellRendererComponent(arg0, arg1, selected, expanded, leaf, arg5, arg6);
            if (leaf) {
                // TODO
                // obtain the class for the file
                FileNode f = (FileNode) ((DefaultMutableTreeNode) arg1).getUserObject();
                ClassInterface smaliClass = model.getCurrentApplication().getSmaliClass(f.getFile());
                // check if its obfuscated
                if (smaliClass != null && smaliClass.isObfuscated()) {
                    // set foreground to red if it is
                    c.setForeground(Color.RED);
                } else {
                    // otherwise set foreground to black
                    c.setForeground(Color.BLACK);
                }
            }
            return c;
        }

    }

    private static final String MENU_ACTION_CFG = "Generate CFGs";
    private final Vector<Vector<String>> history;
    private final LinkEditorKit linkEditorKit;

    private static final long serialVersionUID = -560054884407736589L;

    private File directory;
    private File smali;
    public FileNode lastClick;
    private JTree fileTree;

    private final EditorView editor;
    private OpenAnalysis openAna;
    private final Logger logger = Logger.getLogger(FileTree.class);

    private final ViewerStarter viewer = new ViewerStarter(ConfigKeys.VIEWER_IMAGES);
    private EditorModel model;
    private OutlineView outlineTree;

    public EditorModel getModel() {
        return model;
    }

    public void setModel(EditorModel model) {
        this.model = model;
    }

    private final class SelectionListener implements TreeSelectionListener {

        public void valueChanged(TreeSelectionEvent e) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.getPath().getLastPathComponent();

            // if (node == null) { // at startup this will show the manifest
            // try {
            // editor.open(app.getManifestFile());
            // } catch (Exception e1) {
            // logger.warn("Problem during tree construction", e1);
            // }
            // return;
            // }

            Object nodeInfo = node.getUserObject();
            if (node.isLeaf()) {
                FileNode nodeObject;
                try {
                    nodeObject = (FileNode) nodeInfo;
                } catch (ClassCastException cce) {
                    // No file in this node (empty directory)
                    return;
                }

                linkEditorKit.setLastClickedFile(nodeObject.getFile());

                try {
                    // Load text from file
                    model.setCurrentFile(nodeObject.getFile());
                } catch (Exception e1) {
                    logger.warn("Problem during tree construction: " + e1.getMessage());
                }
            }
        }
    }

    private class FileNode {
        private String fileName;
        private File file;

        public FileNode(String fileName_p, File file_p) {
            fileName = fileName_p;
            file = file_p;
        }

        // this is shown in the tree
        public String toString() {
            return fileName;
        }

        public File getFile() {
            return file;
        }
    }

    public FileTree(final ApplicationInterface app, File dir, OpenAnalysis open) {
        super();
        this.openAna = open;
        history = new Vector<Vector<String>>();
        linkEditorKit = new LinkEditorKit(history, app.getUnpackedDataDir(), this);

        directory = dir;
        //userful to debug layout issues
        //setBackground(Color.MAGENTA);
        setLayout(new GridBagLayout());

        this.model = new EditorModel(app);

        // we want to be notified if the file changes so we can reflect that in
        // the tree
        model.addPropertyChangeListener(this);

        // the tree that lists the files (top left)
        JTree tree = new JTree(addNodes(null, dir));
        tree.addMouseListener(ma);
        tree.addTreeSelectionListener(new SelectionListener());
        tree.setCellRenderer(new FileCellRenderer());
        fileTree = tree;

        GridBagConstraints treeConstraints = new GridBagConstraints();
        treeConstraints.fill = GridBagConstraints.BOTH;
        treeConstraints.gridheight = 1;
        treeConstraints.gridwidth = 1;
        treeConstraints.gridx = 0;
        treeConstraints.gridy = 0;
        treeConstraints.weightx = 0.20;
        treeConstraints.weighty = 1.0;
        treeConstraints.anchor = GridBagConstraints.FIRST_LINE_START;
        this.add(new JScrollPane(tree), treeConstraints);

        // the list of components (bottom left)
        EntryPointsView entrypoints = new EntryPointsView(model);
        model.addPropertyChangeListener(entrypoints);

        JScrollPane entryPointsScroller = new JScrollPane(entrypoints);
        GridBagConstraints entrypointConstraints = new GridBagConstraints();
        entrypointConstraints.anchor = GridBagConstraints.FIRST_LINE_START;
        entrypointConstraints.fill = GridBagConstraints.BOTH;
        entrypointConstraints.gridheight = 1;
        entrypointConstraints.gridwidth = 1;
        entrypointConstraints.gridx = 0;
        entrypointConstraints.gridy = 1;
        entrypointConstraints.weightx = 0.15;
        entrypointConstraints.weighty = 1.0;

        this.add(entryPointsScroller, entrypointConstraints);

        // the editor (contains the textview and the list of methods)
        this.editor = new EditorView(model, this);
        this.model.addPropertyChangeListener(this.editor);

        GridBagConstraints editorConstraints = new GridBagConstraints();
        editorConstraints.anchor = GridBagConstraints.NORTHWEST;
        editorConstraints.fill = GridBagConstraints.BOTH;
        editorConstraints.gridheight = 2;
        editorConstraints.gridwidth = 1;
        editorConstraints.gridx = 1;
        editorConstraints.gridy = 0;
        editorConstraints.weightx = 0.70;
        editorConstraints.weighty = 1.0;
        this.add(editor, editorConstraints);

        this.outlineTree = new OutlineView(this.model);
        model.addPropertyChangeListener("currentClass", outlineTree);

        GridBagConstraints outlineConstraints = new GridBagConstraints();
        outlineConstraints.anchor = GridBagConstraints.NORTHWEST;
        outlineConstraints.fill = GridBagConstraints.BOTH;
        outlineConstraints.gridwidth = 1;
        outlineConstraints.gridheight = 2;
        outlineConstraints.gridx = 2;
        outlineConstraints.gridy = 0;
        outlineConstraints.weightx = 0.15;
        outlineConstraints.weighty = 1.0;
        this.add(outlineTree, outlineConstraints);

        String shortpath = model.getCurrentFile().getAbsolutePath()
                .replace(app.getApplicationDirectory().getAbsolutePath(), "");
        this.setTitle("Editor - " + shortpath);
        searchNode(shortpath, null);
    }

    public DefaultMutableTreeNode searchNode(String nodeStr, String lineNr) {

        DefaultMutableTreeNode node = null;
        @SuppressWarnings("unchecked")
        Enumeration<DefaultMutableTreeNode> e = ((DefaultMutableTreeNode) fileTree.getModel().getRoot())
                .breadthFirstEnumeration();

        while (e.hasMoreElements()) {

            node = (DefaultMutableTreeNode) e.nextElement();
            String filepath = "";

            for (int i = 0; i < node.getPath().length; i++) {
                filepath = filepath + File.separator + node.getPath()[i];
            }
            if (filepath.startsWith(nodeStr)) {
                TreeNode[] nodes = node.getPath();
                TreePath path = new TreePath(nodes);
                fileTree.scrollPathToVisible(path);
                fileTree.setSelectionPath(path);
                if (lineNr != null) {
                    try {
                        editor.goToLine(Integer.parseInt(lineNr));
                    } catch (Exception e1) {
                        logger.warn("Problem during tree construction", e1);
                    }
                }

                return node;
            }

        }
        return null;
    }

    /** Add nodes from under "dir" into curTop. Highly recursive. */
    DefaultMutableTreeNode addNodes(DefaultMutableTreeNode curTop, File dir) {

        // String curPath = dir.getPath();
        String curPath = dir.getName();
        DefaultMutableTreeNode curDir = new DefaultMutableTreeNode(curPath);

        if (curTop != null) {
            // should only be null at root
            curTop.add(curDir);
        }

        // ignore some files
        IOFileFilter fileFilter = new NotFileFilter(
                new SuffixFileFilter(new String[] { ".class", ".java", ".DS_Store", ".yml" }));

        LinkedList<File> files = new LinkedList<File>(FileUtils.listFiles(dir, fileFilter, null));
        // FIXME: How the hell can directories be listed?!
        // LinkedList<File> directories = new
        // LinkedList<File>(FileUtils.listFiles(dir, FalseFileFilter.INSTANCE,
        // DirectoryFileFilter.INSTANCE));
        LinkedList<File> directories = new LinkedList<File>(
                Arrays.asList(dir.listFiles((FileFilter) DirectoryFileFilter.DIRECTORY)));

        Collections.sort(files);
        Collections.sort(directories);

        // Recursively add directories
        for (File directory : directories) {
            addNodes(curDir, directory);
        }
        // Add files
        for (File file : files) {
            if (file.getAbsolutePath().endsWith(".png") && file.getAbsolutePath().contains("/bytecode/smali")) {
                /*
                 * Skipping generated PNG CFGs in bytecode folder. Other PNG
                 * files will be shown in the tree, eg, /res/drawable-hdpi This
                 * might not be necessary b/c no PNGs are currently not created
                 * at startup.
                 */
                continue;
            }
            curDir.add(new DefaultMutableTreeNode(new FileNode(file.getName(), file)));
        }
        return curDir;
    }

    // public Dimension getMinimumSize() {
    // return new Dimension(1000, 400);
    // }
    //
    // public Dimension getPreferredSize() {
    // return new Dimension(1000, 600);
    // }

    MouseAdapter ma = new MouseAdapter() {
        private void openExternalFile(MouseEvent e) {
            int x = e.getX();
            int y = e.getY();
            JTree tree = (JTree) e.getSource();
            TreePath path = tree.getPathForLocation(x, y);
            if (path == null)
                return;

            //tree.setSelectionPath(path);

            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
            Object nodeInfo = node.getUserObject();
            FileNode node_object = (FileNode) nodeInfo;
            //lastClick = node_object;

            File png = node_object.getFile();

            if (png.getAbsolutePath().endsWith(".png")) {
                try {
                    viewer.showFile(png);
                } catch (IOException e1) {
                    e1.printStackTrace();
                }

            }
        }

        private void myPopupEvent(MouseEvent e) {
            int x = e.getX();
            int y = e.getY();
            JTree tree = (JTree) e.getSource();
            TreePath path = tree.getPathForLocation(x, y);
            if (path == null)
                return;

            tree.setSelectionPath(path);

            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
            Object nodeInfo = node.getUserObject();
            FileNode node_object = (FileNode) nodeInfo;
            lastClick = node_object;

            smali = node_object.getFile();
            File myDir = new File(smali.getParent());
            File[] cfgFiles = myDir.listFiles();

            if (smali.getAbsolutePath().endsWith(".smali")) {

                JPopupMenu popup = new JPopupMenu();

                JMenuItem item = new JMenuItem(MENU_ACTION_CFG);
                item.addActionListener(menuListener);
                popup.add(item);

                popup.addSeparator();

                // smali File
                item = new JMenuItem(smali.getName());
                item.addActionListener(menuListener);
                popup.add(item);

                // Java File (need decompilation first)
                String javaFileName = smali.getName().substring(0, smali.getName().length() - 6) + ".java";
                File javaFile = new File(smali.getParentFile(), javaFileName);
                item = new JMenuItem(javaFileName);
                item.addActionListener(menuListener);
                popup.add(item);
                if (!javaFile.exists()) {
                    item.setEnabled(false);
                }
                popup.addSeparator();

                for (int t = 0; t < cfgFiles.length; t++) {
                    if (cfgFiles[t].getName().endsWith(".png")
                            && cfgFiles[t].getName().startsWith(node_object.getFile().getName().substring(0,
                                    node_object.getFile().getName().length() - 6) + "_")) {
                        item = new JMenuItem(cfgFiles[t].getName());
                        item.addActionListener(menuListener);
                        popup.add(item);
                    }
                }

                popup.addSeparator();
                JMenu cfgs = new JMenu("cfgs");
                ClassInterface smaliClass = model.getCurrentApplication().getSmaliClass(smali);
                for (MethodInterface method : smaliClass.getMethods()) {
                    String parameters = "(" + method.getParameterString() + ")";//TODO: maybe do this in method.getParameterString, or at least the "(" and ")"
                    StringBuilder realFileName = new StringBuilder();
                    realFileName.append(method.getName());
                    realFileName.append(parameters);
                    realFileName.append(method.getReturnValueString());
                    JMenuItem methodButton = new JMenuItem(realFileName.toString());
                    cfgs.add(methodButton);
                    methodButton.addActionListener(cfgListener);
                }

                popup.add(cfgs);

                popup.show(tree, x, y);
            }
        }

        ActionListener menuListener = new ActionListener() {
            public void actionPerformed(ActionEvent event) {

                if (event.getActionCommand().equals(MENU_ACTION_CFG)) {
                    if (openAna != null) {
                        try {
                            openAna.showOrOpenNewFrame(OpenAnalysis.AppFrame.CFGS);
                        } catch (Exception e) {
                            logger.error(e);
                        }
                    }

                }

                else if (event.getActionCommand().endsWith(".png")) {

                    File cfg = new File(
                            lastClick.getFile().getParent() + File.separator + event.getActionCommand());
                    cfg.deleteOnExit();

                    try {

                        viewer.showFile(cfg);

                    } catch (IOException e) {
                        logger.error(e);
                    }
                }

                else { // load the file and display it (normally .smali)
                    try {
                        File f = new File(
                                lastClick.getFile().getParent() + File.separator + event.getActionCommand());
                        model.setCurrentFile(f);
                    } catch (Exception e) {
                        logger.error(e);
                    }
                }
            }
        };

        public void mousePressed(MouseEvent e) {
            if (e.isPopupTrigger())
                myPopupEvent(e);
            else if (e.getButton() == MouseEvent.BUTTON1) {
                openExternalFile(e);
            }
        }

        public void mouseReleased(MouseEvent e) {
            if (e.isPopupTrigger())
                myPopupEvent(e);
        }

        ActionListener cfgListener = new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                ClassInterface smaliFile = model.getCurrentApplication().getSmaliClass(lastClick.getFile());

                for (MethodInterface method : smaliFile.getMethods()) {
                    String parameters = "(" + method.getParameterString() + ")";//TODO: maybe do this in method.getParameterString, or at least the "(" and ")"
                    StringBuilder cfgFile = new StringBuilder();
                    cfgFile.append(method.getName());
                    cfgFile.append(parameters);
                    cfgFile.append(method.getReturnValueString());
                    if (cfgFile.toString().equals(event.getActionCommand())) {
                        MethodViewer cfgViewer = new MethodViewer(method);//TODO: maybe rename to CFGViewer
                        MainWindow.getDesktopPane().add(cfgViewer);
                        try {
                            cfgViewer.setSelected(true);
                        } catch (java.beans.PropertyVetoException e) {
                        }
                        break;
                    }
                }
            }
        };

    };

    public Vector<Vector<String>> getHistory() {
        return this.history;
    }

    public LinkEditorKit getLinkEditorKit() {
        return this.linkEditorKit;
    }

    @Override
    public void propertyChange(PropertyChangeEvent arg0) {
        if ("currentFile".equals(arg0.getPropertyName())) {
            File f = (File) arg0.getNewValue();
            ApplicationInterface app = model.getCurrentApplication();
            String shortpath = f.getAbsolutePath().replace(app.getApplicationDirectory().getAbsolutePath(), "");

            // update the tree selection
            searchNode(shortpath, null);
            //update the editor so we see what we are editing
            this.setTitle("Editor - " + shortpath);
        }
    }

}