001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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.DetailAstImpl;
028import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.DetailNode;
031import com.puppycrawl.tools.checkstyle.api.TokenTypes;
032import com.puppycrawl.tools.checkstyle.gui.MainFrameModel.ParseMode;
033import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
034import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
035
036/**
037 * The model that backs the parse tree in the GUI.
038 *
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((AST) 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            case 4:
123                columnClass = String.class;
124                break;
125            case 2:
126            case 3:
127                columnClass = Integer.class;
128                break;
129            default:
130                throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
131        }
132        return columnClass;
133    }
134
135    /**
136     * Returns the value to be displayed for node at column number.
137     * @param node the node
138     * @param column the column number
139     * @return the value to be displayed for node {@code node}, at column number {@code column}.
140     */
141    public Object getValueAt(Object node, int column) {
142        final Object result;
143
144        if (node instanceof DetailNode) {
145            result = getValueAtDetailNode((DetailNode) node, column);
146        }
147        else {
148            result = getValueAtDetailAST((DetailAST) node, column);
149        }
150
151        return result;
152    }
153
154    /**
155     * Returns the child of parent at index.
156     * @param parent the node to get a child from.
157     * @param index the index of a child.
158     * @return the child of parent at index.
159     */
160    public Object getChild(Object parent, int index) {
161        final Object result;
162
163        if (parent instanceof DetailNode) {
164            result = ((DetailNode) parent).getChildren()[index];
165        }
166        else {
167            result = getChildAtDetailAst((DetailAST) parent, index);
168        }
169
170        return result;
171    }
172
173    /**
174     * Returns the number of children of parent.
175     * @param parent the node to count children for.
176     * @return the number of children of the node parent.
177     */
178    public int getChildCount(Object parent) {
179        final int result;
180
181        if (parent instanceof DetailNode) {
182            result = ((DetailNode) parent).getChildren().length;
183        }
184        else {
185            if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
186                    && ((AST) parent).getType() == TokenTypes.COMMENT_CONTENT
187                    && JavadocUtil.isJavadocComment(((DetailAST) parent).getParent())) {
188                //getChildCount return 0 on COMMENT_CONTENT,
189                //but we need to attach javadoc tree, that is separate tree
190                result = 1;
191            }
192            else {
193                result = ((DetailAST) parent).getChildCount();
194            }
195        }
196
197        return result;
198    }
199
200    /**
201     * Returns value of root.
202     * @return the root.
203     */
204    public Object getRoot() {
205        return root;
206    }
207
208    /**
209     * Whether the node is a leaf.
210     * @param node the node to check.
211     * @return true if the node is a leaf.
212     */
213    public boolean isLeaf(Object node) {
214        return getChildCount(node) == 0;
215    }
216
217    /**
218     * Return the index of child in parent.  If either {@code parent}
219     * or {@code child} is {@code null}, returns -1.
220     * If either {@code parent} or {@code child} don't
221     * belong to this tree model, returns -1.
222     *
223     * @param parent a node in the tree, obtained from this data source.
224     * @param child the node we are interested in.
225     * @return the index of the child in the parent, or -1 if either
226     *     {@code child} or {@code parent} are {@code null}
227     *     or don't belong to this tree model.
228     */
229    public int getIndexOfChild(Object parent, Object child) {
230        int index = -1;
231        for (int i = 0; i < getChildCount(parent); i++) {
232            if (getChild(parent, i).equals(child)) {
233                index = i;
234                break;
235            }
236        }
237        return index;
238    }
239
240    /**
241     * Indicates whether the the value for node {@code node}, at column number {@code column} is
242     * editable.
243     * @param column the column number
244     * @return true if editable
245     */
246    public boolean isCellEditable(int column) {
247        return false;
248    }
249
250    /**
251     * Creates artificial tree root.
252     * @return artificial tree root.
253     */
254    private static DetailAST createArtificialTreeRoot() {
255        final ASTFactory factory = new ASTFactory();
256        factory.setASTNodeClass(DetailAstImpl.class.getName());
257        return (DetailAST) factory.create(TokenTypes.EOF, "ROOT");
258    }
259
260    /**
261     * Gets child of DetailAST node at specified index.
262     * @param parent DetailAST node
263     * @param index child index
264     * @return child DetailsAST or DetailNode if child is Javadoc node
265     *         and parseMode is JAVA_WITH_JAVADOC_AND_COMMENTS.
266     */
267    private Object getChildAtDetailAst(DetailAST parent, int index) {
268        final Object result;
269        if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
270                && parent.getType() == TokenTypes.COMMENT_CONTENT
271                && JavadocUtil.isJavadocComment(parent.getParent())) {
272            result = getJavadocTree(parent.getParent());
273        }
274        else {
275            int currentIndex = 0;
276            DetailAST child = parent.getFirstChild();
277            while (currentIndex < index) {
278                child = child.getNextSibling();
279                currentIndex++;
280            }
281            result = child;
282        }
283
284        return result;
285    }
286
287    /**
288     * Gets a value for DetailNode object.
289     * @param node DetailNode(Javadoc) node.
290     * @param column column index.
291     * @return value at specified column.
292     */
293    private static Object getValueAtDetailNode(DetailNode node, int column) {
294        final Object value;
295
296        switch (column) {
297            case 0:
298                // first column is tree model. no value needed
299                value = null;
300                break;
301            case 1:
302                value = JavadocUtil.getTokenName(node.getType());
303                break;
304            case 2:
305                value = node.getLineNumber();
306                break;
307            case 3:
308                value = node.getColumnNumber();
309                break;
310            case 4:
311                value = node.getText();
312                break;
313            default:
314                throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
315        }
316        return value;
317    }
318
319    /**
320     * Gets a value for DetailAST object.
321     * @param ast DetailAST node.
322     * @param column column index.
323     * @return value at specified column.
324     */
325    private static Object getValueAtDetailAST(DetailAST ast, int column) {
326        final Object value;
327
328        switch (column) {
329            case 0:
330                // first column is tree model. no value needed
331                value = null;
332                break;
333            case 1:
334                value = TokenUtil.getTokenName(ast.getType());
335                break;
336            case 2:
337                value = ast.getLineNo();
338                break;
339            case 3:
340                value = ast.getColumnNo();
341                break;
342            case 4:
343                value = ast.getText();
344                break;
345            default:
346                throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
347        }
348        return value;
349    }
350
351    /**
352     * Gets Javadoc (DetailNode) tree of specified block comments.
353     * @param blockComment Javadoc comment as a block comment
354     * @return DetailNode tree
355     */
356    private DetailNode getJavadocTree(DetailAST blockComment) {
357        DetailNode javadocTree = blockCommentToJavadocTree.get(blockComment);
358        if (javadocTree == null) {
359            javadocTree = new JavadocDetailNodeParser().parseJavadocAsDetailNode(blockComment)
360                    .getTree();
361            blockCommentToJavadocTree.put(blockComment, javadocTree);
362        }
363        return javadocTree;
364    }
365
366}