Skip to content

Commit

Permalink
Issue #20: Configuration Component
Browse files Browse the repository at this point in the history
  • Loading branch information
Luolc committed Jul 3, 2017
1 parent 55e8381 commit 8532b26
Show file tree
Hide file tree
Showing 10 changed files with 437 additions and 0 deletions.
6 changes: 6 additions & 0 deletions config/import-control.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
<allow pkg="com.github.checkstyle.regression.data"/>
<allow pkg="org.immutables.value"/>

<subpackage name="configuration">
<allow pkg="javax.xml"/>
<allow pkg="org.w3c.dom"/>
<allow pkg="org.xml"/>
</subpackage>

<subpackage name="data">
<!-- it is only allowed until https://github.com/checkstyle/checkstyle/issues/3492 -->
<allow pkg="org.immutables.gson"/>
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,11 @@
<branchRate>0</branchRate>
<lineRate>0</lineRate>
</regex>
<regex>
<pattern>com.github.checkstyle.regression.configuration.ConfigGenerator</pattern>
<branchRate>80</branchRate>
<lineRate>91</lineRate>
</regex>
<regex>
<pattern>com.github.checkstyle.regression.module.ModuleUtils</pattern>
<branchRate>28</branchRate>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
////////////////////////////////////////////////////////////////////////////////
// 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 attribute name "name". */
private static final String ATTR_NAME = "name";

/** 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<ModuleInfo> 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());
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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<module name="Checker">
<property name="charset" value="UTF-8"/>
<!-- do not change severity to 'error', as that will hide errors caused by exceptions -->
<property name="severity" value="warning"/>
<!-- haltOnException is required for exception fixes and reporting of all exceptions -->
<property name="haltOnException" value="false"/>
<!-- BeforeExecutionFileFilters is required for sources of java9 -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>

<!-- Contents below are generated by regression-tool. -->
<module name="TreeWalker"/></module>
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
////////////////////////////////////////////////////////////////////////////////
// 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.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 testDtdVersion() throws Exception {
}

@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 moduleInfo = ImmutableModuleInfo.builder()
.moduleExtractInfo(extractInfo)
.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 moduleInfo = ImmutableModuleInfo.builder()
.moduleExtractInfo(extractInfo)
.build();

final String actual = ConfigGenerator.generateConfigText(
Collections.singletonList(moduleInfo));
assertEquals("Config is not as expected", excepted, actual);
}

@Test
public void testGenerateConfigTextWithMiscModuleInfos() throws Exception {
final String excepted =
getExpectedXml("expected_misc_module_infos.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();
}
}
}
Loading

0 comments on commit 8532b26

Please sign in to comment.