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