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}