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