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