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}