diff --git a/config/import-control.xml b/config/import-control.xml index fb22fab..a1065a3 100644 --- a/config/import-control.xml +++ b/config/import-control.xml @@ -9,6 +9,12 @@ + + + + + + diff --git a/pom.xml b/pom.xml index d5afcf3..135fd4d 100644 --- a/pom.xml +++ b/pom.xml @@ -285,6 +285,11 @@ 0 0 + + com.github.checkstyle.regression.configuration.ConfigGenerator + 83 + 92 + com.github.checkstyle.regression.module.ModuleUtils 28 diff --git a/src/main/java/com/github/checkstyle/regression/configuration/ConfigGenerator.java b/src/main/java/com/github/checkstyle/regression/configuration/ConfigGenerator.java new file mode 100644 index 0000000..f0bba7b --- /dev/null +++ b/src/main/java/com/github/checkstyle/regression/configuration/ConfigGenerator.java @@ -0,0 +1,200 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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.checkstyle.regression.configuration; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import com.github.checkstyle.regression.data.ModuleInfo; + +/** + * Generates the config XML and output it to a specific file. + * @author LuoLiangchen + */ +public final class ConfigGenerator { + /** The parent name "Checker". */ + private static final String PARENT_CHECKER = "Checker"; + + /** The parent name "TreeWalker". */ + private static final String PARENT_TREE_WALKER = "TreeWalker"; + + /** The name of a module element. */ + private static final String ELEMENT_MODULE = "module"; + + /** The name of a property element. */ + private static final String ELEMENT_PROPERTY = "property"; + + /** The attribute name "name". */ + private static final String ATTR_NAME = "name"; + + /** The attribute name "value". */ + private static final String ATTR_VALUE = "value"; + + /** The "doctype-public" value of the config. */ + private static final String DOCTYPE_PUBLIC = + "-//Puppy Crawl//DTD Check Configuration 1.3//EN"; + + /** The "doctype-system" value of the config. */ + private static final String DOCTYPE_SYSTEM = + "http://www.puppycrawl.com/dtds/configuration_1_3.dtd"; + + /** Prevents instantiation. */ + private ConfigGenerator() { + } + + /** + * Creates a new {@link Document} instance from the {@code BASE_CONFIG}. + * @return a new {@link Document} instance + */ + private static Document createXmlDocument() { + final Document document; + + try { + final File baseConfig = new File("src/main/resources/com/github/checkstyle/regression/" + + "configuration/base_config.xml"); + document = DocumentBuilderFactory.newInstance().newDocumentBuilder() + .parse(baseConfig); + } + catch (ParserConfigurationException | SAXException | IOException ex) { + throw new IllegalStateException("cannot instantiate Document instance", ex); + } + + return document; + } + + /** + * Creates a new {@link Transformer} instance and do initialization. + * @return a new {@link Transformer} instance + */ + private static Transformer createXmlTransformer() { + final Transformer transformer; + + try { + transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, DOCTYPE_PUBLIC); + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, DOCTYPE_SYSTEM); + } + catch (TransformerConfigurationException ex) { + throw new IllegalStateException("cannot instantiate Transformer instance", ex); + } + + return transformer; + } + + /** + * Generates the plain text of the config XML from the given module infos. + * @param moduleInfos the given module infos + * @return the generated plain text of the config + * @throws TransformerException failure of transforming the XML document + */ + public static String generateConfigText(List moduleInfos) + throws TransformerException { + final Document document = createXmlDocument(); + + final Node checkerNode = getCheckerModuleNode(document); + final Node treeWalkerNode = getTreeWalkerModuleNode(document); + for (ModuleInfo moduleInfo : moduleInfos) { + final Node moduleNode = createModuleNode(document, moduleInfo); + final String parent = moduleInfo.moduleExtractInfo().parent(); + if (PARENT_CHECKER.equals(parent)) { + checkerNode.appendChild(moduleNode); + } + else if (PARENT_TREE_WALKER.equals(parent)) { + treeWalkerNode.appendChild(moduleNode); + } + } + + final Transformer transformer = createXmlTransformer(); + final StringWriter writer = new StringWriter(); + transformer.transform(new DOMSource(document), new StreamResult(writer)); + return writer.getBuffer().toString().replaceAll("\r\n", "\n"); + } + + /** + * Creates a XML element node which represents the settings of a checkstyle module. + * The information to create the node is grabbed from the given module info. + * @param document the XML document to create element + * @param moduleInfo the given module info + * @return a checkstyle module node + */ + private static Node createModuleNode(Document document, ModuleInfo moduleInfo) { + final Element moduleNode = document.createElement(ELEMENT_MODULE); + moduleNode.setAttribute(ATTR_NAME, moduleInfo.name()); + + for (ModuleInfo.Property property : moduleInfo.properties()) { + final Element propertyNode = document.createElement(ELEMENT_PROPERTY); + propertyNode.setAttribute(ATTR_NAME, property.name()); + propertyNode.setAttribute(ATTR_VALUE, property.value()); + moduleNode.appendChild(propertyNode); + } + return moduleNode; + } + + /** + * Gets the "TreeWalker" element node of the config document. + * @param document the XML document to search + * @return the "TreeWalker" element node + */ + private static Node getTreeWalkerModuleNode(Document document) { + Node returnValue = null; + final NodeList nodeList = document.getDocumentElement() + .getElementsByTagName(ELEMENT_MODULE); + + for (int i = 0; i < nodeList.getLength(); ++i) { + final Node node = nodeList.item(i); + final Node nameAttr = node.getAttributes().getNamedItem(ATTR_NAME); + if (PARENT_TREE_WALKER.equals(nameAttr.getNodeValue())) { + returnValue = node; + break; + } + } + + return returnValue; + } + + /** + * Gets the "Checker" element node of the config document. + * @param document the XML document to search + * @return the "Checker" element node + */ + private static Node getCheckerModuleNode(Document document) { + return document.getDocumentElement(); + } +} diff --git a/src/main/java/com/github/checkstyle/regression/configuration/package-info.java b/src/main/java/com/github/checkstyle/regression/configuration/package-info.java new file mode 100644 index 0000000..a30ef4e --- /dev/null +++ b/src/main/java/com/github/checkstyle/regression/configuration/package-info.java @@ -0,0 +1,24 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 +//////////////////////////////////////////////////////////////////////////////// + +/** + * Contains the config XML generation classes. + * @author LuoLiangchen + */ +package com.github.checkstyle.regression.configuration; diff --git a/src/main/resources/com/github/checkstyle/regression/configuration/base_config.xml b/src/main/resources/com/github/checkstyle/regression/configuration/base_config.xml new file mode 100644 index 0000000..f7c3219 --- /dev/null +++ b/src/main/resources/com/github/checkstyle/regression/configuration/base_config.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/java/com/github/checkstyle/regression/configuration/ConfigGeneratorTest.java b/src/test/java/com/github/checkstyle/regression/configuration/ConfigGeneratorTest.java new file mode 100644 index 0000000..80b1e52 --- /dev/null +++ b/src/test/java/com/github/checkstyle/regression/configuration/ConfigGeneratorTest.java @@ -0,0 +1,146 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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.checkstyle.regression.configuration; + +import static com.github.checkstyle.regression.internal.TestUtils.assertUtilsClassHasPrivateConstructor; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Collections; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +import com.github.checkstyle.regression.data.ImmutableModuleExtractInfo; +import com.github.checkstyle.regression.data.ImmutableModuleInfo; +import com.github.checkstyle.regression.data.ImmutableProperty; +import com.github.checkstyle.regression.data.ModuleExtractInfo; +import com.github.checkstyle.regression.data.ModuleInfo; + +public class ConfigGeneratorTest { + private static final String BASE_PACKAGE = "com.puppycrawl.tools.checkstyle"; + + @Test + public void testIsProperUtilsClass() throws Exception { + assertUtilsClassHasPrivateConstructor(ConfigGenerator.class); + } + + @Test + public void testGenerateConfigTextWithEmptyModuleInfos() throws Exception { + final String excepted = getExpectedXml("expected_empty_module_infos.xml"); + + final String actual = ConfigGenerator.generateConfigText(Collections.emptyList()); + assertEquals("Config is not as expected", excepted, actual); + } + + @Test + public void testGenerateConfigTextWithCheckerParentModule() throws Exception { + final String excepted = getExpectedXml("expected_checker_parent_module.xml"); + + final ModuleExtractInfo extractInfo = ImmutableModuleExtractInfo.builder() + .name("FileLengthCheck") + .packageName(BASE_PACKAGE + ".checks.sizes") + .parent("Checker") + .build(); + final ModuleInfo.Property property = ImmutableProperty.builder() + .name("fileExtensions") + .value("java") + .build(); + final ModuleInfo moduleInfo = ImmutableModuleInfo.builder() + .moduleExtractInfo(extractInfo) + .addProperties(property) + .build(); + + final String actual = ConfigGenerator.generateConfigText( + Collections.singletonList(moduleInfo)); + assertEquals("Config is not as expected", excepted, actual); + } + + @Test + public void testGenerateConfigTextWithTreeWalkerParentModule() throws Exception { + final String excepted = getExpectedXml("expected_tree_walker_parent_module.xml"); + + final ModuleExtractInfo extractInfo = ImmutableModuleExtractInfo.builder() + .name("HiddenFieldCheck") + .packageName(BASE_PACKAGE + ".checks.coding") + .parent("TreeWalker") + .build(); + final ModuleInfo.Property property1 = ImmutableProperty.builder() + .name("ignoreConstructorParameter") + .value("true") + .build(); + final ModuleInfo.Property property2 = ImmutableProperty.builder() + .name("ignoreSetter") + .value("true") + .build(); + final ModuleInfo.Property property3 = ImmutableProperty.builder() + .name("setterCanReturnItsClass") + .value("true") + .build(); + final ModuleInfo moduleInfo = ImmutableModuleInfo.builder() + .moduleExtractInfo(extractInfo) + .addProperties(property1, property2, property3) + .build(); + + final String actual = ConfigGenerator.generateConfigText( + Collections.singletonList(moduleInfo)); + assertEquals("Config is not as expected", excepted, actual); + } + + @Test + public void testGenerateConfigTextWithModuleInfosWithoutProperties() throws Exception { + final String excepted = + getExpectedXml("expected_module_infos_without_properties.xml"); + + final ModuleExtractInfo extractInfo1 = ImmutableModuleExtractInfo.builder() + .name("NewlineAtEndOfFileCheck") + .packageName(BASE_PACKAGE + ".checks") + .parent("Checker") + .build(); + final ModuleInfo moduleInfo1 = ImmutableModuleInfo.builder() + .moduleExtractInfo(extractInfo1) + .build(); + + final ModuleExtractInfo extractInfo2 = ImmutableModuleExtractInfo.builder() + .name("EmptyStatementCheck") + .packageName(BASE_PACKAGE + ".checks.coding") + .parent("TreeWalker") + .build(); + final ModuleInfo moduleInfo2 = ImmutableModuleInfo.builder() + .moduleExtractInfo(extractInfo2) + .build(); + + final String actual = ConfigGenerator.generateConfigText( + Arrays.asList(moduleInfo1, moduleInfo2)); + assertEquals("Config is not as expected", excepted, actual); + } + + private static String getExpectedXml(String fileName) throws IOException { + try (InputStream stream = ConfigGeneratorTest.class.getClassLoader().getResourceAsStream( + "com/github/checkstyle/regression/configuration/" + fileName)) { + final StringWriter writer = new StringWriter(); + IOUtils.copy(stream, writer, "UTF-8"); + return writer.toString(); + } + } +} diff --git a/src/test/resources/com/github/checkstyle/regression/configuration/expected_checker_parent_module.xml b/src/test/resources/com/github/checkstyle/regression/configuration/expected_checker_parent_module.xml new file mode 100644 index 0000000..51a821e --- /dev/null +++ b/src/test/resources/com/github/checkstyle/regression/configuration/expected_checker_parent_module.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/com/github/checkstyle/regression/configuration/expected_empty_module_infos.xml b/src/test/resources/com/github/checkstyle/regression/configuration/expected_empty_module_infos.xml new file mode 100644 index 0000000..82c1889 --- /dev/null +++ b/src/test/resources/com/github/checkstyle/regression/configuration/expected_empty_module_infos.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/resources/com/github/checkstyle/regression/configuration/expected_module_infos_without_properties.xml b/src/test/resources/com/github/checkstyle/regression/configuration/expected_module_infos_without_properties.xml new file mode 100644 index 0000000..4bafc78 --- /dev/null +++ b/src/test/resources/com/github/checkstyle/regression/configuration/expected_module_infos_without_properties.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/com/github/checkstyle/regression/configuration/expected_tree_walker_parent_module.xml b/src/test/resources/com/github/checkstyle/regression/configuration/expected_tree_walker_parent_module.xml new file mode 100644 index 0000000..9d00b17 --- /dev/null +++ b/src/test/resources/com/github/checkstyle/regression/configuration/expected_tree_walker_parent_module.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + +