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