Java tutorial
//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2016 the original author or authors. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package org.solenopsis.checkstyle; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.SortedSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.solenopsis.checkstyle.api.AuditEvent; import org.solenopsis.checkstyle.api.AuditListener; import org.solenopsis.checkstyle.api.AutomaticBean; import org.solenopsis.checkstyle.api.CheckstyleException; import org.solenopsis.checkstyle.api.Configuration; import org.solenopsis.checkstyle.api.Context; import org.solenopsis.checkstyle.api.FileSetCheck; import org.solenopsis.checkstyle.api.FileText; import org.solenopsis.checkstyle.api.Filter; import org.solenopsis.checkstyle.api.FilterSet; import org.solenopsis.checkstyle.api.LocalizedMessage; import org.solenopsis.checkstyle.api.MessageDispatcher; import org.solenopsis.checkstyle.api.SeverityLevel; import org.solenopsis.checkstyle.api.SeverityLevelCounter; import org.solenopsis.checkstyle.utils.CommonUtils; /** * This class provides the functionality to check a set of files. * @author Oliver Burn * @author <a href="mailto:stephane.bailliez@wanadoo.fr">Stephane Bailliez</a> * @author lkuehne */ public class Checker extends AutomaticBean implements MessageDispatcher { /** Logger for Checker. */ private static final Log LOG = LogFactory.getLog(Checker.class); /** Maintains error count. */ private final SeverityLevelCounter counter = new SeverityLevelCounter(SeverityLevel.ERROR); /** Vector of listeners. */ private final List<AuditListener> listeners = Lists.newArrayList(); /** Vector of fileset checks. */ private final List<FileSetCheck> fileSetChecks = Lists.newArrayList(); /** The audit event filters. */ private final FilterSet filters = new FilterSet(); /** Class loader to resolve classes with. **/ private ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); /** The basedir to strip off in file names. */ private String basedir; /** Locale country to report messages . **/ private String localeCountry = Locale.getDefault().getCountry(); /** Locale language to report messages . **/ private String localeLanguage = Locale.getDefault().getLanguage(); /** The factory for instantiating submodules. */ private ModuleFactory moduleFactory; /** The classloader used for loading Checkstyle module classes. */ private ClassLoader moduleClassLoader; /** The context of all child components. */ private Context childContext; /** The file extensions that are accepted. */ private String[] fileExtensions = CommonUtils.EMPTY_STRING_ARRAY; /** * The severity level of any violations found by submodules. * The value of this property is passed to submodules via * contextualize(). * * <p>Note: Since the Checker is merely a container for modules * it does not make sense to implement logging functionality * here. Consequently Checker does not extend AbstractViolationReporter, * leading to a bit of duplicated code for severity level setting. */ private SeverityLevel severityLevel = SeverityLevel.ERROR; /** Name of a charset. */ private String charset = System.getProperty("file.encoding", "UTF-8"); /** Cache file. **/ private PropertyCacheFile cache; /** * Creates a new {@code Checker} instance. * The instance needs to be contextualized and configured. */ public Checker() { addListener(counter); } /** * Sets cache file. * @param fileName the cache file. * @throws IOException if there are some problems with file loading. */ public void setCacheFile(String fileName) throws IOException { final Configuration configuration = getConfiguration(); cache = new PropertyCacheFile(configuration, fileName); cache.load(); } /** * Removes filter. * @param filter filter to remove. */ public void removeFilter(Filter filter) { filters.removeFilter(filter); } /** Cleans up the object. **/ public void destroy() { listeners.clear(); filters.clear(); if (cache != null) { try { cache.persist(); } catch (IOException ex) { throw new IllegalStateException("Unable to persist cache file.", ex); } } } /** * Removes a given listener. * @param listener a listener to remove */ public void removeListener(AuditListener listener) { listeners.remove(listener); } /** * Sets base directory. * @param basedir the base directory to strip off in file names */ public void setBasedir(String basedir) { this.basedir = basedir; } /** * Processes a set of files with all FileSetChecks. * Once this is done, it is highly recommended to call for * the destroy method to close and remove the listeners. * @param files the list of files to be audited. * @return the total number of errors found * @throws CheckstyleException if error condition within Checkstyle occurs * @see #destroy() */ public int process(List<File> files) throws CheckstyleException { // Prepare to start fireAuditStarted(); for (final FileSetCheck fsc : fileSetChecks) { fsc.beginProcessing(charset); } processFiles(files); // Finish up for (final FileSetCheck fsc : fileSetChecks) { // It may also log!!! fsc.finishProcessing(); } for (final FileSetCheck fsc : fileSetChecks) { // It may also log!!! fsc.destroy(); } final int errorCount = counter.getCount(); fireAuditFinished(); return errorCount; } /** Notify all listeners about the audit start. */ private void fireAuditStarted() { final AuditEvent event = new AuditEvent(this); for (final AuditListener listener : listeners) { listener.auditStarted(event); } } /** Notify all listeners about the audit end. */ private void fireAuditFinished() { final AuditEvent event = new AuditEvent(this); for (final AuditListener listener : listeners) { listener.auditFinished(event); } } /** * Processes a list of files with all FileSetChecks. * @param files a list of files to process. * @throws CheckstyleException if error condition within Checkstyle occurs. * @noinspection ProhibitedExceptionThrown */ private void processFiles(List<File> files) throws CheckstyleException { for (final File file : files) { try { final String fileName = file.getAbsolutePath(); final long timestamp = file.lastModified(); if (cache != null && cache.isInCache(fileName, timestamp) || !CommonUtils.matchesFileExtension(file, fileExtensions)) { continue; } fireFileStarted(fileName); final SortedSet<LocalizedMessage> fileMessages = processFile(file); fireErrors(fileName, fileMessages); fireFileFinished(fileName); if (cache != null && fileMessages.isEmpty()) { cache.put(fileName, timestamp); } } catch (Exception ex) { // We need to catch all exceptions to put a reason failure (file name) in exception throw new CheckstyleException("Exception was thrown while processing " + file.getPath(), ex); } catch (Error error) { // We need to catch all errors to put a reason failure (file name) in error throw new Error("Error was thrown while processing " + file.getPath(), error); } } } /** * Processes a file with all FileSetChecks. * @param file a file to process. * @return a sorted set of messages to be logged. * @throws CheckstyleException if error condition within Checkstyle occurs. */ private SortedSet<LocalizedMessage> processFile(File file) throws CheckstyleException { final SortedSet<LocalizedMessage> fileMessages = Sets.newTreeSet(); try { final FileText theText = new FileText(file.getAbsoluteFile(), charset); for (final FileSetCheck fsc : fileSetChecks) { fileMessages.addAll(fsc.process(file, theText)); } } catch (final IOException ioe) { LOG.debug("IOException occurred.", ioe); fileMessages.add(new LocalizedMessage(0, Definitions.CHECKSTYLE_BUNDLE, "general.exception", new String[] { ioe.getMessage() }, null, getClass(), null)); } return fileMessages; } /** * Notify all listeners about the beginning of a file audit. * * @param fileName * the file to be audited */ @Override public void fireFileStarted(String fileName) { final String stripped = CommonUtils.relativizeAndNormalizePath(basedir, fileName); final AuditEvent event = new AuditEvent(this, stripped); for (final AuditListener listener : listeners) { listener.fileStarted(event); } } /** * Notify all listeners about the errors in a file. * * @param fileName the audited file * @param errors the audit errors from the file */ @Override public void fireErrors(String fileName, SortedSet<LocalizedMessage> errors) { final String stripped = CommonUtils.relativizeAndNormalizePath(basedir, fileName); for (final LocalizedMessage element : errors) { final AuditEvent event = new AuditEvent(this, stripped, element); if (filters.accept(event)) { for (final AuditListener listener : listeners) { listener.addError(event); } } } } /** * Notify all listeners about the end of a file audit. * * @param fileName * the audited file */ @Override public void fireFileFinished(String fileName) { final String stripped = CommonUtils.relativizeAndNormalizePath(basedir, fileName); final AuditEvent event = new AuditEvent(this, stripped); for (final AuditListener listener : listeners) { listener.fileFinished(event); } } @Override public void finishLocalSetup() throws CheckstyleException { final Locale locale = new Locale(localeLanguage, localeCountry); LocalizedMessage.setLocale(locale); if (moduleFactory == null) { if (moduleClassLoader == null) { throw new CheckstyleException( "if no custom moduleFactory is set, " + "moduleClassLoader must be specified"); } final Set<String> packageNames = PackageNamesLoader.getPackageNames(moduleClassLoader); moduleFactory = new PackageObjectFactory(packageNames, moduleClassLoader); } final DefaultContext context = new DefaultContext(); context.add("charset", charset); context.add("classLoader", classLoader); context.add("moduleFactory", moduleFactory); context.add("severity", severityLevel.getName()); context.add("basedir", basedir); childContext = context; } @Override protected void setupChild(Configuration childConf) throws CheckstyleException { final String name = childConf.getName(); final Object child; try { child = moduleFactory.createModule(name); if (child instanceof AutomaticBean) { final AutomaticBean bean = (AutomaticBean) child; bean.contextualize(childContext); bean.configure(childConf); } } catch (final CheckstyleException ex) { throw new CheckstyleException("cannot initialize module " + name + " - " + ex.getMessage(), ex); } if (child instanceof FileSetCheck) { final FileSetCheck fsc = (FileSetCheck) child; fsc.init(); addFileSetCheck(fsc); } else if (child instanceof Filter) { final Filter filter = (Filter) child; addFilter(filter); } else if (child instanceof AuditListener) { final AuditListener listener = (AuditListener) child; addListener(listener); } else { throw new CheckstyleException(name + " is not allowed as a child in Checker"); } } /** * Adds a FileSetCheck to the list of FileSetChecks * that is executed in process(). * @param fileSetCheck the additional FileSetCheck */ public void addFileSetCheck(FileSetCheck fileSetCheck) { fileSetCheck.setMessageDispatcher(this); fileSetChecks.add(fileSetCheck); } /** * Adds a filter to the end of the audit event filter chain. * @param filter the additional filter */ public void addFilter(Filter filter) { filters.addFilter(filter); } /** * Add the listener that will be used to receive events from the audit. * @param listener the nosy thing */ public final void addListener(AuditListener listener) { listeners.add(listener); } /** * Sets the file extensions that identify the files that pass the * filter of this FileSetCheck. * @param extensions the set of file extensions. A missing * initial '.' character of an extension is automatically added. */ public final void setFileExtensions(String... extensions) { if (extensions == null) { fileExtensions = null; return; } fileExtensions = new String[extensions.length]; for (int i = 0; i < extensions.length; i++) { final String extension = extensions[i]; if (CommonUtils.startsWithChar(extension, '.')) { fileExtensions[i] = extension; } else { fileExtensions[i] = "." + extension; } } } /** * Sets the factory for creating submodules. * * @param moduleFactory the factory for creating FileSetChecks */ public void setModuleFactory(ModuleFactory moduleFactory) { this.moduleFactory = moduleFactory; } /** * Sets locale country. * @param localeCountry the country to report messages */ public void setLocaleCountry(String localeCountry) { this.localeCountry = localeCountry; } /** * Sets locale language. * @param localeLanguage the language to report messages */ public void setLocaleLanguage(String localeLanguage) { this.localeLanguage = localeLanguage; } /** * Sets the severity level. The string should be one of the names * defined in the {@code SeverityLevel} class. * * @param severity The new severity level * @see SeverityLevel */ public final void setSeverity(String severity) { severityLevel = SeverityLevel.getInstance(severity); } /** * Sets the classloader that is used to contextualize fileset checks. * Some Check implementations will use that classloader to improve the * quality of their reports, e.g. to load a class and then analyze it via * reflection. * @param classLoader the new classloader */ public final void setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } /** * Sets the classloader that is used to contextualize fileset checks. * Some Check implementations will use that classloader to improve the * quality of their reports, e.g. to load a class and then analyze it via * reflection. * @param loader the new classloader * @deprecated use {@link #setClassLoader(ClassLoader loader)} instead. */ @Deprecated public final void setClassloader(ClassLoader loader) { classLoader = loader; } /** * Sets the classloader used to load Checkstyle core and custom module * classes when the module tree is being built up. * If no custom ModuleFactory is being set for the Checker module then * this module classloader must be specified. * @param moduleClassLoader the classloader used to load module classes */ public final void setModuleClassLoader(ClassLoader moduleClassLoader) { this.moduleClassLoader = moduleClassLoader; } /** * Sets a named charset. * @param charset the name of a charset * @throws UnsupportedEncodingException if charset is unsupported. */ public void setCharset(String charset) throws UnsupportedEncodingException { if (!Charset.isSupported(charset)) { final String message = "unsupported charset: '" + charset + "'"; throw new UnsupportedEncodingException(message); } this.charset = charset; } /** * Clears the cache. */ public void clearCache() { if (cache != null) { cache.clear(); } } }