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}