001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 */ 118 // -@cs[ForbidWildcardAsReturnType] We need to satisfy javax.swing.table.AbstractTableModel 119 // public Class<?> getColumnClass(int columnIndex) {...} 120 public Class<?> getColumnClass(int column) { 121 final Class<?> columnClass; 122 123 switch (column) { 124 case 0: 125 columnClass = ParseTreeTableModel.class; 126 break; 127 case 1: 128 case 4: 129 columnClass = String.class; 130 break; 131 case 2: 132 case 3: 133 columnClass = Integer.class; 134 break; 135 default: 136 throw new IllegalStateException(UNKNOWN_COLUMN_MSG); 137 } 138 return columnClass; 139 } 140 141 /** 142 * Returns the value to be displayed for node at column number. 143 * 144 * @param node the node 145 * @param column the column number 146 * @return the value to be displayed for node {@code node}, at column number {@code column}. 147 */ 148 public Object getValueAt(Object node, int column) { 149 final Object result; 150 151 if (node instanceof DetailNode) { 152 result = getValueAtDetailNode((DetailNode) node, column); 153 } 154 else { 155 result = getValueAtDetailAST((DetailAST) node, column); 156 } 157 158 return result; 159 } 160 161 /** 162 * Returns the child of parent at index. 163 * 164 * @param parent the node to get a child from. 165 * @param index the index of a child. 166 * @return the child of parent at index. 167 */ 168 public Object getChild(Object parent, int index) { 169 final Object result; 170 171 if (parent instanceof DetailNode) { 172 result = ((DetailNode) parent).getChildren()[index]; 173 } 174 else { 175 result = getChildAtDetailAst((DetailAST) parent, index); 176 } 177 178 return result; 179 } 180 181 /** 182 * Returns the number of children of parent. 183 * 184 * @param parent the node to count children for. 185 * @return the number of children of the node parent. 186 */ 187 public int getChildCount(Object parent) { 188 final int result; 189 190 if (parent instanceof DetailNode) { 191 result = ((DetailNode) parent).getChildren().length; 192 } 193 else { 194 if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS 195 && ((AST) parent).getType() == TokenTypes.COMMENT_CONTENT 196 && JavadocUtil.isJavadocComment(((DetailAST) parent).getParent())) { 197 // getChildCount return 0 on COMMENT_CONTENT, 198 // but we need to attach javadoc tree, that is separate tree 199 result = 1; 200 } 201 else { 202 result = ((DetailAST) parent).getChildCount(); 203 } 204 } 205 206 return result; 207 } 208 209 /** 210 * Returns value of root. 211 * 212 * @return the root. 213 */ 214 public Object getRoot() { 215 return root; 216 } 217 218 /** 219 * Whether the node is a leaf. 220 * 221 * @param node the node to check. 222 * @return true if the node is a leaf. 223 */ 224 public boolean isLeaf(Object node) { 225 return getChildCount(node) == 0; 226 } 227 228 /** 229 * Return the index of child in parent. If either {@code parent} 230 * or {@code child} is {@code null}, returns -1. 231 * If either {@code parent} or {@code child} don't 232 * belong to this tree model, returns -1. 233 * 234 * @param parent a node in the tree, obtained from this data source. 235 * @param child the node we are interested in. 236 * @return the index of the child in the parent, or -1 if either 237 * {@code child} or {@code parent} are {@code null} 238 * or don't belong to this tree model. 239 */ 240 public int getIndexOfChild(Object parent, Object child) { 241 int index = -1; 242 for (int i = 0; i < getChildCount(parent); i++) { 243 if (getChild(parent, i).equals(child)) { 244 index = i; 245 break; 246 } 247 } 248 return index; 249 } 250 251 /** 252 * Indicates whether the the value for node {@code node}, at column number {@code column} is 253 * editable. 254 * 255 * @param column the column number 256 * @return true if editable 257 */ 258 public boolean isCellEditable(int column) { 259 return false; 260 } 261 262 /** 263 * Creates artificial tree root. 264 * 265 * @return artificial tree root. 266 */ 267 private static DetailAST createArtificialTreeRoot() { 268 final ASTFactory factory = new ASTFactory(); 269 factory.setASTNodeClass(DetailAstImpl.class.getName()); 270 return (DetailAST) factory.create(TokenTypes.EOF, "ROOT"); 271 } 272 273 /** 274 * Gets child of DetailAST node at specified index. 275 * 276 * @param parent DetailAST node 277 * @param index child index 278 * @return child DetailsAST or DetailNode if child is Javadoc node 279 * and parseMode is JAVA_WITH_JAVADOC_AND_COMMENTS. 280 */ 281 private Object getChildAtDetailAst(DetailAST parent, int index) { 282 final Object result; 283 if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS 284 && parent.getType() == TokenTypes.COMMENT_CONTENT 285 && JavadocUtil.isJavadocComment(parent.getParent())) { 286 result = getJavadocTree(parent.getParent()); 287 } 288 else { 289 int currentIndex = 0; 290 DetailAST child = parent.getFirstChild(); 291 while (currentIndex < index) { 292 child = child.getNextSibling(); 293 currentIndex++; 294 } 295 result = child; 296 } 297 298 return result; 299 } 300 301 /** 302 * Gets a value for DetailNode object. 303 * 304 * @param node DetailNode(Javadoc) node. 305 * @param column column index. 306 * @return value at specified column. 307 */ 308 private static Object getValueAtDetailNode(DetailNode node, int column) { 309 final Object value; 310 311 switch (column) { 312 case 0: 313 // first column is tree model. no value needed 314 value = null; 315 break; 316 case 1: 317 value = JavadocUtil.getTokenName(node.getType()); 318 break; 319 case 2: 320 value = node.getLineNumber(); 321 break; 322 case 3: 323 value = node.getColumnNumber(); 324 break; 325 case 4: 326 value = node.getText(); 327 break; 328 default: 329 throw new IllegalStateException(UNKNOWN_COLUMN_MSG); 330 } 331 return value; 332 } 333 334 /** 335 * Gets a value for DetailAST object. 336 * 337 * @param ast DetailAST node. 338 * @param column column index. 339 * @return value at specified column. 340 */ 341 private static Object getValueAtDetailAST(DetailAST ast, int column) { 342 final Object value; 343 344 switch (column) { 345 case 0: 346 // first column is tree model. no value needed 347 value = null; 348 break; 349 case 1: 350 value = TokenUtil.getTokenName(ast.getType()); 351 break; 352 case 2: 353 value = ast.getLineNo(); 354 break; 355 case 3: 356 value = ast.getColumnNo(); 357 break; 358 case 4: 359 value = ast.getText(); 360 break; 361 default: 362 throw new IllegalStateException(UNKNOWN_COLUMN_MSG); 363 } 364 return value; 365 } 366 367 /** 368 * Gets Javadoc (DetailNode) tree of specified block comments. 369 * 370 * @param blockComment Javadoc comment as a block comment 371 * @return root of DetailNode tree 372 */ 373 private DetailNode getJavadocTree(DetailAST blockComment) { 374 return blockCommentToJavadocTree.computeIfAbsent(blockComment, 375 ParseTreeTablePresentation::parseJavadocTree); 376 } 377 378 /** 379 * Parses Javadoc (DetailNode) tree of specified block comments. 380 * 381 * @param blockComment Javadoc comment as a block comment 382 * @return root of DetailNode tree 383 */ 384 private static DetailNode parseJavadocTree(DetailAST blockComment) { 385 return new JavadocDetailNodeParser().parseJavadocAsDetailNode(blockComment).getTree(); 386 } 387 388}