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