/* * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.util.fxdesigner.model.testing; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.checkerframework.checker.nullness.qual.Nullable; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.util.fxdesigner.model.ObservableRuleBuilder; import net.sourceforge.pmd.util.fxdesigner.util.AuxLanguageRegistry; import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil; import net.sourceforge.pmd.util.fxdesigner.util.codearea.PmdCoordinatesSystem.TextRange; public class TestXmlParser { private Map<LiveTestCase, Element> parseTests(Document doc, ObservableRuleBuilder owner) { Element root = doc.getDocumentElement(); NodeList testCodes = root.getElementsByTagName("test-code"); Map<LiveTestCase, Element> tests = new LinkedHashMap<>(testCodes.getLength()); for (int i = 0; i < testCodes.getLength(); i++) { Element testCode = (Element) testCodes.item(i); LiveTestCase descriptor = parseSingle(testCode, root, owner); tests.put(descriptor, testCode); } return tests; } private LiveTestCase parseSingle(Element testCode, Element root, ObservableRuleBuilder owner) { // // boolean reinitializeRule = true; // Node reinitializeRuleAttribute = testCode.getAttributes().getNamedItem("reinitializeRule"); // if (reinitializeRuleAttribute != null) { // String reinitializeRuleValue = reinitializeRuleAttribute.getNodeValue(); // if ("false".equalsIgnoreCase(reinitializeRuleValue) || "0".equalsIgnoreCase(reinitializeRuleValue)) { // reinitializeRule = false; // } // } boolean isRegressionTest = true; Node regressionTestAttribute = testCode.getAttributes().getNamedItem("regressionTest"); if (regressionTestAttribute != null) { String reinitializeRuleValue = regressionTestAttribute.getNodeValue(); if ("false".equalsIgnoreCase(reinitializeRuleValue)) { isRegressionTest = false; } } // // boolean isUseAuxClasspath = true; // Node useAuxClasspathAttribute = testCode.getAttributes().getNamedItem("useAuxClasspath"); // if (useAuxClasspathAttribute != null) { // String useAuxClasspathValue = useAuxClasspathAttribute.getNodeValue(); // if ("false".equalsIgnoreCase(useAuxClasspathValue)) { // isUseAuxClasspath = false; // } // } NodeList ruleProperties = testCode.getElementsByTagName("rule-property"); Properties properties = new Properties(); for (int j = 0; j < ruleProperties.getLength(); j++) { Node ruleProperty = ruleProperties.item(j); String propertyName = ruleProperty.getAttributes().getNamedItem("name").getNodeValue(); properties.setProperty(propertyName, parseTextNode(ruleProperty)); } NodeList expectedMessagesNodes = testCode.getElementsByTagName("expected-messages"); List<String> messages = new ArrayList<>(); if (expectedMessagesNodes != null && expectedMessagesNodes.getLength() > 0) { Element item = (Element) expectedMessagesNodes.item(0); NodeList messagesNodes = item.getElementsByTagName("message"); for (int j = 0; j < messagesNodes.getLength(); j++) { messages.add(parseTextNode(messagesNodes.item(j))); } } NodeList expectedLineNumbersNodes = testCode.getElementsByTagName("expected-linenumbers"); List<Integer> expectedLineNumbers = new ArrayList<>(); if (expectedLineNumbersNodes != null && expectedLineNumbersNodes.getLength() > 0) { Element item = (Element) expectedLineNumbersNodes.item(0); String numbers = item.getTextContent(); for (String n : numbers.split(" *, *")) { expectedLineNumbers.add(Integer.valueOf(n)); } } String code = getNodeValue(testCode, "code", false); if (code == null) { // Should have a coderef NodeList coderefs = testCode.getElementsByTagName("code-ref"); if (coderefs.getLength() == 0) { throw new RuntimeException( "Required tag is missing from the test-xml. Supply either a code or a code-ref tag"); } Node coderef = coderefs.item(0); String referenceId = coderef.getAttributes().getNamedItem("id").getNodeValue(); NodeList codeFragments = root.getElementsByTagName("code-fragment"); for (int j = 0; j < codeFragments.getLength(); j++) { String fragmentId = codeFragments.item(j).getAttributes().getNamedItem("id").getNodeValue(); if (referenceId.equals(fragmentId)) { code = parseTextNode(codeFragments.item(j)); } } if (code == null) { throw new RuntimeException("No matching code fragment found for coderef"); } } String description = getNodeValue(testCode, "description", true); int expectedProblems = Integer.parseInt(getNodeValue(testCode, "expected-problems", true)); String languageVersionString = getNodeValue(testCode, "source-type", false); LanguageVersion languageVersion = null; if (languageVersionString != null) { languageVersion = AuxLanguageRegistry.findLanguageVersionByTerseName(languageVersionString); if (languageVersion == null) { throw new RuntimeException("Unknown LanguageVersion for test: " + languageVersionString); } } return fromDescriptor( code, description, expectedProblems, languageVersion, !isRegressionTest, messages, expectedLineNumbers, properties, owner ); } private static LiveTestCase fromDescriptor( String code, String description, int expectedProblems, @Nullable LanguageVersion version, boolean ignored, List<String> messages, List<Integer> lineNumbers, Properties properties, ObservableRuleBuilder owner ) { LiveTestCase live = new LiveTestCase(); live.setRule(owner); live.setSource(code); live.setDescription(description); live.setLanguageVersion(version); live.setIgnored(ignored); List<String> lines = Arrays.asList(code.split("\\r?\\n")); for (int i = 0; i < expectedProblems; i++) { String m = messages.size() > i ? messages.get(i) : null; int line = lineNumbers.size() > i ? lineNumbers.get(i) : -1; TextRange tr = line >= 0 ? TextRange.fullLine(line, lines.get(line - 1).length()) : null; live.getExpectedViolations().add(new LiveViolationRecord(tr, m, false)); } properties.forEach((k, v) -> live.setProperty(k.toString(), v.toString())); return live; } private String getNodeValue(Element parentElm, String nodeName, boolean required) { NodeList nodes = parentElm.getElementsByTagName(nodeName); if (nodes == null || nodes.getLength() == 0) { if (required) { throw new RuntimeException("Required tag is missing from the test-xml: " + nodeName); } else { return null; } } Node node = nodes.item(0); return parseTextNode(node); } private String parseTextNode(Node exampleNode) { StringBuilder buffer = new StringBuilder(); for (int i = 0; i < exampleNode.getChildNodes().getLength(); i++) { Node node = exampleNode.getChildNodes().item(i); if (node.getNodeType() == Node.CDATA_SECTION_NODE || node.getNodeType() == Node.TEXT_NODE) { buffer.append(node.getNodeValue()); } } return buffer.toString().trim(); } public static TestCollection parseXmlTests(String xml, ObservableRuleBuilder owner) throws Exception { ByteArrayInputStream bis = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); return parseXmlTests(bis, owner); } public static TestCollection parseXmlTests(Path path, ObservableRuleBuilder owner) throws Exception { try (InputStream is = Files.newInputStream(path)) { return parseXmlTests(is, owner); } } private static TestCollection parseXmlTests(InputStream is, ObservableRuleBuilder owner) throws Exception { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = schemaFactory.newSchema(DesignerUtil.getResource("testschema/rule-tests_1_0_0.xsd")); dbf.setSchema(schema); dbf.setNamespaceAware(true); DocumentBuilder builder = getDocumentBuilder(dbf); Document doc = builder.parse(is); Map<LiveTestCase, Element> testDescriptors = new TestXmlParser().parseTests(doc, owner); List<LiveTestCase> tests = new ArrayList<>(testDescriptors.keySet()); return new TestCollection(null, tests); } private static DocumentBuilder getDocumentBuilder(DocumentBuilderFactory dbf) throws ParserConfigurationException { DocumentBuilder builder = dbf.newDocumentBuilder(); builder.setErrorHandler(new ErrorHandler() { @Override public void warning(SAXParseException exception) throws SAXException { throw exception; } @Override public void fatalError(SAXParseException exception) throws SAXException { throw exception; } @Override public void error(SAXParseException exception) throws SAXException { throw exception; } }); return builder; } }