Java tutorial
/* * 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); } } }