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; 031 032import javax.swing.AbstractAction; 033import javax.swing.Action; 034import javax.swing.JButton; 035import javax.swing.JFileChooser; 036import javax.swing.JOptionPane; 037import javax.swing.JPanel; 038import javax.swing.JScrollPane; 039import javax.swing.JTextArea; 040import javax.swing.SwingUtilities; 041import javax.swing.filechooser.FileFilter; 042 043import antlr.ANTLRException; 044 045import com.puppycrawl.tools.checkstyle.TreeWalker; 046import com.puppycrawl.tools.checkstyle.api.DetailAST; 047import com.puppycrawl.tools.checkstyle.api.FileContents; 048import com.puppycrawl.tools.checkstyle.api.FileText; 049 050/** 051 * Displays information about a parse tree. 052 * The user can change the file that is parsed and displayed 053 * through a JFileChooser. 054 * 055 * @author Lars Kühne 056 */ 057public class ParseTreeInfoPanel extends JPanel { 058 private static final long serialVersionUID = -4243405131202059043L; 059 060 /** Parse tree model. */ 061 private final transient ParseTreeModel parseTreeModel; 062 /** JTextArea component. */ 063 private final JTextArea textArea; 064 /** Last directory. */ 065 private File lastDirectory; 066 /** Current file. */ 067 private File currentFile; 068 /** Reload action. */ 069 private final ReloadAction reloadAction; 070 /** Lines to position map. */ 071 private final List<Integer> linesToPosition = new ArrayList<>(); 072 073 /** 074 * Create a new ParseTreeInfoPanel instance. 075 */ 076 public ParseTreeInfoPanel() { 077 setLayout(new BorderLayout()); 078 079 parseTreeModel = new ParseTreeModel(null); 080 final JTreeTable treeTable = new JTreeTable(parseTreeModel); 081 final JScrollPane scrollPane = new JScrollPane(treeTable); 082 add(scrollPane, BorderLayout.PAGE_START); 083 084 final JButton fileSelectionButton = 085 new JButton(new FileSelectionAction()); 086 087 reloadAction = new ReloadAction(); 088 reloadAction.setEnabled(false); 089 final JButton reloadButton = new JButton(reloadAction); 090 091 textArea = new JTextArea(20, 15); 092 textArea.setEditable(false); 093 treeTable.setEditor(textArea); 094 treeTable.setLinePositionMap(linesToPosition); 095 096 final JScrollPane sp2 = new JScrollPane(textArea); 097 add(sp2, BorderLayout.CENTER); 098 099 final JPanel pane = new JPanel(new GridLayout(1, 2)); 100 add(pane, BorderLayout.PAGE_END); 101 pane.add(fileSelectionButton); 102 pane.add(reloadButton); 103 104 } 105 106 /** 107 * Opens the input parse tree ast. 108 * @param parseTree DetailAST tree. 109 */ 110 public void openAst(DetailAST parseTree) { 111 parseTreeModel.setParseTree(parseTree); 112 reloadAction.setEnabled(true); 113 114 // clear for each new file 115 clearLinesToPosition(); 116 // starts line counting at 1 117 addLineToPosition(0); 118 // insert the contents of the file to the text area 119 120 // clean the text area before inserting the lines of the new file 121 if (!textArea.getText().isEmpty()) { 122 textArea.replaceRange("", 0, textArea.getText().length()); 123 } 124 125 // move back to the top of the file 126 textArea.moveCaretPosition(0); 127 } 128 129 /** 130 * Opens file and loads it into text area. 131 * @param file File to open. 132 * @param parent Component for displaying errors if file can't be open. 133 */ 134 public void openFile(File file, final Component parent) { 135 if (file != null) { 136 try { 137 Main.getFrame().setTitle("Checkstyle : " + file.getName()); 138 final FileText text = new FileText(file.getAbsoluteFile(), 139 getEncoding()); 140 final DetailAST parseTree = parseFile(text); 141 parseTreeModel.setParseTree(parseTree); 142 currentFile = file; 143 lastDirectory = file.getParentFile(); 144 reloadAction.setEnabled(true); 145 146 final String[] sourceLines = text.toLinesArray(); 147 148 // clear for each new file 149 clearLinesToPosition(); 150 // starts line counting at 1 151 addLineToPosition(0); 152 153 //clean the text area before inserting the lines of the new file 154 if (!textArea.getText().isEmpty()) { 155 textArea.replaceRange("", 0, textArea.getText() 156 .length()); 157 } 158 159 // insert the contents of the file to the text area 160 for (final String element : sourceLines) { 161 addLineToPosition(textArea.getText().length()); 162 textArea.append(element + System.lineSeparator()); 163 } 164 165 // move back to the top of the file 166 textArea.moveCaretPosition(0); 167 } 168 catch (final IOException | ANTLRException ex) { 169 showErrorDialog( 170 parent, 171 "Could not parse" + file + ": " + ex.getMessage()); 172 } 173 } 174 } 175 176 /** 177 * Parses a file and returns the parse tree. 178 * @param text the file to parse 179 * @return the root node of the parse tree 180 * @throws ANTLRException if the file is not a Java source 181 */ 182 private static DetailAST parseFile(FileText text) 183 throws ANTLRException { 184 final FileContents contents = new FileContents(text); 185 return TreeWalker.parse(contents); 186 } 187 188 /** 189 * Returns the configured file encoding. 190 * This can be set using the {@code file.encoding} system property. 191 * It defaults to UTF-8. 192 * @return the configured file encoding 193 */ 194 private static String getEncoding() { 195 return System.getProperty("file.encoding", "UTF-8"); 196 } 197 198 /** 199 * Opens dialog with error. 200 * @param parent Component for displaying errors. 201 * @param msg Error message to display. 202 */ 203 private static void showErrorDialog(final Component parent, final String msg) { 204 final Runnable showError = new FrameShower(parent, msg); 205 SwingUtilities.invokeLater(showError); 206 } 207 208 /** 209 * Adds a new value into lines to position map. 210 * @param value Value to be added into position map. 211 */ 212 private void addLineToPosition(int value) { 213 linesToPosition.add(value); 214 } 215 216 /** Clears lines to position map. */ 217 private void clearLinesToPosition() { 218 linesToPosition.clear(); 219 } 220 221 /** 222 * Http://findbugs.sourceforge.net/bugDescriptions.html#SW_SWING_METHODS_INVOKED_IN_SWING_THREAD 223 */ 224 private static class FrameShower implements Runnable { 225 /** 226 * Frame. 227 */ 228 private final Component parent; 229 230 /** 231 * Frame. 232 */ 233 private final String msg; 234 235 /** 236 * @param parent Frame's component. 237 * @param msg Message to show. 238 */ 239 FrameShower(Component parent, final String msg) { 240 this.parent = parent; 241 this.msg = msg; 242 } 243 244 /** 245 * Display a frame. 246 */ 247 @Override 248 public void run() { 249 JOptionPane.showMessageDialog(parent, msg); 250 } 251 } 252 253 /** 254 * Filter for Java files. 255 */ 256 private static class JavaFileFilter extends FileFilter { 257 @Override 258 public boolean accept(File file) { 259 if (file == null) { 260 return false; 261 } 262 return file.isDirectory() || file.getName().endsWith(".java"); 263 } 264 265 @Override 266 public String getDescription() { 267 return "Java Source Code"; 268 } 269 } 270 271 /** 272 * Handler for file selection action events. 273 */ 274 private class FileSelectionAction extends AbstractAction { 275 private static final long serialVersionUID = -1926935338069418119L; 276 277 /** Default constructor to setup current action. */ 278 FileSelectionAction() { 279 super("Select Java File"); 280 putValue(Action.MNEMONIC_KEY, KeyEvent.VK_S); 281 } 282 283 @Override 284 public void actionPerformed(ActionEvent event) { 285 final JFileChooser chooser = new JFileChooser(lastDirectory); 286 final FileFilter filter = new JavaFileFilter(); 287 chooser.setFileFilter(filter); 288 final Component parent = 289 SwingUtilities.getRoot(ParseTreeInfoPanel.this); 290 chooser.showDialog(parent, "Open"); 291 final File file = chooser.getSelectedFile(); 292 openFile(file, parent); 293 294 } 295 } 296 297 /** 298 * Handler for reload action events. 299 */ 300 private class ReloadAction extends AbstractAction { 301 private static final long serialVersionUID = -1021880396046355863L; 302 303 /** Default constructor to setup current action. */ 304 ReloadAction() { 305 super("Reload Java File"); 306 putValue(Action.MNEMONIC_KEY, KeyEvent.VK_R); 307 } 308 309 @Override 310 public void actionPerformed(ActionEvent event) { 311 final Component parent = 312 SwingUtilities.getRoot(ParseTreeInfoPanel.this); 313 openFile(currentFile, parent); 314 } 315 } 316 317}