001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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.util.HashMap;
023import java.util.Map;
024
025import antlr.ASTFactory;
026import antlr.collections.AST;
027import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.DetailNode;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031import com.puppycrawl.tools.checkstyle.gui.MainFrameModel.ParseMode;
032import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
033import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
034
035/**
036 * The model that backs the parse tree in the GUI.
037 *
038 * @author Lars K├╝hne
039 */
040public class ParseTreeTablePresentation {
041
042    /** Exception message. */
043    private static final String UNKNOWN_COLUMN_MSG = "Unknown column";
044
045    /** Column names. */
046    private static final String[] COLUMN_NAMES = {
047        "Tree",
048        "Type",
049        "Line",
050        "Column",
051        "Text",
052    };
053
054    /**
055     * The root node of the tree table model.
056     */
057    private final Object root;
058
059    /** Cache to store already parsed Javadoc comments. Used for optimisation purposes. */
060    private final Map<DetailAST, DetailNode> blockCommentToJavadocTree = new HashMap<>();
061
062    /** Parsing mode. */
063    private ParseMode parseMode;
064
065    /**
066     * Constructor initialise root node.
067     * @param parseTree DetailAST parse tree.
068     */
069    public ParseTreeTablePresentation(DetailAST parseTree) {
070        root = createArtificialTreeRoot();
071        setParseTree(parseTree);
072    }
073
074    /**
075     * Set parse tree.
076     * @param parseTree DetailAST parse tree.
077     */
078    protected final void setParseTree(DetailAST parseTree) {
079        ((AST) root).setFirstChild(parseTree);
080    }
081
082    /**
083     * Set parse mode.
084     * @param mode ParseMode enum
085     */
086    protected void setParseMode(ParseMode mode) {
087        parseMode = mode;
088    }
089
090    /**
091     * Returns number of available columns.
092     * @return the number of available columns.
093     */
094    public int getColumnCount() {
095        return COLUMN_NAMES.length;
096    }
097
098    /**
099     * Returns name for specified column number.
100     * @param column the column number
101     * @return the name for column number {@code column}.
102     */
103    public String getColumnName(int column) {
104        return COLUMN_NAMES[column];
105    }
106
107    /**
108     * Returns type of specified column number.
109     * @param column the column number
110     * @return the type for column number {@code column}.
111     */
112    // -@cs[ForbidWildcardAsReturnType] We need to satisfy javax.swing.table.AbstractTableModel
113    // public Class<?> getColumnClass(int columnIndex) {...}
114    public Class<?> getColumnClass(int column) {
115        final Class<?> columnClass;
116
117        switch (column) {
118            case 0:
119                columnClass = ParseTreeTableModel.class;
120                break;
121            case 1:
122                columnClass = String.class;
123                break;
124            case 2:
125                columnClass = Integer.class;
126                break;
127            case 3:
128                columnClass = Integer.class;
129                break;
130            case 4:
131                columnClass = String.class;
132                break;
133            default:
134                throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
135        }
136        return columnClass;
137    }
138
139    /**
140     * Returns the value to be displayed for node at column number.
141     * @param node the node
142     * @param column the column number
143     * @return the value to be displayed for node {@code node}, at column number {@code column}.
144     */
145    public Object getValueAt(Object node, int column) {
146        final Object result;
147
148        if (node instanceof DetailNode) {
149            result = getValueAtDetailNode((DetailNode) node, column);
150        }
151        else {
152            result = getValueAtDetailAST((DetailAST) node, column);
153        }
154
155        return result;
156    }
157
158    /**
159     * Returns the child of parent at index.
160     * @param parent the node to get a child from.
161     * @param index the index of a child.
162     * @return the child of parent at index.
163     */
164    public Object getChild(Object parent, int index) {
165        final Object result;
166
167        if (parent instanceof DetailNode) {
168            result = ((DetailNode) parent).getChildren()[index];
169        }
170        else {
171            result = getChildAtDetailAst((DetailAST) parent, index);
172        }
173
174        return result;
175    }
176
177    /**
178     * Returns the number of children of parent.
179     * @param parent the node to count children for.
180     * @return the number of children of the node parent.
181     */
182    public int getChildCount(Object parent) {
183        final int result;
184
185        if (parent instanceof DetailNode) {
186            result = ((DetailNode) parent).getChildren().length;
187        }
188        else {
189            if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
190                    && ((AST) parent).getType() == TokenTypes.COMMENT_CONTENT
191                    && JavadocUtils.isJavadocComment(((DetailAST) parent).getParent())) {
192                //getChildCount return 0 on COMMENT_CONTENT,
193                //but we need to attach javadoc tree, that is separate tree
194                result = 1;
195            }
196            else {
197                result = ((DetailAST) parent).getChildCount();
198            }
199        }
200
201        return result;
202    }
203
204    /**
205     * Returns value of root.
206     * @return the root.
207     */
208    public Object getRoot() {
209        return root;
210    }
211
212    /**
213     * Whether the node is a leaf.
214     * @param node the node to check.
215     * @return true if the node is a leaf.
216     */
217    public boolean isLeaf(Object node) {
218        return getChildCount(node) == 0;
219    }
220
221    /**
222     * Return the index of child in parent.  If either {@code parent}
223     * or {@code child} is {@code null}, returns -1.
224     * If either {@code parent} or {@code child} don't
225     * belong to this tree model, returns -1.
226     *
227     * @param parent a node in the tree, obtained from this data source.
228     * @param child the node we are interested in.
229     * @return the index of the child in the parent, or -1 if either
230     *     {@code child} or {@code parent} are {@code null}
231     *     or don't belong to this tree model.
232     */
233    public int getIndexOfChild(Object parent, Object child) {
234        int index = -1;
235        for (int i = 0; i < getChildCount(parent); i++) {
236            if (getChild(parent, i).equals(child)) {
237                index = i;
238                break;
239            }
240        }
241        return index;
242    }
243
244    /**
245     * Indicates whether the the value for node {@code node}, at column number {@code column} is
246     * editable.
247     * @param column the column number
248     * @return true if editable
249     */
250    public boolean isCellEditable(int column) {
251        return false;
252    }
253
254    /**
255     * Creates artificial tree root.
256     * @return artificial tree root.
257     */
258    private static DetailAST createArtificialTreeRoot() {
259        final ASTFactory factory = new ASTFactory();
260        factory.setASTNodeClass(DetailAST.class.getName());
261        return (DetailAST) factory.create(TokenTypes.EOF, "ROOT");
262    }
263
264    /**
265     * Gets child of DetailAST node at specified index.
266     * @param parent DetailAST node
267     * @param index child index
268     * @return child DetailsAST or DetailNode if child is Javadoc node
269     *         and parseMode is JAVA_WITH_JAVADOC_AND_COMMENTS.
270     */
271    private Object getChildAtDetailAst(DetailAST parent, int index) {
272        final Object result;
273        if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
274                && parent.getType() == TokenTypes.COMMENT_CONTENT
275                && JavadocUtils.isJavadocComment(parent.getParent())) {
276            result = getJavadocTree(parent.getParent());
277        }
278        else {
279            int currentIndex = 0;
280            DetailAST child = parent.getFirstChild();
281            while (currentIndex < index) {
282                child = child.getNextSibling();
283                currentIndex++;
284            }
285            result = child;
286        }
287
288        return result;
289    }
290
291    /**
292     * Gets a value for DetailNode object.
293     * @param node DetailNode(Javadoc) node.
294     * @param column column index.
295     * @return value at specified column.
296     */
297    private static Object getValueAtDetailNode(DetailNode node, int column) {
298        final Object value;
299
300        switch (column) {
301            case 0:
302                // first column is tree model. no value needed
303                value = null;
304                break;
305            case 1:
306                value = JavadocUtils.getTokenName(node.getType());
307                break;
308            case 2:
309                value = node.getLineNumber();
310                break;
311            case 3:
312                value = node.getColumnNumber();
313                break;
314            case 4:
315                value = node.getText();
316                break;
317            default:
318                throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
319        }
320        return value;
321    }
322
323    /**
324     * Gets a value for DetailAST object.
325     * @param ast DetailAST node.
326     * @param column column index.
327     * @return value at specified column.
328     */
329    private static Object getValueAtDetailAST(DetailAST ast, int column) {
330        final Object value;
331
332        switch (column) {
333            case 0:
334                // first column is tree model. no value needed
335                value = null;
336                break;
337            case 1:
338                value = TokenUtils.getTokenName(ast.getType());
339                break;
340            case 2:
341                value = ast.getLineNo();
342                break;
343            case 3:
344                value = ast.getColumnNo();
345                break;
346            case 4:
347                value = ast.getText();
348                break;
349            default:
350                throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
351        }
352        return value;
353    }
354
355    /**
356     * Gets Javadoc (DetailNode) tree of specified block comments.
357     * @param blockComment Javadoc comment as a block comment
358     * @return DetailNode tree
359     */
360    private DetailNode getJavadocTree(DetailAST blockComment) {
361        DetailNode javadocTree = blockCommentToJavadocTree.get(blockComment);
362        if (javadocTree == null) {
363            javadocTree = new JavadocDetailNodeParser().parseJavadocAsDetailNode(blockComment)
364                    .getTree();
365            blockCommentToJavadocTree.put(blockComment, javadocTree);
366        }
367        return javadocTree;
368    }
369}