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 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}