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; 021 022import java.io.File; 023import java.io.IOException; 024import java.io.UnsupportedEncodingException; 025import java.nio.charset.Charset; 026import java.util.List; 027import java.util.Locale; 028import java.util.Set; 029import java.util.SortedSet; 030 031import org.apache.commons.lang3.ArrayUtils; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034 035import com.google.common.collect.Lists; 036import com.google.common.collect.Sets; 037import com.puppycrawl.tools.checkstyle.api.AuditEvent; 038import com.puppycrawl.tools.checkstyle.api.AuditListener; 039import com.puppycrawl.tools.checkstyle.api.AutomaticBean; 040import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 041import com.puppycrawl.tools.checkstyle.api.Configuration; 042import com.puppycrawl.tools.checkstyle.api.Context; 043import com.puppycrawl.tools.checkstyle.api.FileSetCheck; 044import com.puppycrawl.tools.checkstyle.api.FileText; 045import com.puppycrawl.tools.checkstyle.api.Filter; 046import com.puppycrawl.tools.checkstyle.api.FilterSet; 047import com.puppycrawl.tools.checkstyle.api.LocalizedMessage; 048import com.puppycrawl.tools.checkstyle.api.MessageDispatcher; 049import com.puppycrawl.tools.checkstyle.api.SeverityLevel; 050import com.puppycrawl.tools.checkstyle.api.SeverityLevelCounter; 051import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 052 053/** 054 * This class provides the functionality to check a set of files. 055 * @author Oliver Burn 056 * @author <a href="mailto:stephane.bailliez@wanadoo.fr">Stephane Bailliez</a> 057 * @author lkuehne 058 */ 059public class Checker extends AutomaticBean implements MessageDispatcher { 060 /** Logger for Checker. */ 061 private static final Log LOG = LogFactory.getLog(Checker.class); 062 063 /** Maintains error count. */ 064 private final SeverityLevelCounter counter = new SeverityLevelCounter( 065 SeverityLevel.ERROR); 066 067 /** Vector of listeners. */ 068 private final List<AuditListener> listeners = Lists.newArrayList(); 069 070 /** Vector of fileset checks. */ 071 private final List<FileSetCheck> fileSetChecks = Lists.newArrayList(); 072 073 /** Class loader to resolve classes with. **/ 074 private ClassLoader classLoader = Thread.currentThread() 075 .getContextClassLoader(); 076 077 /** The basedir to strip off in file names. */ 078 private String basedir; 079 080 /** Locale country to report messages . **/ 081 private String localeCountry = Locale.getDefault().getCountry(); 082 /** Locale language to report messages . **/ 083 private String localeLanguage = Locale.getDefault().getLanguage(); 084 085 /** The factory for instantiating submodules. */ 086 private ModuleFactory moduleFactory; 087 088 /** The classloader used for loading Checkstyle module classes. */ 089 private ClassLoader moduleClassLoader; 090 091 /** The context of all child components. */ 092 private Context childContext; 093 094 /** The audit event filters. */ 095 private final FilterSet filters = new FilterSet(); 096 097 /** The file extensions that are accepted. */ 098 private String[] fileExtensions = ArrayUtils.EMPTY_STRING_ARRAY; 099 100 /** 101 * The severity level of any violations found by submodules. 102 * The value of this property is passed to submodules via 103 * contextualize(). 104 * 105 * <p>Note: Since the Checker is merely a container for modules 106 * it does not make sense to implement logging functionality 107 * here. Consequently Checker does not extend AbstractViolationReporter, 108 * leading to a bit of duplicated code for severity level setting. 109 */ 110 private SeverityLevel severityLevel = SeverityLevel.ERROR; 111 112 /** Name of a charset. */ 113 private String charset = System.getProperty("file.encoding", "UTF-8"); 114 115 /** 116 * Creates a new {@code Checker} instance. 117 * The instance needs to be contextualized and configured. 118 */ 119 public Checker() { 120 addListener(counter); 121 } 122 123 @Override 124 public void finishLocalSetup() throws CheckstyleException { 125 final Locale locale = new Locale(localeLanguage, localeCountry); 126 LocalizedMessage.setLocale(locale); 127 128 if (moduleFactory == null) { 129 130 if (moduleClassLoader == null) { 131 throw new CheckstyleException( 132 "if no custom moduleFactory is set, " 133 + "moduleClassLoader must be specified"); 134 } 135 136 final Set<String> packageNames = PackageNamesLoader 137 .getPackageNames(moduleClassLoader); 138 moduleFactory = new PackageObjectFactory(packageNames, 139 moduleClassLoader); 140 } 141 142 final DefaultContext context = new DefaultContext(); 143 context.add("charset", charset); 144 context.add("classLoader", classLoader); 145 context.add("moduleFactory", moduleFactory); 146 context.add("severity", severityLevel.getName()); 147 context.add("basedir", basedir); 148 childContext = context; 149 } 150 151 @Override 152 protected void setupChild(Configuration childConf) 153 throws CheckstyleException { 154 final String name = childConf.getName(); 155 final Object child; 156 157 try { 158 child = moduleFactory.createModule(name); 159 160 if (child instanceof AutomaticBean) { 161 final AutomaticBean bean = (AutomaticBean) child; 162 bean.contextualize(childContext); 163 bean.configure(childConf); 164 } 165 } 166 catch (final CheckstyleException ex) { 167 throw new CheckstyleException("cannot initialize module " + name 168 + " - " + ex.getMessage(), ex); 169 } 170 if (child instanceof FileSetCheck) { 171 final FileSetCheck fsc = (FileSetCheck) child; 172 fsc.init(); 173 addFileSetCheck(fsc); 174 } 175 else if (child instanceof Filter) { 176 final Filter filter = (Filter) child; 177 addFilter(filter); 178 } 179 else if (child instanceof AuditListener) { 180 final AuditListener listener = (AuditListener) child; 181 addListener(listener); 182 } 183 else { 184 throw new CheckstyleException(name 185 + " is not allowed as a child in Checker"); 186 } 187 } 188 189 /** 190 * Adds a FileSetCheck to the list of FileSetChecks 191 * that is executed in process(). 192 * @param fileSetCheck the additional FileSetCheck 193 */ 194 public void addFileSetCheck(FileSetCheck fileSetCheck) { 195 fileSetCheck.setMessageDispatcher(this); 196 fileSetChecks.add(fileSetCheck); 197 } 198 199 /** 200 * Adds a filter to the end of the audit event filter chain. 201 * @param filter the additional filter 202 */ 203 public void addFilter(Filter filter) { 204 filters.addFilter(filter); 205 } 206 207 /** 208 * Removes filter. 209 * @param filter filter to remove. 210 */ 211 public void removeFilter(Filter filter) { 212 filters.removeFilter(filter); 213 } 214 215 /** Cleans up the object. **/ 216 public void destroy() { 217 listeners.clear(); 218 filters.clear(); 219 } 220 221 /** 222 * Add the listener that will be used to receive events from the audit. 223 * @param listener the nosy thing 224 */ 225 public final void addListener(AuditListener listener) { 226 listeners.add(listener); 227 } 228 229 /** 230 * Removes a given listener. 231 * @param listener a listener to remove 232 */ 233 public void removeListener(AuditListener listener) { 234 listeners.remove(listener); 235 } 236 237 /** 238 * Processes a set of files with all FileSetChecks. 239 * Once this is done, it is highly recommended to call for 240 * the destroy method to close and remove the listeners. 241 * @param files the list of files to be audited. 242 * @return the total number of errors found 243 * @throws CheckstyleException if error condition within Checkstyle occurs 244 * @see #destroy() 245 */ 246 public int process(List<File> files) throws CheckstyleException { 247 // Prepare to start 248 fireAuditStarted(); 249 for (final FileSetCheck fsc : fileSetChecks) { 250 fsc.beginProcessing(charset); 251 } 252 253 // Process each file 254 for (final File file : files) { 255 if (!CommonUtils.matchesFileExtension(file, fileExtensions)) { 256 continue; 257 } 258 final String fileName = file.getAbsolutePath(); 259 fireFileStarted(fileName); 260 final SortedSet<LocalizedMessage> fileMessages = Sets.newTreeSet(); 261 try { 262 final FileText theText = new FileText(file.getAbsoluteFile(), 263 charset); 264 for (final FileSetCheck fsc : fileSetChecks) { 265 fileMessages.addAll(fsc.process(file, theText)); 266 } 267 } 268 catch (final IOException ioe) { 269 LOG.debug("IOException occurred.", ioe); 270 fileMessages.add(new LocalizedMessage(0, 271 Definitions.CHECKSTYLE_BUNDLE, "general.exception", 272 new String[] {ioe.getMessage()}, null, getClass(), 273 null)); 274 } 275 fireErrors(fileName, fileMessages); 276 fireFileFinished(fileName); 277 } 278 279 // Finish up 280 for (final FileSetCheck fsc : fileSetChecks) { 281 // It may also log!!! 282 fsc.finishProcessing(); 283 } 284 285 for (final FileSetCheck fsc : fileSetChecks) { 286 // It may also log!!! 287 fsc.destroy(); 288 } 289 290 final int errorCount = counter.getCount(); 291 fireAuditFinished(); 292 return errorCount; 293 } 294 295 /** 296 * Sets base directory. 297 * @param basedir the base directory to strip off in file names 298 */ 299 public void setBasedir(String basedir) { 300 this.basedir = basedir; 301 } 302 303 /** Notify all listeners about the audit start. */ 304 void fireAuditStarted() { 305 final AuditEvent evt = new AuditEvent(this); 306 for (final AuditListener listener : listeners) { 307 listener.auditStarted(evt); 308 } 309 } 310 311 /** Notify all listeners about the audit end. */ 312 void fireAuditFinished() { 313 final AuditEvent evt = new AuditEvent(this); 314 for (final AuditListener listener : listeners) { 315 listener.auditFinished(evt); 316 } 317 } 318 319 /** 320 * Notify all listeners about the beginning of a file audit. 321 * 322 * @param fileName 323 * the file to be audited 324 */ 325 @Override 326 public void fireFileStarted(String fileName) { 327 final String stripped = CommonUtils.relativizeAndNormalizePath(basedir, fileName); 328 final AuditEvent evt = new AuditEvent(this, stripped); 329 for (final AuditListener listener : listeners) { 330 listener.fileStarted(evt); 331 } 332 } 333 334 /** 335 * Notify all listeners about the end of a file audit. 336 * 337 * @param fileName 338 * the audited file 339 */ 340 @Override 341 public void fireFileFinished(String fileName) { 342 final String stripped = CommonUtils.relativizeAndNormalizePath(basedir, fileName); 343 final AuditEvent evt = new AuditEvent(this, stripped); 344 for (final AuditListener listener : listeners) { 345 listener.fileFinished(evt); 346 } 347 } 348 349 /** 350 * Notify all listeners about the errors in a file. 351 * 352 * @param fileName the audited file 353 * @param errors the audit errors from the file 354 */ 355 @Override 356 public void fireErrors(String fileName, SortedSet<LocalizedMessage> errors) { 357 final String stripped = CommonUtils.relativizeAndNormalizePath(basedir, fileName); 358 for (final LocalizedMessage element : errors) { 359 final AuditEvent evt = new AuditEvent(this, stripped, element); 360 if (filters.accept(evt)) { 361 for (final AuditListener listener : listeners) { 362 listener.addError(evt); 363 } 364 } 365 } 366 } 367 368 /** 369 * Sets the file extensions that identify the files that pass the 370 * filter of this FileSetCheck. 371 * @param extensions the set of file extensions. A missing 372 * initial '.' character of an extension is automatically added. 373 */ 374 public final void setFileExtensions(String... extensions) { 375 if (extensions == null) { 376 fileExtensions = null; 377 return; 378 } 379 380 fileExtensions = new String[extensions.length]; 381 for (int i = 0; i < extensions.length; i++) { 382 final String extension = extensions[i]; 383 if (CommonUtils.startsWithChar(extension, '.')) { 384 fileExtensions[i] = extension; 385 } 386 else { 387 fileExtensions[i] = "." + extension; 388 } 389 } 390 } 391 392 /** 393 * Sets the factory for creating submodules. 394 * 395 * @param moduleFactory the factory for creating FileSetChecks 396 */ 397 public void setModuleFactory(ModuleFactory moduleFactory) { 398 this.moduleFactory = moduleFactory; 399 } 400 401 /** 402 * Sets locale country. 403 * @param localeCountry the country to report messages 404 */ 405 public void setLocaleCountry(String localeCountry) { 406 this.localeCountry = localeCountry; 407 } 408 409 /** 410 * Sets locale language. 411 * @param localeLanguage the language to report messages 412 */ 413 public void setLocaleLanguage(String localeLanguage) { 414 this.localeLanguage = localeLanguage; 415 } 416 417 /** 418 * Sets the severity level. The string should be one of the names 419 * defined in the {@code SeverityLevel} class. 420 * 421 * @param severity The new severity level 422 * @see SeverityLevel 423 */ 424 public final void setSeverity(String severity) { 425 severityLevel = SeverityLevel.getInstance(severity); 426 } 427 428 /** 429 * Sets the classloader that is used to contextualize fileset checks. 430 * Some Check implementations will use that classloader to improve the 431 * quality of their reports, e.g. to load a class and then analyze it via 432 * reflection. 433 * @param classLoader the new classloader 434 */ 435 public final void setClassLoader(ClassLoader classLoader) { 436 this.classLoader = classLoader; 437 } 438 439 /** 440 * Sets the classloader that is used to contextualize fileset checks. 441 * Some Check implementations will use that classloader to improve the 442 * quality of their reports, e.g. to load a class and then analyze it via 443 * reflection. 444 * @param loader the new classloader 445 * @deprecated use {@link #setClassLoader(ClassLoader loader)} instead. 446 */ 447 @Deprecated 448 public final void setClassloader(ClassLoader loader) { 449 classLoader = loader; 450 } 451 452 /** 453 * Sets the classloader used to load Checkstyle core and custom module 454 * classes when the module tree is being built up. 455 * If no custom ModuleFactory is being set for the Checker module then 456 * this module classloader must be specified. 457 * @param moduleClassLoader the classloader used to load module classes 458 */ 459 public final void setModuleClassLoader(ClassLoader moduleClassLoader) { 460 this.moduleClassLoader = moduleClassLoader; 461 } 462 463 /** 464 * Sets a named charset. 465 * @param charset the name of a charset 466 * @throws UnsupportedEncodingException if charset is unsupported. 467 */ 468 public void setCharset(String charset) 469 throws UnsupportedEncodingException { 470 if (!Charset.isSupported(charset)) { 471 final String message = "unsupported charset: '" + charset + "'"; 472 throw new UnsupportedEncodingException(message); 473 } 474 this.charset = charset; 475 } 476}