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