001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.gui; 021 022import java.awt.BorderLayout; 023import java.awt.Component; 024import java.awt.GridLayout; 025import java.awt.event.ActionEvent; 026import java.awt.event.KeyEvent; 027import java.io.File; 028import java.io.IOException; 029import java.util.ArrayList; 030import java.util.List; 031import java.util.TooManyListenersException; 032 033import javax.swing.AbstractAction; 034import javax.swing.Action; 035import javax.swing.JButton; 036import javax.swing.JFileChooser; 037import javax.swing.JOptionPane; 038import javax.swing.JPanel; 039import javax.swing.JScrollPane; 040import javax.swing.JTextArea; 041import javax.swing.SwingUtilities; 042import javax.swing.filechooser.FileFilter; 043 044import antlr.ANTLRException; 045 046import com.puppycrawl.tools.checkstyle.TreeWalker; 047import com.puppycrawl.tools.checkstyle.api.DetailAST; 048import com.puppycrawl.tools.checkstyle.api.FileContents; 049import com.puppycrawl.tools.checkstyle.api.FileText; 050 051/** 052 * Displays information about a parse tree. 053 * The user can change the file that is parsed and displayed 054 * through a JFileChooser. 055 * 056 * @author Lars Kühne 057 */ 058public class ParseTreeInfoPanel extends JPanel { 059 /** For Serialisation that will never happen. */ 060 private static final long serialVersionUID = -4243405131202059043L; 061 062 /** Parse tree model. */ 063 private final transient ParseTreeModel parseTreeModel; 064 /** JTextArea component. */ 065 private final JTextArea textArea; 066 /** Last directory. */ 067 private File lastDirectory; 068 /** Current file. */ 069 private File currentFile; 070 /** Reload action. */ 071 private final ReloadAction reloadAction; 072 /** Lines to position map. */ 073 private final List<Integer> linesToPosition = new ArrayList<>(); 074 075 /** 076 * Create a new ParseTreeInfoPanel instance. 077 */ 078 public ParseTreeInfoPanel() { 079 setLayout(new BorderLayout()); 080 081 parseTreeModel = new ParseTreeModel(null); 082 final JTreeTable treeTable = new JTreeTable(parseTreeModel); 083 final JScrollPane sp = new JScrollPane(treeTable); 084 add(sp, BorderLayout.PAGE_START); 085 086 final JButton fileSelectionButton = 087 new JButton(new FileSelectionAction()); 088 089 reloadAction = new ReloadAction(); 090 reloadAction.setEnabled(false); 091 final JButton reloadButton = new JButton(reloadAction); 092 093 textArea = new JTextArea(20, 15); 094 textArea.setEditable(false); 095 treeTable.setEditor(textArea); 096 treeTable.setLinePositionMap(linesToPosition); 097 098 final JScrollPane sp2 = new JScrollPane(textArea); 099 add(sp2, BorderLayout.CENTER); 100 101 final JPanel p = new JPanel(new GridLayout(1, 2)); 102 add(p, BorderLayout.PAGE_END); 103 p.add(fileSelectionButton); 104 p.add(reloadButton); 105 106 try { 107 new FileDrop(sp, new FileDropListener(sp)); 108 } 109 catch (final TooManyListenersException ignored) { 110 showErrorDialog(null, "Cannot initialize Drag and Drop support"); 111 } 112 113 } 114 115 /** 116 * Opens the input parse tree ast. 117 * @param parseTree DetailAST tree. 118 */ 119 public void openAst(DetailAST parseTree) { 120 parseTreeModel.setParseTree(parseTree); 121 reloadAction.setEnabled(true); 122 123 // clear for each new file 124 clearLinesToPosition(); 125 // starts line counting at 1 126 addLineToPosition(0); 127 // insert the contents of the file to the text area 128 129 // clean the text area before inserting the lines of the new file 130 if (!textArea.getText().isEmpty()) { 131 textArea.replaceRange("", 0, textArea.getText().length()); 132 } 133 134 // move back to the top of the file 135 textArea.moveCaretPosition(0); 136 } 137 138 /** 139 * Opens file and loads it into text area. 140 * @param file File to open. 141 * @param parent Component for displaying errors if file can't be open. 142 */ 143 public void openFile(File file, final Component parent) { 144 if (file != null) { 145 try { 146 Main.getFrame().setTitle("Checkstyle : " + file.getName()); 147 final FileText text = new FileText(file.getAbsoluteFile(), 148 getEncoding()); 149 final DetailAST parseTree = parseFile(text); 150 parseTreeModel.setParseTree(parseTree); 151 currentFile = file; 152 lastDirectory = file.getParentFile(); 153 reloadAction.setEnabled(true); 154 155 final String[] sourceLines = text.toLinesArray(); 156 157 // clear for each new file 158 clearLinesToPosition(); 159 // starts line counting at 1 160 addLineToPosition(0); 161 162 //clean the text area before inserting the lines of the new file 163 if (!textArea.getText().isEmpty()) { 164 textArea.replaceRange("", 0, textArea.getText() 165 .length()); 166 } 167 168 // insert the contents of the file to the text area 169 for (final String element : sourceLines) { 170 addLineToPosition(textArea.getText().length()); 171 textArea.append(element + System.lineSeparator()); 172 } 173 174 // move back to the top of the file 175 textArea.moveCaretPosition(0); 176 } 177 catch (final IOException | ANTLRException ex) { 178 showErrorDialog( 179 parent, 180 "Could not parse" + file + ": " + ex.getMessage()); 181 } 182 } 183 } 184 185 /** 186 * Parses a file and returns the parse tree. 187 * @param text the file to parse 188 * @return the root node of the parse tree 189 * @throws ANTLRException if the file is not a Java source 190 */ 191 private static DetailAST parseFile(FileText text) 192 throws ANTLRException { 193 final FileContents contents = new FileContents(text); 194 return TreeWalker.parse(contents); 195 } 196 197 /** 198 * Returns the configured file encoding. 199 * This can be set using the {@code file.encoding} system property. 200 * It defaults to UTF-8. 201 * @return the configured file encoding 202 */ 203 private static String getEncoding() { 204 return System.getProperty("file.encoding", "UTF-8"); 205 } 206 207 /** 208 * Opens dialog with error. 209 * @param parent Component for displaying errors. 210 * @param msg Error message to display. 211 */ 212 private static void showErrorDialog(final Component parent, final String msg) { 213 final Runnable showError = new FrameShower(parent, msg); 214 SwingUtilities.invokeLater(showError); 215 } 216 217 /** 218 * Adds a new value into lines to position map. 219 * @param value Value to be added into position map. 220 */ 221 private void addLineToPosition(int value) { 222 linesToPosition.add(value); 223 } 224 225 /** Clears lines to position map. */ 226 private void clearLinesToPosition() { 227 linesToPosition.clear(); 228 } 229 230 /** 231 * Http://findbugs.sourceforge.net/bugDescriptions.html#SW_SWING_METHODS_INVOKED_IN_SWING_THREAD 232 */ 233 private static class FrameShower implements Runnable { 234 /** 235 * Frame. 236 */ 237 private final Component parent; 238 239 /** 240 * Frame. 241 */ 242 private final String msg; 243 244 /** 245 * @param parent Frame's component. 246 * @param msg Message to show. 247 */ 248 FrameShower(Component parent, final String msg) { 249 this.parent = parent; 250 this.msg = msg; 251 } 252 253 /** 254 * Display a frame. 255 */ 256 @Override 257 public void run() { 258 JOptionPane.showMessageDialog(parent, msg); 259 } 260 } 261 262 /** 263 * Filter for Java files. 264 */ 265 private static class JavaFileFilter extends FileFilter { 266 @Override 267 public boolean accept(File file) { 268 if (file == null) { 269 return false; 270 } 271 return file.isDirectory() || file.getName().endsWith(".java"); 272 } 273 274 @Override 275 public String getDescription() { 276 return "Java Source Code"; 277 } 278 } 279 280 /** 281 * Handler for file selection action events. 282 */ 283 private class FileSelectionAction extends AbstractAction { 284 /** 285 * Serial ID. 286 */ 287 private static final long serialVersionUID = -1926935338069418119L; 288 289 /** Default constructor to setup current action. */ 290 FileSelectionAction() { 291 super("Select Java File"); 292 putValue(Action.MNEMONIC_KEY, KeyEvent.VK_S); 293 } 294 295 @Override 296 public void actionPerformed(ActionEvent e) { 297 final JFileChooser fc = new JFileChooser(lastDirectory); 298 final FileFilter filter = new JavaFileFilter(); 299 fc.setFileFilter(filter); 300 final Component parent = 301 SwingUtilities.getRoot(ParseTreeInfoPanel.this); 302 fc.showDialog(parent, "Open"); 303 final File file = fc.getSelectedFile(); 304 openFile(file, parent); 305 306 } 307 } 308 309 /** 310 * Handler for reload action events. 311 */ 312 private class ReloadAction extends AbstractAction { 313 /** 314 * Serial UID. 315 */ 316 private static final long serialVersionUID = -1021880396046355863L; 317 318 /** Default constructor to setup current action. */ 319 ReloadAction() { 320 super("Reload Java File"); 321 putValue(Action.MNEMONIC_KEY, KeyEvent.VK_R); 322 } 323 324 @Override 325 public void actionPerformed(ActionEvent e) { 326 final Component parent = 327 SwingUtilities.getRoot(ParseTreeInfoPanel.this); 328 openFile(currentFile, parent); 329 } 330 } 331 332 /** 333 * Listener and handler for file dropped events. 334 */ 335 private class FileDropListener implements Listener { 336 /** Scroll pane. */ 337 private final JScrollPane scrollPane; 338 339 /** 340 * @param scrollPane Scroll pane. 341 */ 342 FileDropListener(JScrollPane scrollPane) { 343 this.scrollPane = scrollPane; 344 } 345 346 @Override 347 public void filesDropped(File... files) { 348 if (files != null && files.length > 0) { 349 final File file = files[0]; 350 openFile(file, scrollPane); 351 } 352 } 353 } 354}