////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 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 com.github.sevntu.checkstyle.internal;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
import com.puppycrawl.tools.checkstyle.api.BeforeExecutionFileFilter;
import com.puppycrawl.tools.checkstyle.api.Filter;
public final class CheckUtil {
private CheckUtil() {
}
public static Set<String> getConfigCheckStyleChecks() {
return getCheckStyleChecksReferencedInConfig("sevntu-checks.xml");
}
/**
* Gets a set of names of checkstyle's checks which are referenced in checkstyle_checks.xml.
*
* @param configFilePath
* file path of checkstyle_checks.xml.
* @return names of checkstyle's checks which are referenced in checkstyle_checks.xml.
*/
public static Set<String> getCheckStyleChecksReferencedInConfig(String configFilePath) {
try {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Validations of XML file make parsing too slow, that is why we
// disable all validations.
factory.setNamespaceAware(false);
factory.setValidating(false);
factory.setFeature("http://xml.org/sax/features/namespaces", false);
factory.setFeature("http://xml.org/sax/features/validation", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar",
false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd",
false);
final DocumentBuilder builder = factory.newDocumentBuilder();
final Document document = builder.parse(new File(configFilePath));
// optional, but recommended
// FYI:
// http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-
// how-does-it-work
document.getDocumentElement().normalize();
final NodeList nodeList = document.getElementsByTagName("module");
final Set<String> checksReferencedInCheckstyleChecksXml = new HashSet<>();
for (int i = 0; i < nodeList.getLength(); i++) {
final Node currentNode = nodeList.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
final Element module = (Element) currentNode;
final String checkName = module.getAttribute("name");
if (!"Checker".equals(checkName) && !"TreeWalker".equals(checkName)) {
checksReferencedInCheckstyleChecksXml.add(checkName);
}
}
}
return checksReferencedInCheckstyleChecksXml;
}
catch (Exception exception) {
throw new IllegalStateException(exception);
}
}
/**
* Gets the checkstyle's non abstract checks.
* @return the set of checkstyle's non abstract check classes.
* @throws IOException if the attempt to read class path resources failed.
*/
public static Set<Class<?>> getCheckstyleChecks() throws IOException {
final Set<Class<?>> checkstyleChecks = new HashSet<>();
final ClassLoader loader = Thread.currentThread()
.getContextClassLoader();
final ClassPath classpath = ClassPath.from(loader);
final String packageName = "com.github.sevntu.checkstyle";
final ImmutableSet<ClassPath.ClassInfo> checkstyleClasses = classpath
.getTopLevelClassesRecursive(packageName);
for (ClassPath.ClassInfo clazz : checkstyleClasses) {
final String className = clazz.getSimpleName();
final Class<?> loadedClass = clazz.load();
if (isCheckstyleNonAbstractCheck(loadedClass, className)) {
checkstyleChecks.add(loadedClass);
}
}
return checkstyleChecks;
}
public static Set<String> getPackages(Set<Class<?>> modules) {
final Set<String> result = new HashSet<>();
for (Class<?> module : modules) {
result.add(module.getPackage().getName()
.replace("com.github.sevntu.checkstyle.checks.", ""));
}
return result;
}
public static Set<Class<?>> getModulesInPackage(Set<Class<?>> modules, String packge) {
final Set<Class<?>> result = new HashSet<>();
for (Class<?> module : modules) {
if (module.getPackage().getName().endsWith(packge)) {
result.add(module);
}
}
return result;
}
/**
* Gets the checkstyle's modules. Checkstyle's modules are nonabstract
* classes from com.puppycrawl.tools.checkstyle package which names end with
* 'Check', do not contain the word 'Input' (are not input files for UTs),
* checkstyle's filters and SuppressWarningsHolder class.
*
* @return a set of checkstyle's modules names.
* @throws IOException if the attempt to read class path resources failed.
*/
public static Set<Class<?>> getCheckstyleModules() throws IOException {
final Set<Class<?>> checkstyleModules = new HashSet<>();
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
final ClassPath classpath = ClassPath.from(loader);
final String packageName = "com.github.sevntu.checkstyle.checks";
final ImmutableSet<ClassPath.ClassInfo> checkstyleClasses = classpath
.getTopLevelClassesRecursive(packageName);
for (ClassPath.ClassInfo clazz : checkstyleClasses) {
final Class<?> loadedClass = clazz.load();
if (isCheckstyleModule(loadedClass)) {
checkstyleModules.add(loadedClass);
}
}
return checkstyleModules;
}
/**
* Get's the check's messages.
* @param module class to examine.
* @return a set of checkstyle's module message fields.
* @throws ClassNotFoundException if the attempt to read a protected class fails.
*/
public static Set<Field> getCheckMessages(Class<?> module) throws ClassNotFoundException {
final Set<Field> checkstyleMessages = new HashSet<>();
// get all fields from current class
final Field[] fields = module.getDeclaredFields();
for (Field field : fields) {
if (field.getName().startsWith("MSG_")) {
checkstyleMessages.add(field);
}
}
// deep scan class through hierarchy
final Class<?> superModule = module.getSuperclass();
if (superModule != null) {
checkstyleMessages.addAll(getCheckMessages(superModule));
}
return checkstyleMessages;
}
/**
* Checks whether a class may be considered as the checkstyle module.
* Checkstyle's modules are nonabstract classes which names end with
* 'Check', do not contain the word 'Input' (are not input files for UTs),
* checkstyle's filters, checkstyle's file filters and
* SuppressWarningsHolder class.
*
* @param loadedClass class to check.
* @return true if the class may be considered as the checkstyle module.
*/
private static boolean isCheckstyleModule(Class<?> loadedClass) {
final String className = loadedClass.getSimpleName();
return isCheckstyleNonAbstractCheck(loadedClass, className)
|| isFilterModule(loadedClass, className)
|| isFileFilterModule(loadedClass, className)
|| "SuppressWarningsHolder".equals(className)
|| "FileContentsHolder".equals(className);
}
/**
* Checks whether a class may be considered as the checkstyle check.
* Checkstyle's checks are nonabstract classes which names end with 'Check',
* do not contain the word 'Input' (are not input files for UTs), and extend
* Check.
*
* @param loadedClass class to check.
* @param className class name to check.
* @return true if a class may be considered as the checkstyle check.
*/
private static boolean isCheckstyleNonAbstractCheck(Class<?> loadedClass, String className) {
return !Modifier.isAbstract(loadedClass.getModifiers()) && className.endsWith("Check")
&& !className.contains("Input")
&& AbstractCheck.class.isAssignableFrom(loadedClass);
}
/**
* Checks whether a class may be considered as the checkstyle filter.
* Checkstyle's filters are classes which are subclasses of AutomaticBean,
* implement 'Filter' interface, and which names end with 'Filter'.
*
* @param loadedClass class to check.
* @param className class name to check.
* @return true if a class may be considered as the checkstyle filter.
*/
private static boolean isFilterModule(Class<?> loadedClass, String className) {
return Filter.class.isAssignableFrom(loadedClass)
&& AutomaticBean.class.isAssignableFrom(loadedClass)
&& className.endsWith("Filter");
}
/**
* Checks whether a class may be considered as the checkstyle file filter.
* Checkstyle's file filters are classes which are subclasses of
* AutomaticBean, implement 'BeforeExecutionFileFilter' interface, and which
* names end with 'FileFilter'.
*
* @param loadedClass class to check.
* @param className class name to check.
* @return true if a class may be considered as the checkstyle file filter.
*/
private static boolean isFileFilterModule(Class<?> loadedClass, String className) {
return BeforeExecutionFileFilter.class.isAssignableFrom(loadedClass)
&& AutomaticBean.class.isAssignableFrom(loadedClass)
&& className.endsWith("FileFilter");
}
/**
* Gets the check message 'as is' from appropriate 'messages.properties'
* file.
*
* @param locale the locale to get the message for.
* @param messageKey the key of message in 'messages*.properties' file.
* @param arguments the arguments of message in 'messages*.properties' file.
* @return the check's formatted message.
*/
public static String getCheckMessage(Class<?> module, String messageKey,
Object... arguments) {
final Properties pr = new Properties();
try {
pr.load(module.getResourceAsStream("messages.properties"));
}
catch (IOException ex) {
return null;
}
final MessageFormat formatter = new MessageFormat(pr.getProperty(messageKey));
return formatter.format(arguments);
}
}