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