/** * */ package de.rub.syssec.saaf.gui.editor; import java.awt.Color; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.Set; import java.util.Vector; import java.util.WeakHashMap; import javax.swing.ImageIcon; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultStyledDocument; import javax.swing.text.Element; import javax.swing.text.StyleContext; import org.apache.commons.io.FileUtils; import de.rub.syssec.saaf.gui.MainWindow; import de.rub.syssec.saaf.model.application.ClassInterface; import de.rub.syssec.saaf.model.application.CodeLineInterface; /** * @author Tilman Bender <tilman.bender@rub.de> * */ public class EditorView extends JPanel implements PropertyChangeListener { private static final long serialVersionUID = 8404271707246217439L; private final Vector<Vector<String>> history; private final class LineNumberUpdater implements DocumentListener { public String getText() { return getNumberedLine(); } @Override public void changedUpdate(DocumentEvent de) { lines.setText(getText()); } @Override public void insertUpdate(DocumentEvent de) { lines.setText(getText()); } @Override public void removeUpdate(DocumentEvent de) { lines.setText(getText()); } } private final class InternalKeyListener implements KeyListener { @Override public void keyPressed(KeyEvent keyEvent) { /* * Navigation works as follows: ESC: If you're in the middle of a * smali b/c you clicked a link, you will jump back. * * Backspace: Deletes the history and you cannot return anymore with * ESC. * * TODO: This can greatly be improved :) */ if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) { if (history.size() >= 2) { history.removeElementAt(history.size() - 1); fileTree.searchNode(history.get(history.size() - 1).get(0), history.get(0).get(1)); } } else if (keyEvent.getKeyCode() == KeyEvent.VK_BACK_SPACE) { history.clear(); } } @Override public void keyReleased(KeyEvent keyEvent) { // nothing } @Override public void keyTyped(KeyEvent keyEvent) { // nothing } } private final static StyleContext STYLE_CONTEXT = new StyleContext(); /** A cache for already parsed documents */ private final static WeakHashMap<String, DefaultStyledDocument> DOCUMENT_MAP = new WeakHashMap<String, DefaultStyledDocument>(); private JScrollPane editorScrollPane; private EditorTextPane editor; private JTextArea lines; private LinkEditorKit linkEditorKit; private final FileTree fileTree; private EditorModel model; private ImageIcon permissionIcon; public EditorView(EditorModel model, FileTree fileTree) { super(); this.model = model; this.fileTree = fileTree; this.history = fileTree.getHistory(); this.linkEditorKit = fileTree.getLinkEditorKit(); URL imageURL = getClass().getResource("/images/permission.png"); permissionIcon = new ImageIcon(imageURL); // setBackground(Color.CYAN); setLayout(new GridBagLayout()); this.lines = new JTextArea("1"); this.lines.setBackground(Color.LIGHT_GRAY); this.lines.setEditable(false); this.editor = new EditorTextPane(model); this.editor.setToolTipText("enable"); this.lines.setMargin(editor.getMargin()); File f = model.getCurrentFile(); if (f != null) { DefaultStyledDocument doc = loadDocument(f); this.editor.setDocument(doc); this.lines.setText(getNumberedLine()); this.editor.setCaretPosition(0); } this.editor.setFont(lines.getFont()); this.editor.setEditable(false); // /Key Listener this.editor.addKeyListener(new InternalKeyListener()); this.editor.getDocument().addDocumentListener(new LineNumberUpdater()); this.editorScrollPane = new JScrollPane(editor); this.editorScrollPane.setRowHeaderView(lines); this.editorScrollPane .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); this.editorScrollPane .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); Dimension minimumSize = new Dimension(300, 300); this.editorScrollPane.setMinimumSize(minimumSize); GridBagConstraints editorConstraints = new GridBagConstraints(); editorConstraints.anchor = GridBagConstraints.NORTHWEST; editorConstraints.fill = GridBagConstraints.BOTH; editorConstraints.gridx = 0; editorConstraints.gridy = 0; editorConstraints.gridwidth=1; editorConstraints.gridheight=1; editorConstraints.weightx = 0.75; editorConstraints.weighty = 1.0; this.add(editorScrollPane, editorConstraints); } private int determinePosition(String[] content, int oldCursor, int line) { // visible lines on a single page int numVisibleLines = (int) Math.floor(editor.getVisibleRect() .getHeight() / editor.getFontMetrics(editor.getFont()).getHeight()); int numCharsOnFirstPage = 0; for (int i = 0; i < (numVisibleLines); i++) { // length of line numCharsOnFirstPage = numCharsOnFirstPage + content[i].length(); // for the new line numCharsOnFirstPage = numCharsOnFirstPage + 1;// change to // lineseperator // size } numCharsOnFirstPage++; int c1 = 0; int c2 = 0; int n1 = line - 1; c1 = byteCount(content, n1); n1 = line - 1 + numVisibleLines - 1 - 1; c2 = byteCount(content, n1); if (c1 >= oldCursor) return c2 + 1; else return c1 + 1; } /** * return the # of bytes from the begining of the array until line number * line * * @param content * @param line * @return */ private int byteCount(String[] content, int line) { int c1 = 0; if (line >= content.length) line = content.length - 2; for (int i = 0; i < line; i++) { // line length c1 = c1 + content[i].length(); // new line c1 = c1 + 1; } c1++; return c1; } /** * Get a String with line numbers. * * @return */ private String getNumberedLine() { int caretPosition = editor.getDocument().getLength(); Element root = editor.getDocument().getDefaultRootElement(); StringBuilder sb = new StringBuilder(); String lineSep = System.getProperty("line.separator"); sb.append(1); sb.append(lineSep); for (int i = 2; i < root.getElementIndex(caretPosition) + 2; i++) { sb.append(i); sb.append(lineSep); } return sb.toString(); } /** * Constructs a styled document for a given file. Only smali files are * styled. * * @param file * the file to read and style * @return the styled document * @throws IOException * @throws BadLocationException */ private DefaultStyledDocument loadDocument(File file) { // Already cached? DefaultStyledDocument doc = DOCUMENT_MAP.get(file.getAbsolutePath()); if (doc == null) { doc = new DefaultStyledDocument(STYLE_CONTEXT); } if (file.getName().endsWith(".smali")) { ClassInterface smali = this.model.getCurrentClass(); StringBuilder builder = new StringBuilder(); String separator = System.getProperty("line.separator"); for (CodeLineInterface cl : smali.getAllCodeLines()) { builder.append(new String(cl.getLine())); builder.append(separator); } try { doc.insertString(0, builder.toString(), null); SmaliTextStyler ts = new SmaliTextStyler(); ts.highlightStrings(this.model.getCurrentApplication(), doc, builder.toString()); } catch (BadLocationException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { /* * TODO: Some files, such as images, cannot properly be displayed. * Maybe open an external program or display them in a hex-editor? */ String fileAsString; try { fileAsString = FileUtils.readFileToString(file); doc.insertString(0, fileAsString, null); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (BadLocationException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return doc; } public void goToLine(int lineNr) throws BadLocationException { String[] content = editor.getDocument() .getText(0, editor.getDocument().getLength()).split("\n"); int oldCursor = editor.getCaretPosition(); editor.setCaretPosition(determinePosition(content, oldCursor, lineNr)); editorScrollPane.repaint(); } @Override public void propertyChange(final PropertyChangeEvent evt) { if ("currentFile".equals(evt.getPropertyName())) { if (evt.getNewValue() != null) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { File theFile = (File) evt.getNewValue(); if(theFile.getName().endsWith("Manifest.xml")) { if(model.getCurrentApplication().getManifest()==null) { MainWindow.showInfoDialog("You may notice that this manifest is not wellformed\n" + "You can find a wellformed version under\n"+ model.getCurrentApplication().getManifest().getTidiedPath(), "Malformed Manifest"); } } if(!theFile.getName().endsWith(".png")){ DefaultStyledDocument doc = loadDocument(theFile); Set <CodeLineInterface> keys = model.getCurrentApplication().getMatchedCalls().keySet(); editor.setDocument(doc); //split the document content into lines, and add indicator icon, if the current line contains an apicall String[] content = null; for(CodeLineInterface cl: keys){ if(theFile.getAbsolutePath().contains(cl.getSmaliClass().getFullClassName(false))){ try { content = doc .getText(0, doc.getLength()) .split("\n"); } catch (BadLocationException e) { e.printStackTrace(); } int offset = byteCount(content, cl.getLineNr()-1)-1; editor.setCaretPosition(offset); editor.insertIcon(permissionIcon); } } // editor.setCaretPosition(0); String newnumbers = getNumberedLine(); lines.setText(newnumbers); editorScrollPane.getVerticalScrollBar().setValue(0); editorScrollPane.revalidate(); editorScrollPane.repaint(); // lines.setText(getNumberedLine()); // editor.setCaretPosition(0); // repaint(); } } }); } else { // TODO unset the document } }else if("currentLine".equals(evt.getPropertyName())){ SwingUtilities.invokeLater(new Runnable() { @Override public void run() { int lineNr = (Integer)evt.getNewValue(); try { goToLine(lineNr); } catch (BadLocationException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); } } }