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