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}