Coverage Report - com.puppycrawl.tools.checkstyle.Checker
 
Classes in this File Line Coverage Branch Coverage Complexity
Checker
86%
169/196
70%
66/94
3.154
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  Oliver Burn
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 package com.puppycrawl.tools.checkstyle;
 20  
 
 21  
 import com.google.common.collect.Lists;
 22  
 import com.google.common.collect.Sets;
 23  
 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
 24  
 import com.puppycrawl.tools.checkstyle.api.AuditListener;
 25  
 import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
 26  
 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
 27  
 import com.puppycrawl.tools.checkstyle.api.Configuration;
 28  
 import com.puppycrawl.tools.checkstyle.api.Context;
 29  
 import com.puppycrawl.tools.checkstyle.api.FastStack;
 30  
 import com.puppycrawl.tools.checkstyle.api.FileSetCheck;
 31  
 import com.puppycrawl.tools.checkstyle.api.FileText;
 32  
 import com.puppycrawl.tools.checkstyle.api.Filter;
 33  
 import com.puppycrawl.tools.checkstyle.api.FilterSet;
 34  
 import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
 35  
 import com.puppycrawl.tools.checkstyle.api.MessageDispatcher;
 36  
 import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
 37  
 import com.puppycrawl.tools.checkstyle.api.SeverityLevelCounter;
 38  
 import com.puppycrawl.tools.checkstyle.api.Utils;
 39  
 
 40  
 import java.io.File;
 41  
 import java.io.FileNotFoundException;
 42  
 import java.io.IOException;
 43  
 import java.io.UnsupportedEncodingException;
 44  
 import java.nio.charset.Charset;
 45  
 import java.util.List;
 46  
 import java.util.Locale;
 47  
 import java.util.Set;
 48  
 import java.util.SortedSet;
 49  
 import java.util.StringTokenizer;
 50  
 
 51  
 /**
 52  
  * This class provides the functionality to check a set of files.
 53  
  * @author Oliver Burn
 54  
  * @author <a href="mailto:stephane.bailliez@wanadoo.fr">Stephane Bailliez</a>
 55  
  * @author lkuehne
 56  
  */
 57  
 public class Checker extends AutomaticBean implements MessageDispatcher
 58  
 {
 59  
     /** maintains error count */
 60  602
     private final SeverityLevelCounter mCounter = new SeverityLevelCounter(
 61  
             SeverityLevel.ERROR);
 62  
 
 63  
     /** vector of listeners */
 64  602
     private final List<AuditListener> mListeners = Lists.newArrayList();
 65  
 
 66  
     /** vector of fileset checks */
 67  602
     private final List<FileSetCheck> mFileSetChecks = Lists.newArrayList();
 68  
 
 69  
     /** class loader to resolve classes with. **/
 70  602
     private ClassLoader mLoader = Thread.currentThread()
 71  
             .getContextClassLoader();
 72  
 
 73  
     /** the basedir to strip off in filenames */
 74  
     private String mBasedir;
 75  
 
 76  
     /** locale country to report messages  **/
 77  602
     private String mLocaleCountry = Locale.getDefault().getCountry();
 78  
     /** locale language to report messages  **/
 79  602
     private String mLocaleLanguage = Locale.getDefault().getLanguage();
 80  
 
 81  
     /** The factory for instantiating submodules */
 82  
     private ModuleFactory mModuleFactory;
 83  
 
 84  
     /** The classloader used for loading Checkstyle module classes. */
 85  
     private ClassLoader mModuleClassLoader;
 86  
 
 87  
     /** the context of all child components */
 88  
     private Context mChildContext;
 89  
 
 90  
     /** The audit event filters */
 91  602
     private final FilterSet mFilters = new FilterSet();
 92  
 
 93  
     /**
 94  
      * The severity level of any violations found by submodules.
 95  
      * The value of this property is passed to submodules via
 96  
      * contextualize().
 97  
      *
 98  
      * Note: Since the Checker is merely a container for modules
 99  
      * it does not make sense to implement logging functionality
 100  
      * here. Consequently Checker does not extend AbstractViolationReporter,
 101  
      * leading to a bit of duplicated code for severity level setting.
 102  
      */
 103  602
     private SeverityLevel mSeverityLevel = SeverityLevel.ERROR;
 104  
 
 105  
     /** Name of a charset */
 106  602
     private String mCharset = System.getProperty("file.encoding", "UTF-8");
 107  
 
 108  
     /**
 109  
      * Creates a new <code>Checker</code> instance.
 110  
      * The instance needs to be contextualized and configured.
 111  
      *
 112  
      * @throws CheckstyleException if an error occurs
 113  
      */
 114  
     public Checker() throws CheckstyleException
 115  602
     {
 116  602
         addListener(mCounter);
 117  602
     }
 118  
 
 119  
     @Override
 120  
     public void finishLocalSetup() throws CheckstyleException
 121  
     {
 122  593
         final Locale locale = new Locale(mLocaleLanguage, mLocaleCountry);
 123  593
         LocalizedMessage.setLocale(locale);
 124  
 
 125  593
         if (mModuleFactory == null) {
 126  
 
 127  593
             if (mModuleClassLoader == null) {
 128  0
                 throw new CheckstyleException(
 129  
                         "if no custom moduleFactory is set, "
 130  
                                 + "moduleClassLoader must be specified");
 131  
             }
 132  
 
 133  593
             final Set<String> packageNames = PackageNamesLoader
 134  
                     .getPackageNames(mModuleClassLoader);
 135  593
             mModuleFactory = new PackageObjectFactory(packageNames,
 136  
                     mModuleClassLoader);
 137  
         }
 138  
 
 139  593
         final DefaultContext context = new DefaultContext();
 140  593
         context.add("charset", mCharset);
 141  593
         context.add("classLoader", mLoader);
 142  593
         context.add("moduleFactory", mModuleFactory);
 143  593
         context.add("severity", mSeverityLevel.getName());
 144  593
         context.add("basedir", mBasedir);
 145  593
         mChildContext = context;
 146  593
     }
 147  
 
 148  
     @Override
 149  
     protected void setupChild(Configuration aChildConf)
 150  
         throws CheckstyleException
 151  
     {
 152  609
         final String name = aChildConf.getName();
 153  
         try {
 154  609
             final Object child = mModuleFactory.createModule(name);
 155  609
             if (child instanceof AutomaticBean) {
 156  609
                 final AutomaticBean bean = (AutomaticBean) child;
 157  609
                 bean.contextualize(mChildContext);
 158  609
                 bean.configure(aChildConf);
 159  
             }
 160  599
             if (child instanceof FileSetCheck) {
 161  583
                 final FileSetCheck fsc = (FileSetCheck) child;
 162  583
                 addFileSetCheck(fsc);
 163  583
             }
 164  16
             else if (child instanceof Filter) {
 165  16
                 final Filter filter = (Filter) child;
 166  16
                 addFilter(filter);
 167  16
             }
 168  0
             else if (child instanceof AuditListener) {
 169  0
                 final AuditListener listener = (AuditListener) child;
 170  0
                 addListener(listener);
 171  0
             }
 172  
             else {
 173  0
                 throw new CheckstyleException(name
 174  
                         + " is not allowed as a child in Checker");
 175  
             }
 176  
         }
 177  10
         catch (final Exception ex) {
 178  
             // TODO i18n
 179  10
             throw new CheckstyleException("cannot initialize module " + name
 180  
                     + " - " + ex.getMessage(), ex);
 181  599
         }
 182  599
     }
 183  
 
 184  
     /**
 185  
      * Adds a FileSetCheck to the list of FileSetChecks
 186  
      * that is executed in process().
 187  
      * @param aFileSetCheck the additional FileSetCheck
 188  
      */
 189  
     public void addFileSetCheck(FileSetCheck aFileSetCheck)
 190  
     {
 191  583
         aFileSetCheck.setMessageDispatcher(this);
 192  583
         mFileSetChecks.add(aFileSetCheck);
 193  583
     }
 194  
 
 195  
     /**
 196  
      * Adds a filter to the end of the audit event filter chain.
 197  
      * @param aFilter the additional filter
 198  
      */
 199  
     public void addFilter(Filter aFilter)
 200  
     {
 201  20
         mFilters.addFilter(aFilter);
 202  20
     }
 203  
 
 204  
     /**
 205  
      * Removes filter.
 206  
      * @param aFilter filter to remove.
 207  
      */
 208  
     public void removeFilter(Filter aFilter)
 209  
     {
 210  1
         mFilters.removeFilter(aFilter);
 211  1
     }
 212  
 
 213  
     /** Cleans up the object. **/
 214  
     public void destroy()
 215  
     {
 216  584
         mListeners.clear();
 217  584
         mFilters.clear();
 218  584
     }
 219  
 
 220  
     /**
 221  
      * Add the listener that will be used to receive events from the audit.
 222  
      * @param aListener the nosy thing
 223  
      */
 224  
     public final void addListener(AuditListener aListener)
 225  
     {
 226  1189
         mListeners.add(aListener);
 227  1189
     }
 228  
 
 229  
     /**
 230  
      * Removes a given listener.
 231  
      * @param aListener a listener to remove
 232  
      */
 233  
     public void removeListener(AuditListener aListener)
 234  
     {
 235  1
         mListeners.remove(aListener);
 236  1
     }
 237  
 
 238  
     /**
 239  
      * Processes a set of files with all FileSetChecks.
 240  
      * Once this is done, it is highly recommended to call for
 241  
      * the destroy method to close and remove the listeners.
 242  
      * @param aFiles the list of files to be audited.
 243  
      * @return the total number of errors found
 244  
      * @see #destroy()
 245  
      */
 246  
     public int process(List<File> aFiles)
 247  
     {
 248  
         // Prepare to start
 249  583
         fireAuditStarted();
 250  583
         for (final FileSetCheck fsc : mFileSetChecks) {
 251  583
             fsc.beginProcessing(mCharset);
 252  
         }
 253  
 
 254  
         // Process each file
 255  583
         for (final File f : aFiles) {
 256  586
             final String fileName = f.getAbsolutePath();
 257  586
             fireFileStarted(fileName);
 258  586
             final SortedSet<LocalizedMessage> fileMessages = Sets.newTreeSet();
 259  
             try {
 260  586
                 final FileText theText = new FileText(f.getAbsoluteFile(),
 261  
                         mCharset);
 262  585
                 for (final FileSetCheck fsc : mFileSetChecks) {
 263  585
                     fileMessages.addAll(fsc.process(f, theText));
 264  
                 }
 265  
             }
 266  1
             catch (final FileNotFoundException fnfe) {
 267  1
                 Utils.getExceptionLogger().debug(
 268  
                         "FileNotFoundException occured.", fnfe);
 269  1
                 fileMessages.add(new LocalizedMessage(0,
 270  
                         Defn.CHECKSTYLE_BUNDLE, "general.fileNotFound", null,
 271  
                         null, this.getClass(), null));
 272  
             }
 273  0
             catch (final IOException ioe) {
 274  0
                 Utils.getExceptionLogger().debug("IOException occured.", ioe);
 275  0
                 fileMessages.add(new LocalizedMessage(0,
 276  
                         Defn.CHECKSTYLE_BUNDLE, "general.exception",
 277  
                         new String[] {ioe.getMessage()}, null, this.getClass(),
 278  
                         null));
 279  586
             }
 280  586
             fireErrors(fileName, fileMessages);
 281  586
             fireFileFinished(fileName);
 282  586
         }
 283  
 
 284  
         // Finish up
 285  583
         for (final FileSetCheck fsc : mFileSetChecks) {
 286  
             // They may also log!!!
 287  583
             fsc.finishProcessing();
 288  583
             fsc.destroy();
 289  
         }
 290  
 
 291  583
         final int errorCount = mCounter.getCount();
 292  583
         fireAuditFinished();
 293  583
         return errorCount;
 294  
     }
 295  
 
 296  
     /**
 297  
      * Create a stripped down version of a filename.
 298  
      * @param aFileName the original filename
 299  
      * @return the filename where an initial prefix of basedir is stripped
 300  
      */
 301  
     private String getStrippedFileName(final String aFileName)
 302  
     {
 303  1793
         return Utils.getStrippedFileName(mBasedir, aFileName);
 304  
     }
 305  
 
 306  
     /** @param aBasedir the base directory to strip off in filenames */
 307  
     public void setBasedir(String aBasedir)
 308  
     {
 309  
         // we use getAbsolutePath() instead of getCanonicalPath()
 310  
         // because normalize() removes all . and .. so path
 311  
         // will be canonical by default.
 312  2
         mBasedir = normalize(aBasedir);
 313  2
     }
 314  
 
 315  
     /**
 316  
      * &quot;normalize&quot; the given absolute path.
 317  
      *
 318  
      * <p>This includes:
 319  
      * <ul>
 320  
      *   <li>Uppercase the drive letter if there is one.</li>
 321  
      *   <li>Remove redundant slashes after the drive spec.</li>
 322  
      *   <li>resolve all ./, .\, ../ and ..\ sequences.</li>
 323  
      *   <li>DOS style paths that start with a drive letter will have
 324  
      *     \ as the separator.</li>
 325  
      * </ul>
 326  
      *
 327  
      * @param aPath a path for &quot;normalizing&quot;
 328  
      * @return &quot;normalized&quot; file name
 329  
      * @throws java.lang.NullPointerException if the file path is
 330  
      * equal to null.
 331  
      */
 332  
     public String normalize(String aPath)
 333  
     {
 334  2
         final String osName = System.getProperty("os.name").toLowerCase(
 335  
                 Locale.US);
 336  2
         final boolean onNetWare = (osName.indexOf("netware") > -1);
 337  
 
 338  2
         String path = aPath.replace('/', File.separatorChar).replace('\\',
 339  
             File.separatorChar);
 340  
 
 341  
         // make sure we are dealing with an absolute path
 342  2
         final int colon = path.indexOf(":");
 343  
 
 344  2
         if (!onNetWare) {
 345  2
             if (!path.startsWith(File.separator)
 346  
                 && !((path.length() >= 2)
 347  
                      && Character.isLetter(path.charAt(0)) && (colon == 1)))
 348  
             {
 349  0
                 final String msg = path + " is not an absolute path";
 350  0
                 throw new IllegalArgumentException(msg);
 351  
             }
 352  
         }
 353  
         else {
 354  0
             if (!path.startsWith(File.separator) && (colon == -1)) {
 355  0
                 final String msg = path + " is not an absolute path";
 356  0
                 throw new IllegalArgumentException(msg);
 357  
             }
 358  
         }
 359  
 
 360  2
         boolean dosWithDrive = false;
 361  2
         String root = null;
 362  
         // Eliminate consecutive slashes after the drive spec
 363  2
         if ((!onNetWare && (path.length() >= 2)
 364  
              && Character.isLetter(path.charAt(0)) && (path.charAt(1) == ':'))
 365  
             || (onNetWare && (colon > -1)))
 366  
         {
 367  
 
 368  1
             dosWithDrive = true;
 369  
 
 370  1
             final char[] ca = path.replace('/', '\\').toCharArray();
 371  1
             final StringBuffer sbRoot = new StringBuffer();
 372  2
             for (int i = 0; i < colon; i++) {
 373  1
                 sbRoot.append(Character.toUpperCase(ca[i]));
 374  
             }
 375  1
             sbRoot.append(':');
 376  1
             if (colon + 1 < path.length()) {
 377  1
                 sbRoot.append(File.separatorChar);
 378  
             }
 379  1
             root = sbRoot.toString();
 380  
 
 381  
             // Eliminate consecutive slashes after the drive spec
 382  1
             final StringBuffer sbPath = new StringBuffer();
 383  14
             for (int i = colon + 1; i < ca.length; i++) {
 384  13
                 if ((ca[i] != '\\') || ((ca[i] == '\\') && (ca[i - 1] != '\\')))
 385  
                 {
 386  13
                     sbPath.append(ca[i]);
 387  
                 }
 388  
             }
 389  1
             path = sbPath.toString().replace('\\', File.separatorChar);
 390  
 
 391  1
         }
 392  
         else {
 393  1
             if (path.length() == 1) {
 394  0
                 root = File.separator;
 395  0
                 path = "";
 396  
             }
 397  1
             else if (path.charAt(1) == File.separatorChar) {
 398  
                 // UNC drive
 399  0
                 root = File.separator + File.separator;
 400  0
                 path = path.substring(2);
 401  
             }
 402  
             else {
 403  1
                 root = File.separator;
 404  1
                 path = path.substring(1);
 405  
             }
 406  
         }
 407  
 
 408  2
         final FastStack<String> s = FastStack.newInstance();
 409  2
         s.push(root);
 410  2
         final StringTokenizer tok = new StringTokenizer(path, File.separator);
 411  22
         while (tok.hasMoreTokens()) {
 412  20
             final String thisToken = tok.nextToken();
 413  20
             if (".".equals(thisToken)) {
 414  2
                 continue;
 415  
             }
 416  18
             else if ("..".equals(thisToken)) {
 417  2
                 if (s.size() < 2) {
 418  0
                     throw new IllegalArgumentException("Cannot resolve path "
 419  
                             + aPath);
 420  
                 }
 421  2
                 s.pop();
 422  
             }
 423  
             else { // plain component
 424  16
                 s.push(thisToken);
 425  
             }
 426  18
         }
 427  
 
 428  2
         final StringBuffer sb = new StringBuffer();
 429  18
         for (int i = 0; i < s.size(); i++) {
 430  16
             if (i > 1) {
 431  
                 // not before the filesystem root and not after it, since root
 432  
                 // already contains one
 433  12
                 sb.append(File.separatorChar);
 434  
             }
 435  16
             sb.append(s.peek(i));
 436  
         }
 437  
 
 438  2
         path = sb.toString();
 439  2
         if (dosWithDrive) {
 440  1
             path = path.replace('/', '\\');
 441  
         }
 442  2
         return path;
 443  
     }
 444  
 
 445  
     /** @return the base directory property used in unit-test. */
 446  
     public final String getBasedir()
 447  
     {
 448  2
         return mBasedir;
 449  
     }
 450  
 
 451  
     /** notify all listeners about the audit start */
 452  
     protected void fireAuditStarted()
 453  
     {
 454  586
         final AuditEvent evt = new AuditEvent(this);
 455  586
         for (final AuditListener listener : mListeners) {
 456  1170
             listener.auditStarted(evt);
 457  
         }
 458  586
     }
 459  
 
 460  
     /** notify all listeners about the audit end */
 461  
     protected void fireAuditFinished()
 462  
     {
 463  586
         final AuditEvent evt = new AuditEvent(this);
 464  586
         for (final AuditListener listener : mListeners) {
 465  1170
             listener.auditFinished(evt);
 466  
         }
 467  586
     }
 468  
 
 469  
     /**
 470  
      * Notify all listeners about the beginning of a file audit.
 471  
      *
 472  
      * @param aFileName
 473  
      *            the file to be audited
 474  
      */
 475  
     public void fireFileStarted(String aFileName)
 476  
     {
 477  597
         final String stripped = getStrippedFileName(aFileName);
 478  597
         final AuditEvent evt = new AuditEvent(this, stripped);
 479  597
         for (final AuditListener listener : mListeners) {
 480  1192
             listener.fileStarted(evt);
 481  
         }
 482  597
     }
 483  
 
 484  
     /**
 485  
      * Notify all listeners about the end of a file audit.
 486  
      *
 487  
      * @param aFileName
 488  
      *            the audited file
 489  
      */
 490  
     public void fireFileFinished(String aFileName)
 491  
     {
 492  597
         final String stripped = getStrippedFileName(aFileName);
 493  597
         final AuditEvent evt = new AuditEvent(this, stripped);
 494  597
         for (final AuditListener listener : mListeners) {
 495  1192
             listener.fileFinished(evt);
 496  
         }
 497  597
     }
 498  
 
 499  
     /**
 500  
      * notify all listeners about the errors in a file.
 501  
      *
 502  
      * @param aFileName the audited file
 503  
      * @param aErrors the audit errors from the file
 504  
      */
 505  
     public void fireErrors(String aFileName,
 506  
         SortedSet<LocalizedMessage> aErrors)
 507  
     {
 508  599
         final String stripped = getStrippedFileName(aFileName);
 509  599
         for (final LocalizedMessage element : aErrors) {
 510  3430
             final AuditEvent evt = new AuditEvent(this, stripped, element);
 511  3430
             if (mFilters.accept(evt)) {
 512  3389
                 for (final AuditListener listener : mListeners) {
 513  6774
                     listener.addError(evt);
 514  
                 }
 515  
             }
 516  3430
         }
 517  599
     }
 518  
 
 519  
     /**
 520  
      * Sets the factory for creating submodules.
 521  
      *
 522  
      * @param aModuleFactory the factory for creating FileSetChecks
 523  
      */
 524  
     public void setModuleFactory(ModuleFactory aModuleFactory)
 525  
     {
 526  0
         mModuleFactory = aModuleFactory;
 527  0
     }
 528  
 
 529  
     /** @param aLocaleCountry the country to report messages  **/
 530  
     public void setLocaleCountry(String aLocaleCountry)
 531  
     {
 532  593
         mLocaleCountry = aLocaleCountry;
 533  593
     }
 534  
 
 535  
     /** @param aLocaleLanguage the language to report messages  **/
 536  
     public void setLocaleLanguage(String aLocaleLanguage)
 537  
     {
 538  593
         mLocaleLanguage = aLocaleLanguage;
 539  593
     }
 540  
 
 541  
     /**
 542  
      * Sets the severity level.  The string should be one of the names
 543  
      * defined in the <code>SeverityLevel</code> class.
 544  
      *
 545  
      * @param aSeverity  The new severity level
 546  
      * @see SeverityLevel
 547  
      */
 548  
     public final void setSeverity(String aSeverity)
 549  
     {
 550  0
         mSeverityLevel = SeverityLevel.getInstance(aSeverity);
 551  0
     }
 552  
 
 553  
     /**
 554  
      * Sets the classloader that is used to contextualize filesetchecks.
 555  
      * Some Check implementations will use that classloader to improve the
 556  
      * quality of their reports, e.g. to load a class and then analyze it via
 557  
      * reflection.
 558  
      * @param aLoader the new classloader
 559  
      */
 560  
     public final void setClassloader(ClassLoader aLoader)
 561  
     {
 562  0
         mLoader = aLoader;
 563  0
     }
 564  
 
 565  
     /**
 566  
      * Sets the classloader used to load Checkstyle core and custom module
 567  
      * classes when the module tree is being built up.
 568  
      * If no custom ModuleFactory is being set for the Checker module then
 569  
      * this module classloader must be specified.
 570  
      * @param aModuleClassLoader the classloader used to load module classes
 571  
      */
 572  
     public final void setModuleClassLoader(ClassLoader aModuleClassLoader)
 573  
     {
 574  593
         mModuleClassLoader = aModuleClassLoader;
 575  593
     }
 576  
 
 577  
     /**
 578  
      * Sets a named charset.
 579  
      * @param aCharset the name of a charset
 580  
      * @throws UnsupportedEncodingException if aCharset is unsupported.
 581  
      */
 582  
     public void setCharset(String aCharset)
 583  
         throws UnsupportedEncodingException
 584  
     {
 585  527
         if (!Charset.isSupported(aCharset)) {
 586  0
             final String message = "unsupported charset: '" + aCharset + "'";
 587  0
             throw new UnsupportedEncodingException(message);
 588  
         }
 589  527
         mCharset = aCharset;
 590  527
     }
 591  
 }