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