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 try { 256 if (!CommonUtils.matchesFileExtension(file, fileExtensions)) { 257 continue; 258 } 259 final String fileName = file.getAbsolutePath(); 260 fireFileStarted(fileName); 261 final SortedSet<LocalizedMessage> fileMessages = Sets.newTreeSet(); 262 try { 263 final FileText theText = new FileText(file.getAbsoluteFile(), 264 charset); 265 for (final FileSetCheck fsc : fileSetChecks) { 266 fileMessages.addAll(fsc.process(file, theText)); 267 } 268 } 269 catch (final IOException ioe) { 270 LOG.debug("IOException occurred.", ioe); 271 fileMessages.add(new LocalizedMessage(0, 272 Definitions.CHECKSTYLE_BUNDLE, "general.exception", 273 new String[] {ioe.getMessage()}, null, getClass(), 274 null)); 275 } 276 fireErrors(fileName, fileMessages); 277 fireFileFinished(fileName); 278 } 279 catch (Exception ex) { 280 // We need to catch all exception to put a reason failure(file name) in exception 281 throw new CheckstyleException("Exception was thrown while processing " 282 + file.getPath(), ex); 283 } 284 } 285 286 // Finish up 287 for (final FileSetCheck fsc : fileSetChecks) { 288 // It may also log!!! 289 fsc.finishProcessing(); 290 } 291 292 for (final FileSetCheck fsc : fileSetChecks) { 293 // It may also log!!! 294 fsc.destroy(); 295 } 296 297 final int errorCount = counter.getCount(); 298 fireAuditFinished(); 299 return errorCount; 300 } 301 302 /** 303 * Sets base directory. 304 * @param basedir the base directory to strip off in file names 305 */ 306 public void setBasedir(String basedir) { 307 this.basedir = basedir; 308 } 309 310 /** Notify all listeners about the audit start. */ 311 void fireAuditStarted() { 312 final AuditEvent event = new AuditEvent(this); 313 for (final AuditListener listener : listeners) { 314 listener.auditStarted(event); 315 } 316 } 317 318 /** Notify all listeners about the audit end. */ 319 void fireAuditFinished() { 320 final AuditEvent event = new AuditEvent(this); 321 for (final AuditListener listener : listeners) { 322 listener.auditFinished(event); 323 } 324 } 325 326 /** 327 * Notify all listeners about the beginning of a file audit. 328 * 329 * @param fileName 330 * the file to be audited 331 */ 332 @Override 333 public void fireFileStarted(String fileName) { 334 final String stripped = CommonUtils.relativizeAndNormalizePath(basedir, fileName); 335 final AuditEvent event = new AuditEvent(this, stripped); 336 for (final AuditListener listener : listeners) { 337 listener.fileStarted(event); 338 } 339 } 340 341 /** 342 * Notify all listeners about the end of a file audit. 343 * 344 * @param fileName 345 * the audited file 346 */ 347 @Override 348 public void fireFileFinished(String fileName) { 349 final String stripped = CommonUtils.relativizeAndNormalizePath(basedir, fileName); 350 final AuditEvent event = new AuditEvent(this, stripped); 351 for (final AuditListener listener : listeners) { 352 listener.fileFinished(event); 353 } 354 } 355 356 /** 357 * Notify all listeners about the errors in a file. 358 * 359 * @param fileName the audited file 360 * @param errors the audit errors from the file 361 */ 362 @Override 363 public void fireErrors(String fileName, SortedSet<LocalizedMessage> errors) { 364 final String stripped = CommonUtils.relativizeAndNormalizePath(basedir, fileName); 365 for (final LocalizedMessage element : errors) { 366 final AuditEvent event = new AuditEvent(this, stripped, element); 367 if (filters.accept(event)) { 368 for (final AuditListener listener : listeners) { 369 listener.addError(event); 370 } 371 } 372 } 373 } 374 375 /** 376 * Sets the file extensions that identify the files that pass the 377 * filter of this FileSetCheck. 378 * @param extensions the set of file extensions. A missing 379 * initial '.' character of an extension is automatically added. 380 */ 381 public final void setFileExtensions(String... extensions) { 382 if (extensions == null) { 383 fileExtensions = null; 384 return; 385 } 386 387 fileExtensions = new String[extensions.length]; 388 for (int i = 0; i < extensions.length; i++) { 389 final String extension = extensions[i]; 390 if (CommonUtils.startsWithChar(extension, '.')) { 391 fileExtensions[i] = extension; 392 } 393 else { 394 fileExtensions[i] = "." + extension; 395 } 396 } 397 } 398 399 /** 400 * Sets the factory for creating submodules. 401 * 402 * @param moduleFactory the factory for creating FileSetChecks 403 */ 404 public void setModuleFactory(ModuleFactory moduleFactory) { 405 this.moduleFactory = moduleFactory; 406 } 407 408 /** 409 * Sets locale country. 410 * @param localeCountry the country to report messages 411 */ 412 public void setLocaleCountry(String localeCountry) { 413 this.localeCountry = localeCountry; 414 } 415 416 /** 417 * Sets locale language. 418 * @param localeLanguage the language to report messages 419 */ 420 public void setLocaleLanguage(String localeLanguage) { 421 this.localeLanguage = localeLanguage; 422 } 423 424 /** 425 * Sets the severity level. The string should be one of the names 426 * defined in the {@code SeverityLevel} class. 427 * 428 * @param severity The new severity level 429 * @see SeverityLevel 430 */ 431 public final void setSeverity(String severity) { 432 severityLevel = SeverityLevel.getInstance(severity); 433 } 434 435 /** 436 * Sets the classloader that is used to contextualize fileset checks. 437 * Some Check implementations will use that classloader to improve the 438 * quality of their reports, e.g. to load a class and then analyze it via 439 * reflection. 440 * @param classLoader the new classloader 441 */ 442 public final void setClassLoader(ClassLoader classLoader) { 443 this.classLoader = classLoader; 444 } 445 446 /** 447 * Sets the classloader that is used to contextualize fileset checks. 448 * Some Check implementations will use that classloader to improve the 449 * quality of their reports, e.g. to load a class and then analyze it via 450 * reflection. 451 * @param loader the new classloader 452 * @deprecated use {@link #setClassLoader(ClassLoader loader)} instead. 453 */ 454 @Deprecated 455 public final void setClassloader(ClassLoader loader) { 456 classLoader = loader; 457 } 458 459 /** 460 * Sets the classloader used to load Checkstyle core and custom module 461 * classes when the module tree is being built up. 462 * If no custom ModuleFactory is being set for the Checker module then 463 * this module classloader must be specified. 464 * @param moduleClassLoader the classloader used to load module classes 465 */ 466 public final void setModuleClassLoader(ClassLoader moduleClassLoader) { 467 this.moduleClassLoader = moduleClassLoader; 468 } 469 470 /** 471 * Sets a named charset. 472 * @param charset the name of a charset 473 * @throws UnsupportedEncodingException if charset is unsupported. 474 */ 475 public void setCharset(String charset) 476 throws UnsupportedEncodingException { 477 if (!Charset.isSupported(charset)) { 478 final String message = "unsupported charset: '" + charset + "'"; 479 throw new UnsupportedEncodingException(message); 480 } 481 this.charset = charset; 482 } 483}