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