001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2021 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 antlr.ASTFactory; 026import antlr.collections.AST; 027import com.puppycrawl.tools.checkstyle.DetailAstImpl; 028import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.DetailNode; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.gui.MainFrameModel.ParseMode; 033import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 034import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 035 036/** 037 * The model that backs the parse tree in the GUI. 038 * 039 */ 040public class ParseTreeTablePresentation { 041 042 /** Exception message. */ 043 private static final String UNKNOWN_COLUMN_MSG = "Unknown column"; 044 045 /** Column names. */ 046 private static final String[] COLUMN_NAMES = { 047 "Tree", 048 "Type", 049 "Line", 050 "Column", 051 "Text", 052 }; 053 054 /** 055 * The root node of the tree table model. 056 */ 057 private final Object root; 058 059 /** Cache to store already parsed Javadoc comments. Used for optimisation purposes. */ 060 private final Map<DetailAST, DetailNode> blockCommentToJavadocTree = new HashMap<>(); 061 062 /** Parsing mode. */ 063 private ParseMode parseMode; 064 065 /** 066 * Constructor initialise root node. 067 * 068 * @param parseTree DetailAST parse tree. 069 */ 070 public ParseTreeTablePresentation(DetailAST parseTree) { 071 root = createArtificialTreeRoot(); 072 setParseTree(parseTree); 073 } 074 075 /** 076 * Set parse tree. 077 * 078 * @param parseTree DetailAST parse tree. 079 */ 080 protected final void setParseTree(DetailAST parseTree) { 081 ((AST) root).setFirstChild((AST) parseTree); 082 } 083 084 /** 085 * Set parse mode. 086 * 087 * @param mode ParseMode enum 088 */ 089 protected void setParseMode(ParseMode mode) { 090 parseMode = mode; 091 } 092 093 /** 094 * Returns number of available columns. 095 * 096 * @return the number of available columns. 097 */ 098 public int getColumnCount() { 099 return COLUMN_NAMES.length; 100 } 101 102 /** 103 * Returns name for specified column number. 104 * 105 * @param column the column number 106 * @return the name for column number {@code column}. 107 */ 108 public String getColumnName(int column) { 109 return COLUMN_NAMES[column]; 110 } 111 112 /** 113 * Returns type of specified column number. 114 * 115 * @param column the column number 116 * @return the type for column number {@code column}. 117 * @throws IllegalStateException if an unknown column index was specified. 118 */ 119 // -@cs[ForbidWildcardAsReturnType] We need to satisfy javax.swing.table.AbstractTableModel 120 // public Class<?> getColumnClass(int columnIndex) {...} 121 public Class<?> getColumnClass(int column) { 122 final Class<?> columnClass; 123 124 switch (column) { 125 case 0: 126 columnClass = ParseTreeTableModel.class; 127 break; 128 case 1: 129 case 4: 130 columnClass = String.class; 131 break; 132 case 2: 133 case 3: 134 columnClass = Integer.class; 135 break; 136 default: 137 throw new IllegalStateException(UNKNOWN_COLUMN_MSG); 138 } 139 return columnClass; 140 } 141 142 /** 143 * Returns the value to be displayed for node at column number. 144 * 145 * @param node the node 146 * @param column the column number 147 * @return the value to be displayed for node {@code node}, at column number {@code column}. 148 */ 149 public Object getValueAt(Object node, int column) { 150 final Object result; 151 152 if (node instanceof DetailNode) { 153 result = getValueAtDetailNode((DetailNode) node, column); 154 } 155 else { 156 result = getValueAtDetailAST((DetailAST) node, column); 157 } 158 159 return result; 160 } 161 162 /** 163 * Returns the child of parent at index. 164 * 165 * @param parent the node to get a child from. 166 * @param index the index of a child. 167 * @return the child of parent at index. 168 */ 169 public Object getChild(Object parent, int index) { 170 final Object result; 171 172 if (parent instanceof DetailNode) { 173 result = ((DetailNode) parent).getChildren()[index]; 174 } 175 else { 176 result = getChildAtDetailAst((DetailAST) parent, index); 177 } 178 179 return result; 180 } 181 182 /** 183 * Returns the number of children of parent. 184 * 185 * @param parent the node to count children for. 186 * @return the number of children of the node parent. 187 */ 188 public int getChildCount(Object parent) { 189 final int result; 190 191 if (parent instanceof DetailNode) { 192 result = ((DetailNode) parent).getChildren().length; 193 } 194 else { 195 if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS 196 && ((AST) parent).getType() == TokenTypes.COMMENT_CONTENT 197 && JavadocUtil.isJavadocComment(((DetailAST) parent).getParent())) { 198 // getChildCount return 0 on COMMENT_CONTENT, 199 // but we need to attach javadoc tree, that is separate tree 200 result = 1; 201 } 202 else { 203 result = ((DetailAST) parent).getChildCount(); 204 } 205 } 206 207 return result; 208 } 209 210 /** 211 * Returns value of root. 212 * 213 * @return the root. 214 */ 215 public Object getRoot() { 216 return root; 217 } 218 219 /** 220 * Whether the node is a leaf. 221 * 222 * @param node the node to check. 223 * @return true if the node is a leaf. 224 */ 225 public boolean isLeaf(Object node) { 226 return getChildCount(node) == 0; 227 } 228 229 /** 230 * Return the index of child in parent. If either {@code parent} 231 * or {@code child} is {@code null}, returns -1. 232 * If either {@code parent} or {@code child} don't 233 * belong to this tree model, returns -1. 234 * 235 * @param parent a node in the tree, obtained from this data source. 236 * @param child the node we are interested in. 237 * @return the index of the child in the parent, or -1 if either 238 * {@code child} or {@code parent} are {@code null} 239 * or don't belong to this tree model. 240 */ 241 public int getIndexOfChild(Object parent, Object child) { 242 int index = -1; 243 for (int i = 0; i < getChildCount(parent); i++) { 244 if (getChild(parent, i).equals(child)) { 245 index = i; 246 break; 247 } 248 } 249 return index; 250 } 251 252 /** 253 * Indicates whether the the value for node {@code node}, at column number {@code column} is 254 * editable. 255 * 256 * @param column the column number 257 * @return true if editable 258 */ 259 public boolean isCellEditable(int column) { 260 return false; 261 } 262 263 /** 264 * Creates artificial tree root. 265 * 266 * @return artificial tree root. 267 */ 268 private static DetailAST createArtificialTreeRoot() { 269 final ASTFactory factory = new ASTFactory(); 270 factory.setASTNodeClass(DetailAstImpl.class.getName()); 271 return (DetailAST) factory.create(TokenTypes.EOF, "ROOT"); 272 } 273 274 /** 275 * Gets child of DetailAST node at specified index. 276 * 277 * @param parent DetailAST node 278 * @param index child index 279 * @return child DetailsAST or DetailNode if child is Javadoc node 280 * and parseMode is JAVA_WITH_JAVADOC_AND_COMMENTS. 281 */ 282 private Object getChildAtDetailAst(DetailAST parent, int index) { 283 final Object result; 284 if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS 285 && parent.getType() == TokenTypes.COMMENT_CONTENT 286 && JavadocUtil.isJavadocComment(parent.getParent())) { 287 result = getJavadocTree(parent.getParent()); 288 } 289 else { 290 int currentIndex = 0; 291 DetailAST child = parent.getFirstChild(); 292 while (currentIndex < index) { 293 child = child.getNextSibling(); 294 currentIndex++; 295 } 296 result = child; 297 } 298 299 return result; 300 } 301 302 /** 303 * Gets a value for DetailNode object. 304 * 305 * @param node DetailNode(Javadoc) node. 306 * @param column column index. 307 * @return value at specified column. 308 * @throws IllegalStateException if an unknown column index was specified. 309 */ 310 private static Object getValueAtDetailNode(DetailNode node, int column) { 311 final Object value; 312 313 switch (column) { 314 case 0: 315 // first column is tree model. no value needed 316 value = null; 317 break; 318 case 1: 319 value = JavadocUtil.getTokenName(node.getType()); 320 break; 321 case 2: 322 value = node.getLineNumber(); 323 break; 324 case 3: 325 value = node.getColumnNumber(); 326 break; 327 case 4: 328 value = node.getText(); 329 break; 330 default: 331 throw new IllegalStateException(UNKNOWN_COLUMN_MSG); 332 } 333 return value; 334 } 335 336 /** 337 * Gets a value for DetailAST object. 338 * 339 * @param ast DetailAST node. 340 * @param column column index. 341 * @return value at specified column. 342 * @throws IllegalStateException if an unknown column index was specified. 343 */ 344 private static Object getValueAtDetailAST(DetailAST ast, int column) { 345 final Object value; 346 347 switch (column) { 348 case 0: 349 // first column is tree model. no value needed 350 value = null; 351 break; 352 case 1: 353 value = TokenUtil.getTokenName(ast.getType()); 354 break; 355 case 2: 356 value = ast.getLineNo(); 357 break; 358 case 3: 359 value = ast.getColumnNo(); 360 break; 361 case 4: 362 value = ast.getText(); 363 break; 364 default: 365 throw new IllegalStateException(UNKNOWN_COLUMN_MSG); 366 } 367 return value; 368 } 369 370 /** 371 * Gets Javadoc (DetailNode) tree of specified block comments. 372 * 373 * @param blockComment Javadoc comment as a block comment 374 * @return root of DetailNode tree 375 */ 376 private DetailNode getJavadocTree(DetailAST blockComment) { 377 return blockCommentToJavadocTree.computeIfAbsent(blockComment, 378 ParseTreeTablePresentation::parseJavadocTree); 379 } 380 381 /** 382 * Parses Javadoc (DetailNode) tree of specified block comments. 383 * 384 * @param blockComment Javadoc comment as a block comment 385 * @return root of DetailNode tree 386 */ 387 private static DetailNode parseJavadocTree(DetailAST blockComment) { 388 return new JavadocDetailNodeParser().parseJavadocAsDetailNode(blockComment).getTree(); 389 } 390 391}