1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.gui;
21
22 import java.util.HashMap;
23 import java.util.Map;
24
25 import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser;
26 import com.puppycrawl.tools.checkstyle.api.DetailAST;
27 import com.puppycrawl.tools.checkstyle.api.DetailNode;
28 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29 import com.puppycrawl.tools.checkstyle.gui.MainFrameModel.ParseMode;
30 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
31 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
32
33 /**
34 * The model that backs the parse tree in the GUI.
35 *
36 */
37 public class ParseTreeTablePresentation {
38
39 /** Exception message. */
40 private static final String UNKNOWN_COLUMN_MSG = "Unknown column";
41
42 /** Column names. */
43 private static final String[] COLUMN_NAMES = {
44 "Tree",
45 "Type",
46 "Line",
47 "Column",
48 "Text",
49 };
50
51 /** Cache to store already parsed Javadoc comments. Used for optimisation purposes. */
52 private final Map<DetailAST, DetailNode> blockCommentToJavadocTree = new HashMap<>();
53
54 /** The root node of the tree table model. */
55 private DetailAST root;
56
57 /** Parsing mode. */
58 private ParseMode parseMode;
59
60 /**
61 * Constructor initialise root node.
62 *
63 * @param parseTree DetailAST parse tree.
64 */
65 public ParseTreeTablePresentation(DetailAST parseTree) {
66 root = parseTree;
67 }
68
69 /**
70 * Set parse tree.
71 *
72 * @param parseTree DetailAST parse tree.
73 */
74 protected final void setRoot(DetailAST parseTree) {
75 root = parseTree;
76 }
77
78 /**
79 * Set parse mode.
80 *
81 * @param mode ParseMode enum
82 */
83 protected void setParseMode(ParseMode mode) {
84 parseMode = mode;
85 }
86
87 /**
88 * Returns number of available columns.
89 *
90 * @return the number of available columns.
91 */
92 public int getColumnCount() {
93 return COLUMN_NAMES.length;
94 }
95
96 /**
97 * Returns name for specified column number.
98 *
99 * @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 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 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 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
224 * belong to this tree model, returns -1.
225 *
226 * @param parent a node in the tree, obtained from this data source.
227 * @param child the node we are interested in.
228 * @return the index of the child in the parent, or -1 if either
229 * {@code child} or {@code parent} are {@code null}
230 * or don't belong to this tree model.
231 */
232 public int getIndexOfChild(Object parent, Object child) {
233 int index = -1;
234 for (int i = 0; i < getChildCount(parent); i++) {
235 if (getChild(parent, i).equals(child)) {
236 index = i;
237 break;
238 }
239 }
240 return index;
241 }
242
243 /**
244 * Indicates whether the value for node {@code node}, at column number {@code column} is
245 * editable.
246 *
247 * @param column the column number
248 * @return true if editable
249 */
250 public boolean isCellEditable(int column) {
251 return false;
252 }
253
254 /**
255 * Gets child of DetailAST node at specified index.
256 *
257 * @param parent DetailAST node
258 * @param index child index
259 * @return child DetailsAST or DetailNode if child is Javadoc node
260 * and parseMode is JAVA_WITH_JAVADOC_AND_COMMENTS.
261 */
262 private Object getChildAtDetailAst(DetailAST parent, int index) {
263 final Object result;
264 if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
265 && parent.getType() == TokenTypes.COMMENT_CONTENT
266 && JavadocUtil.isJavadocComment(parent.getParent())) {
267 result = getJavadocTree(parent.getParent());
268 }
269 else {
270 int currentIndex = 0;
271 DetailAST child = parent.getFirstChild();
272 while (currentIndex < index) {
273 child = child.getNextSibling();
274 currentIndex++;
275 }
276 result = child;
277 }
278
279 return result;
280 }
281
282 /**
283 * Gets a value for DetailNode object.
284 *
285 * @param node DetailNode(Javadoc) node.
286 * @param column column index.
287 * @return value at specified column.
288 * @throws IllegalStateException if an unknown column index was specified.
289 */
290 private static Object getValueAtDetailNode(DetailNode node, int column) {
291
292 return switch (column) {
293 case 0 ->
294 // first column is tree model. no value needed
295 null;
296 case 1 -> JavadocUtil.getTokenName(node.getType());
297 case 2 -> node.getLineNumber();
298 case 3 -> node.getColumnNumber();
299 case 4 -> node.getText();
300 default -> throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
301 };
302 }
303
304 /**
305 * Gets a value for DetailAST object.
306 *
307 * @param ast DetailAST node.
308 * @param column column index.
309 * @return value at specified column.
310 * @throws IllegalStateException if an unknown column index was specified.
311 */
312 private static Object getValueAtDetailAST(DetailAST ast, int column) {
313
314 return switch (column) {
315 case 0 ->
316 // first column is tree model. no value needed
317 null;
318 case 1 -> TokenUtil.getTokenName(ast.getType());
319 case 2 -> ast.getLineNo();
320 case 3 -> ast.getColumnNo();
321 case 4 -> ast.getText();
322 default -> throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
323 };
324 }
325
326 /**
327 * Gets Javadoc (DetailNode) tree of specified block comments.
328 *
329 * @param blockComment Javadoc comment as a block comment
330 * @return root of DetailNode tree
331 */
332 private DetailNode getJavadocTree(DetailAST blockComment) {
333 return blockCommentToJavadocTree.computeIfAbsent(blockComment,
334 ParseTreeTablePresentation::parseJavadocTree);
335 }
336
337 /**
338 * Parses Javadoc (DetailNode) tree of specified block comments.
339 *
340 * @param blockComment Javadoc comment as a block comment
341 * @return root of DetailNode tree
342 */
343 private static DetailNode parseJavadocTree(DetailAST blockComment) {
344 return new JavadocDetailNodeParser().parseJavadocComment(blockComment).getTree();
345 }
346
347 }