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