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