001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2002 Oliver Burn 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{ 060 /** For Serialisation that will never happen. */ 061 private static final long serialVersionUID = -4243405131202059043L; 062 private final JTreeTable treeTable; 063 private final ParseTreeModel parseTreeModel; 064 private final JTextArea jTextArea; 065 private File lastDirectory = null; 066 private File currentFile = null; 067 private final Action reloadAction; 068 private final List<Integer> lines2position = new ArrayList<Integer>(); 069 070 private static class JavaFileFilter extends FileFilter 071 { 072 @Override 073 public boolean accept(File f) 074 { 075 if (f == null) { 076 return false; 077 } 078 return f.isDirectory() || f.getName().endsWith(".java"); 079 } 080 081 @Override 082 public String getDescription() 083 { 084 return "Java Source Code"; 085 } 086 } 087 088 public void openAst(DetailAST parseTree, final Component parent) 089 { 090 parseTreeModel.setParseTree(parseTree); 091 reloadAction.setEnabled(true); 092 093 // clear for each new file 094 getLines2position().clear(); 095 // starts line counting at 1 096 getLines2position().add(0); 097 // insert the contents of the file to the text area 098 099 // clean the text area before inserting the lines of the new file 100 if (jTextArea.getText().length() != 0) { 101 jTextArea.replaceRange("", 0, jTextArea.getText().length()); 102 } 103 104 // move back to the top of the file 105 jTextArea.moveCaretPosition(0); 106 } 107 108 private class FileSelectionAction extends AbstractAction 109 { 110 /** 111 * 112 */ 113 private static final long serialVersionUID = -1926935338069418119L; 114 115 public FileSelectionAction() 116 { 117 super("Select Java File"); 118 putValue(Action.MNEMONIC_KEY, KeyEvent.VK_S); 119 } 120 121 @Override 122 public void actionPerformed(ActionEvent e) 123 { 124 final JFileChooser fc = new JFileChooser( lastDirectory ); 125 final FileFilter filter = new JavaFileFilter(); 126 fc.setFileFilter(filter); 127 final Component parent = 128 SwingUtilities.getRoot(ParseTreeInfoPanel.this); 129 fc.showDialog(parent, "Open"); 130 final File file = fc.getSelectedFile(); 131 openFile(file, parent); 132 133 } 134 } 135 136 private class ReloadAction extends AbstractAction 137 { 138 /** 139 * 140 */ 141 private static final long serialVersionUID = -1021880396046355863L; 142 143 public ReloadAction() 144 { 145 super("Reload Java File"); 146 putValue(Action.MNEMONIC_KEY, KeyEvent.VK_R); 147 } 148 149 @Override 150 public void actionPerformed(ActionEvent e) 151 { 152 final Component parent = 153 SwingUtilities.getRoot(ParseTreeInfoPanel.this); 154 openFile(currentFile, parent); 155 } 156 } 157 158 159 private class FileDropListener implements FileDrop.Listener 160 { 161 private final JScrollPane mSp; 162 163 @Override 164 public void filesDropped(File[] files) 165 { 166 if ((files != null) && (files.length > 0)) 167 { 168 final File file = files[0]; 169 openFile(file, mSp); 170 } 171 } 172 173 public FileDropListener(JScrollPane aSp) 174 { 175 mSp = aSp; 176 } 177 } 178 179 180 public void openFile(File file, final Component parent) 181 { 182 if (file != null) { 183 try { 184 Main.frame.setTitle("Checkstyle : " + file.getName()); 185 final FileText text = new FileText(file.getAbsoluteFile(), 186 getEncoding()); 187 final DetailAST parseTree = parseFile(text); 188 parseTreeModel.setParseTree(parseTree); 189 currentFile = file; 190 lastDirectory = file.getParentFile(); 191 reloadAction.setEnabled(true); 192 193 final String[] sourceLines = text.toLinesArray(); 194 195 // clear for each new file 196 getLines2position().clear(); 197 // starts line counting at 1 198 getLines2position().add(0); 199 // insert the contents of the file to the text area 200 for (String element : sourceLines) 201 { 202 getLines2position().add(jTextArea.getText().length()); 203 jTextArea.append(element + "\n"); 204 } 205 206 //clean the text area before inserting the lines of the new file 207 if (jTextArea.getText().length() != 0) { 208 jTextArea.replaceRange("", 0, jTextArea.getText() 209 .length()); 210 } 211 212 // insert the contents of the file to the text area 213 for (final String element : sourceLines) { 214 jTextArea.append(element + "\n"); 215 } 216 217 // move back to the top of the file 218 jTextArea.moveCaretPosition(0); 219 } 220 catch (final IOException ex) { 221 showErrorDialog( 222 parent, 223 "Could not open " + file + ": " + ex.getMessage()); 224 } 225 catch (final ANTLRException ex) { 226 showErrorDialog( 227 parent, 228 "Could not parse " + file + ": " + ex.getMessage()); 229 } 230 } 231 } 232 233 /** 234 * Parses a file and returns the parse tree. 235 * @param fileName the file to parse 236 * @return the root node of the parse tree 237 * @throws IOException if the file cannot be opened 238 * @throws ANTLRException if the file is not a Java source 239 * @deprecated Use {@link #parseFile(FileText)} instead 240 */ 241 @Deprecated 242 public static DetailAST parseFile(String fileName) 243 throws IOException, ANTLRException 244 { 245 return parseFile(new FileText(new File(fileName), getEncoding())); 246 } 247 248 /** 249 * Parses a file and returns the parse tree. 250 * @param text the file to parse 251 * @return the root node of the parse tree 252 * @throws ANTLRException if the file is not a Java source 253 */ 254 public static DetailAST parseFile(FileText text) 255 throws ANTLRException 256 { 257 final FileContents contents = new FileContents(text); 258 return TreeWalker.parse(contents); 259 } 260 261 /** 262 * Returns the configured file encoding. 263 * This can be set using the {@code file.encoding} system property. 264 * It defaults to UTF-8. 265 * @return the configured file encoding 266 */ 267 private static String getEncoding() 268 { 269 return System.getProperty("file.encoding", "UTF-8"); 270 } 271 272 /** 273 * Create a new ParseTreeInfoPanel instance. 274 */ 275 public ParseTreeInfoPanel() 276 { 277 setLayout(new BorderLayout()); 278 279 final DetailAST treeRoot = null; 280 parseTreeModel = new ParseTreeModel(treeRoot); 281 treeTable = new JTreeTable(parseTreeModel); 282 final JScrollPane sp = new JScrollPane(treeTable); 283 this.add(sp, BorderLayout.NORTH); 284 285 final JButton fileSelectionButton = 286 new JButton(new FileSelectionAction()); 287 288 reloadAction = new ReloadAction(); 289 reloadAction.setEnabled(false); 290 final JButton reloadButton = new JButton(reloadAction); 291 292 jTextArea = new JTextArea(20, 15); 293 jTextArea.setEditable(false); 294 treeTable.setEditor(jTextArea); 295 treeTable.setLinePositionMap(lines2position); 296 297 final JScrollPane sp2 = new JScrollPane(jTextArea); 298 this.add(sp2, BorderLayout.CENTER); 299 300 final JPanel p = new JPanel(new GridLayout(1,2)); 301 this.add(p, BorderLayout.SOUTH); 302 p.add(fileSelectionButton); 303 p.add(reloadButton); 304 305 try { 306 // TODO: creating an object for the side effect of the constructor 307 // and then ignoring the object looks strange. 308 new FileDrop(sp, new FileDropListener(sp)); 309 } 310 catch (final TooManyListenersException ex) 311 { 312 showErrorDialog(null, "Cannot initialize Drag and Drop support"); 313 } 314 315 } 316 317 private void showErrorDialog(final Component parent, final String msg) 318 { 319 final Runnable showError = new Runnable() 320 { 321 @Override 322 public void run() 323 { 324 JOptionPane.showMessageDialog(parent, msg); 325 } 326 }; 327 SwingUtilities.invokeLater(showError); 328 } 329 330 public List<Integer> getLines2position() 331 { 332 return lines2position; 333 } 334} 335