001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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.checks.javadoc; 021 022import java.util.ArrayDeque; 023import java.util.Deque; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.Iterator; 027import java.util.Map; 028import java.util.Set; 029 030import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 031import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 032import com.puppycrawl.tools.checkstyle.api.DetailAST; 033import com.puppycrawl.tools.checkstyle.api.FullIdent; 034import com.puppycrawl.tools.checkstyle.api.LocalizedMessage; 035import com.puppycrawl.tools.checkstyle.api.TokenTypes; 036 037/** 038 * Abstract class that endeavours to maintain type information for the Java 039 * file being checked. It provides helper methods for performing type 040 * information functions. 041 * 042 * @deprecated Checkstyle is not type aware tool and all Checks derived from this 043 * class are potentially unstable. 044 * @noinspection DeprecatedIsStillUsed, AbstractClassWithOnlyOneDirectInheritor 045 */ 046@Deprecated 047@FileStatefulCheck 048public abstract class AbstractTypeAwareCheck extends AbstractCheck { 049 050 /** Stack of maps for type params. */ 051 private final Deque<Map<String, AbstractClassInfo>> typeParams = new ArrayDeque<>(); 052 053 /** Imports details. **/ 054 private final Set<String> imports = new HashSet<>(); 055 056 /** Full identifier for package of the method. **/ 057 private FullIdent packageFullIdent; 058 059 /** Name of current class. */ 060 private String currentClassName; 061 062 /** {@code ClassResolver} instance for current tree. */ 063 private ClassResolver classResolver; 064 065 /** 066 * Whether to log class loading errors to the checkstyle report 067 * instead of throwing a RTE. 068 * 069 * <p>Logging errors will avoid stopping checkstyle completely 070 * because of a typo in javadoc. However, with modern IDEs that 071 * support automated refactoring and generate javadoc this will 072 * occur rarely, so by default we assume a configuration problem 073 * in the checkstyle classpath and throw an exception. 074 * 075 * <p>This configuration option was triggered by bug 1422462. 076 */ 077 private boolean logLoadErrors = true; 078 079 /** 080 * Whether to show class loading errors in the checkstyle report. 081 * Request ID 1491630 082 */ 083 private boolean suppressLoadErrors; 084 085 /** 086 * Called to process an AST when visiting it. 087 * @param ast the AST to process. Guaranteed to not be PACKAGE_DEF or 088 * IMPORT tokens. 089 */ 090 protected abstract void processAST(DetailAST ast); 091 092 /** 093 * Logs error if unable to load class information. 094 * Abstract, should be overridden in subclasses. 095 * @param ident class name for which we can no load class. 096 */ 097 protected abstract void logLoadError(Token ident); 098 099 /** 100 * Controls whether to log class loading errors to the checkstyle report 101 * instead of throwing a RTE. 102 * 103 * @param logLoadErrors true if errors should be logged 104 */ 105 public final void setLogLoadErrors(boolean logLoadErrors) { 106 this.logLoadErrors = logLoadErrors; 107 } 108 109 /** 110 * Controls whether to show class loading errors in the checkstyle report. 111 * 112 * @param suppressLoadErrors true if errors shouldn't be shown 113 */ 114 public final void setSuppressLoadErrors(boolean suppressLoadErrors) { 115 this.suppressLoadErrors = suppressLoadErrors; 116 } 117 118 @Override 119 public final int[] getRequiredTokens() { 120 return new int[] { 121 TokenTypes.PACKAGE_DEF, 122 TokenTypes.IMPORT, 123 TokenTypes.CLASS_DEF, 124 TokenTypes.INTERFACE_DEF, 125 TokenTypes.ENUM_DEF, 126 }; 127 } 128 129 @Override 130 public void beginTree(DetailAST rootAST) { 131 packageFullIdent = FullIdent.createFullIdent(null); 132 imports.clear(); 133 // add java.lang.* since it's always imported 134 imports.add("java.lang.*"); 135 classResolver = null; 136 currentClassName = ""; 137 typeParams.clear(); 138 } 139 140 @Override 141 public final void visitToken(DetailAST ast) { 142 if (ast.getType() == TokenTypes.PACKAGE_DEF) { 143 processPackage(ast); 144 } 145 else if (ast.getType() == TokenTypes.IMPORT) { 146 processImport(ast); 147 } 148 else if (ast.getType() == TokenTypes.CLASS_DEF 149 || ast.getType() == TokenTypes.INTERFACE_DEF 150 || ast.getType() == TokenTypes.ENUM_DEF) { 151 processClass(ast); 152 } 153 else { 154 if (ast.getType() == TokenTypes.METHOD_DEF) { 155 processTypeParams(ast); 156 } 157 processAST(ast); 158 } 159 } 160 161 @Override 162 public final void leaveToken(DetailAST ast) { 163 if (ast.getType() == TokenTypes.CLASS_DEF 164 || ast.getType() == TokenTypes.INTERFACE_DEF 165 || ast.getType() == TokenTypes.ENUM_DEF) { 166 // perhaps it was inner class 167 int dotIdx = currentClassName.lastIndexOf('$'); 168 if (dotIdx == -1) { 169 // perhaps just a class 170 dotIdx = currentClassName.lastIndexOf('.'); 171 } 172 if (dotIdx == -1) { 173 // looks like a topmost class 174 currentClassName = ""; 175 } 176 else { 177 currentClassName = currentClassName.substring(0, dotIdx); 178 } 179 typeParams.pop(); 180 } 181 else if (ast.getType() == TokenTypes.METHOD_DEF) { 182 typeParams.pop(); 183 } 184 } 185 186 /** 187 * Is exception is unchecked (subclass of {@code RuntimeException} 188 * or {@code Error}. 189 * 190 * @param exception {@code Class} of exception to check 191 * @return true if exception is unchecked 192 * false if exception is checked 193 */ 194 protected static boolean isUnchecked(Class<?> exception) { 195 return isSubclass(exception, RuntimeException.class) 196 || isSubclass(exception, Error.class); 197 } 198 199 /** 200 * Checks if one class is subclass of another. 201 * 202 * @param child {@code Class} of class 203 * which should be child 204 * @param parent {@code Class} of class 205 * which should be parent 206 * @return true if aChild is subclass of aParent 207 * false otherwise 208 */ 209 protected static boolean isSubclass(Class<?> child, Class<?> parent) { 210 return parent != null && child != null 211 && parent.isAssignableFrom(child); 212 } 213 214 /** 215 * Returns the current tree's ClassResolver. 216 * @return {@code ClassResolver} for current tree. 217 */ 218 private ClassResolver getClassResolver() { 219 if (classResolver == null) { 220 classResolver = 221 new ClassResolver(getClassLoader(), 222 packageFullIdent.getText(), 223 imports); 224 } 225 return classResolver; 226 } 227 228 /** 229 * Attempts to resolve the Class for a specified name. 230 * @param resolvableClassName name of the class to resolve 231 * @param className name of surrounding class. 232 * @return the resolved class or {@code null} 233 * if unable to resolve the class. 234 * @noinspection WeakerAccess 235 */ 236 // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon. 237 protected final Class<?> resolveClass(String resolvableClassName, 238 String className) { 239 Class<?> clazz; 240 try { 241 clazz = getClassResolver().resolve(resolvableClassName, className); 242 } 243 catch (final ClassNotFoundException ignored) { 244 clazz = null; 245 } 246 return clazz; 247 } 248 249 /** 250 * Tries to load class. Logs error if unable. 251 * @param ident name of class which we try to load. 252 * @param className name of surrounding class. 253 * @return {@code Class} for a ident. 254 * @noinspection WeakerAccess 255 */ 256 // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon. 257 protected final Class<?> tryLoadClass(Token ident, String className) { 258 final Class<?> clazz = resolveClass(ident.getText(), className); 259 if (clazz == null) { 260 logLoadError(ident); 261 } 262 return clazz; 263 } 264 265 /** 266 * Common implementation for logLoadError() method. 267 * @param lineNo line number of the problem. 268 * @param columnNo column number of the problem. 269 * @param msgKey message key to use. 270 * @param values values to fill the message out. 271 */ 272 protected final void logLoadErrorImpl(int lineNo, int columnNo, 273 String msgKey, Object... values) { 274 if (!logLoadErrors) { 275 final LocalizedMessage msg = new LocalizedMessage(lineNo, 276 columnNo, 277 getMessageBundle(), 278 msgKey, 279 values, 280 getSeverityLevel(), 281 getId(), 282 getClass(), 283 null); 284 throw new IllegalStateException(msg.getMessage()); 285 } 286 287 if (!suppressLoadErrors) { 288 log(lineNo, columnNo, msgKey, values); 289 } 290 } 291 292 /** 293 * Collects the details of a package. 294 * @param ast node containing the package details 295 */ 296 private void processPackage(DetailAST ast) { 297 final DetailAST nameAST = ast.getLastChild().getPreviousSibling(); 298 packageFullIdent = FullIdent.createFullIdent(nameAST); 299 } 300 301 /** 302 * Collects the details of imports. 303 * @param ast node containing the import details 304 */ 305 private void processImport(DetailAST ast) { 306 final FullIdent name = FullIdent.createFullIdentBelow(ast); 307 imports.add(name.getText()); 308 } 309 310 /** 311 * Process type params (if any) for given class, enum or method. 312 * @param ast class, enum or method to process. 313 */ 314 private void processTypeParams(DetailAST ast) { 315 final DetailAST params = 316 ast.findFirstToken(TokenTypes.TYPE_PARAMETERS); 317 318 final Map<String, AbstractClassInfo> paramsMap = new HashMap<>(); 319 typeParams.push(paramsMap); 320 321 if (params != null) { 322 for (DetailAST child = params.getFirstChild(); 323 child != null; 324 child = child.getNextSibling()) { 325 if (child.getType() == TokenTypes.TYPE_PARAMETER) { 326 final DetailAST bounds = 327 child.findFirstToken(TokenTypes.TYPE_UPPER_BOUNDS); 328 if (bounds != null) { 329 final FullIdent name = 330 FullIdent.createFullIdentBelow(bounds); 331 final AbstractClassInfo classInfo = 332 createClassInfo(new Token(name), currentClassName); 333 final String alias = 334 child.findFirstToken(TokenTypes.IDENT).getText(); 335 paramsMap.put(alias, classInfo); 336 } 337 } 338 } 339 } 340 } 341 342 /** 343 * Processes class definition. 344 * @param ast class definition to process. 345 */ 346 private void processClass(DetailAST ast) { 347 final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT); 348 String innerClass = ident.getText(); 349 350 if (!currentClassName.isEmpty()) { 351 innerClass = "$" + innerClass; 352 } 353 currentClassName += innerClass; 354 processTypeParams(ast); 355 } 356 357 /** 358 * Returns current class. 359 * @return name of current class. 360 */ 361 protected final String getCurrentClassName() { 362 return currentClassName; 363 } 364 365 /** 366 * Creates class info for given name. 367 * @param name name of type. 368 * @param surroundingClass name of surrounding class. 369 * @return class info for given name. 370 */ 371 protected final AbstractClassInfo createClassInfo(final Token name, 372 final String surroundingClass) { 373 final AbstractClassInfo result; 374 final AbstractClassInfo classInfo = findClassAlias(name.getText()); 375 if (classInfo == null) { 376 result = new RegularClass(name, surroundingClass, this); 377 } 378 else { 379 result = new ClassAlias(name, classInfo); 380 } 381 return result; 382 } 383 384 /** 385 * Looking if a given name is alias. 386 * @param name given name 387 * @return ClassInfo for alias if it exists, null otherwise 388 * @noinspection WeakerAccess 389 */ 390 protected final AbstractClassInfo findClassAlias(final String name) { 391 AbstractClassInfo classInfo = null; 392 final Iterator<Map<String, AbstractClassInfo>> iterator = typeParams.descendingIterator(); 393 while (iterator.hasNext()) { 394 final Map<String, AbstractClassInfo> paramMap = iterator.next(); 395 classInfo = paramMap.get(name); 396 if (classInfo != null) { 397 break; 398 } 399 } 400 return classInfo; 401 } 402 403 /** 404 * Contains class's {@code Token}. 405 * @noinspection ProtectedInnerClass 406 */ 407 protected abstract static class AbstractClassInfo { 408 409 /** {@code FullIdent} associated with this class. */ 410 private final Token name; 411 412 /** 413 * Creates new instance of class information object. 414 * @param className token which represents class name. 415 */ 416 protected AbstractClassInfo(final Token className) { 417 if (className == null) { 418 throw new IllegalArgumentException( 419 "ClassInfo's name should be non-null"); 420 } 421 name = className; 422 } 423 424 /** 425 * Returns class associated with that object. 426 * @return {@code Class} associated with an object. 427 */ 428 // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon. 429 public abstract Class<?> getClazz(); 430 431 /** 432 * Gets class name. 433 * @return class name 434 */ 435 public final Token getName() { 436 return name; 437 } 438 439 } 440 441 /** Represents regular classes/enums. */ 442 private static final class RegularClass extends AbstractClassInfo { 443 444 /** Name of surrounding class. */ 445 private final String surroundingClass; 446 /** The check we use to resolve classes. */ 447 private final AbstractTypeAwareCheck check; 448 /** Is class loadable. */ 449 private boolean loadable = true; 450 /** {@code Class} object of this class if it's loadable. */ 451 private Class<?> classObj; 452 453 /** 454 * Creates new instance of of class information object. 455 * @param name {@code FullIdent} associated with new object. 456 * @param surroundingClass name of current surrounding class. 457 * @param check the check we use to load class. 458 */ 459 RegularClass(final Token name, 460 final String surroundingClass, 461 final AbstractTypeAwareCheck check) { 462 super(name); 463 this.surroundingClass = surroundingClass; 464 this.check = check; 465 } 466 467 @Override 468 public Class<?> getClazz() { 469 if (loadable && classObj == null) { 470 setClazz(check.tryLoadClass(getName(), surroundingClass)); 471 } 472 return classObj; 473 } 474 475 /** 476 * Associates {@code Class} with an object. 477 * @param clazz {@code Class} to associate with. 478 */ 479 private void setClazz(Class<?> clazz) { 480 classObj = clazz; 481 loadable = clazz != null; 482 } 483 484 @Override 485 public String toString() { 486 return "RegularClass[name=" + getName() 487 + ", in class='" + surroundingClass + '\'' 488 + ", check=" + check.hashCode() 489 + ", loadable=" + loadable 490 + ", class=" + classObj 491 + ']'; 492 } 493 494 } 495 496 /** Represents type param which is "alias" for real type. */ 497 private static class ClassAlias extends AbstractClassInfo { 498 499 /** Class information associated with the alias. */ 500 private final AbstractClassInfo classInfo; 501 502 /** 503 * Creates new instance of the class. 504 * @param name token which represents name of class alias. 505 * @param classInfo class information associated with the alias. 506 */ 507 ClassAlias(final Token name, AbstractClassInfo classInfo) { 508 super(name); 509 this.classInfo = classInfo; 510 } 511 512 @Override 513 public final Class<?> getClazz() { 514 return classInfo.getClazz(); 515 } 516 517 @Override 518 public String toString() { 519 return "ClassAlias[alias " + getName() + " for " + classInfo.getName() + "]"; 520 } 521 522 } 523 524 /** 525 * Represents text element with location in the text. 526 * @noinspection ProtectedInnerClass 527 */ 528 protected static class Token { 529 530 /** Token's column number. */ 531 private final int columnNo; 532 /** Token's line number. */ 533 private final int lineNo; 534 /** Token's text. */ 535 private final String text; 536 537 /** 538 * Creates token. 539 * @param text token's text 540 * @param lineNo token's line number 541 * @param columnNo token's column number 542 */ 543 public Token(String text, int lineNo, int columnNo) { 544 this.text = text; 545 this.lineNo = lineNo; 546 this.columnNo = columnNo; 547 } 548 549 /** 550 * Converts FullIdent to Token. 551 * @param fullIdent full ident to convert. 552 */ 553 public Token(FullIdent fullIdent) { 554 text = fullIdent.getText(); 555 lineNo = fullIdent.getLineNo(); 556 columnNo = fullIdent.getColumnNo(); 557 } 558 559 /** 560 * Gets line number of the token. 561 * @return line number of the token 562 */ 563 public int getLineNo() { 564 return lineNo; 565 } 566 567 /** 568 * Gets column number of the token. 569 * @return column number of the token 570 */ 571 public int getColumnNo() { 572 return columnNo; 573 } 574 575 /** 576 * Gets text of the token. 577 * @return text of the token 578 */ 579 public String getText() { 580 return text; 581 } 582 583 @Override 584 public String toString() { 585 return "Token[" + text + "(" + lineNo 586 + "x" + columnNo + ")]"; 587 } 588 589 } 590 591}