All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.pmd.cpd.XMLRenderer Maven / Gradle / Ivy

Go to download

PMD is an extensible multilanguage static code analyzer. It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and so forth. It's mainly concerned with Java and Apex, but supports 16 other languages. It comes with 400+ built-in rules. It can be extended with custom rules. It uses JavaCC and Antlr to parse source files into abstract syntax trees (AST) and runs rules against them to find violations. Rules can be written in Java or using a XPath query. Currently, PMD supports Java, JavaScript, Salesforce.com Apex and Visualforce, Kotlin, Swift, Modelica, PLSQL, Apache Velocity, JSP, WSDL, Maven POM, HTML, XML and XSL. Scala is supported, but there are currently no Scala rules available. Additionally, it includes CPD, the copy-paste-detector. CPD finds duplicated code in Coco, C/C++, C#, Dart, Fortran, Gherkin, Go, Groovy, HTML, Java, JavaScript, JSP, Julia, Kotlin, Lua, Matlab, Modelica, Objective-C, Perl, PHP, PLSQL, Python, Ruby, Salesforce.com Apex and Visualforce, Scala, Swift, T-SQL, Typescript, Apache Velocity, WSDL, XML and XSL.

There is a newer version: 7.5.0-metrics
Show newest version
/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.cpd;

import java.io.IOException;
import java.io.Writer;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
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 net.sourceforge.pmd.PMDVersion;
import net.sourceforge.pmd.lang.document.Chars;
import net.sourceforge.pmd.lang.document.FileId;
import net.sourceforge.pmd.lang.document.FileLocation;
import net.sourceforge.pmd.reporting.Report;
import net.sourceforge.pmd.util.StringUtil;

/**
 * @author Philippe T'Seyen - original implementation
 * @author Romain Pelisse - javax.xml implementation
 *
 */
public final class XMLRenderer implements CPDReportRenderer {
    private static final String NAMESPACE_URI = "https://pmd-code.org/schema/cpd-report";
    private static final String NAMESPACE_LOCATION = "https://pmd.github.io/schema/cpd-report_1_0_0.xsd";
    private static final String SCHEMA_VERSION = "1.0.0";

    private String encoding;

    private final boolean newFormat;

    /**
     * Creates a XML Renderer with the default (platform dependent) encoding.
     */
    public XMLRenderer() {
        this(null, true);
    }

    /**
     * Creates a XML Renderer with a specific output encoding.
     *
     * @param encoding
     *            the encoding to use or null. If null, default (platform
     *            dependent) encoding is used.
     */
    public XMLRenderer(String encoding) {
        this(encoding, true);
    }

    XMLRenderer(String encoding, boolean newFormat) {
        setEncoding(encoding);
        this.newFormat = newFormat;
    }

    public void setEncoding(String encoding) {
        if (encoding != null) {
            this.encoding = encoding;
        } else {
            this.encoding = System.getProperty("file.encoding");
        }
    }

    public String getEncoding() {
        return this.encoding;
    }

    private Document createDocument() {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder parser = factory.newDocumentBuilder();
            return parser.newDocument();
        } catch (ParserConfigurationException e) {
            throw new IllegalStateException(e);
        }
    }

    private void dumpDocToWriter(Document doc, Writer writer) {
        try {
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.setOutputProperty(OutputKeys.VERSION, "1.0");
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            if (newFormat) {
                transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "{" + NAMESPACE_URI + "}codefragment");
            } else {
                transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "codefragment");
            }
            transformer.transform(new DOMSource(doc), new StreamResult(writer));
        } catch (TransformerException e) {
            throw new IllegalStateException(e);
        }
    }


    @Override
    public void render(final CPDReport report, final Writer writer) throws IOException {
        final Document doc = createDocument();
        final Element root = createElement(doc, "pmd-cpd");

        if (newFormat) {
            root.setAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "xsi:schemaLocation", NAMESPACE_URI + " " + NAMESPACE_LOCATION);
            root.setAttributeNS(NAMESPACE_URI, "version", SCHEMA_VERSION);
            root.setAttributeNS(NAMESPACE_URI, "pmdVersion", PMDVersion.VERSION);
            root.setAttributeNS(NAMESPACE_URI, "timestamp", OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
        }

        final Map numberOfTokensPerFile = report.getNumberOfTokensPerFile();
        doc.appendChild(root);

        for (final Map.Entry pair : numberOfTokensPerFile.entrySet()) {
            final Element fileElement = createElement(doc, "file");
            setAttribute(fileElement, "path", report.getDisplayName(pair.getKey()));
            setAttribute(fileElement, "totalNumberOfTokens", String.valueOf(pair.getValue()));
            root.appendChild(fileElement);
        }

        for (Match match : report.getMatches()) {
            Element dupElt = createDuplicationElement(doc, match);
            addFilesToDuplicationElement(doc, dupElt, match, report);
            addCodeSnippet(doc, dupElt, match, report);
            root.appendChild(dupElt);
        }

        if (newFormat) {
            for (Report.ProcessingError error : report.getProcessingErrors()) {
                Element errorElt = doc.createElementNS(NAMESPACE_URI, "error");
                errorElt.setAttributeNS(NAMESPACE_URI, "filename", report.getDisplayName(error.getFileId()));
                errorElt.setAttributeNS(NAMESPACE_URI, "msg", error.getMsg());
                errorElt.setTextContent(error.getDetail());
                root.appendChild(errorElt);
            }
        }

        dumpDocToWriter(doc, writer);
        writer.flush();
    }

    private void addFilesToDuplicationElement(Document doc, Element duplication, Match match, CPDReport report) {
        for (Mark mark : match) {
            final Element file = createElement(doc, "file");
            FileLocation loc = mark.getLocation();
            setAttribute(file, "line", String.valueOf(loc.getStartLine()));
            // only remove invalid characters, escaping is done by the DOM impl.
            String filenameXml10 = StringUtil.removedInvalidXml10Characters(report.getDisplayName(loc.getFileId()));
            setAttribute(file, "path", filenameXml10);
            setAttribute(file, "endline", String.valueOf(loc.getEndLine()));
            setAttribute(file, "column", String.valueOf(loc.getStartColumn()));
            setAttribute(file, "endcolumn", String.valueOf(loc.getEndColumn()));
            setAttribute(file, "begintoken", String.valueOf(mark.getBeginTokenIndex()));
            setAttribute(file, "endtoken", String.valueOf(mark.getEndTokenIndex()));
            duplication.appendChild(file);
        }
    }

    private void addCodeSnippet(Document doc, Element duplication, Match match, CPDReport report) {
        Chars codeSnippet = report.getSourceCodeSlice(match.getFirstMark());
        if (codeSnippet != null) {
            // the code snippet has normalized line endings
            String platformSpecific = codeSnippet.toString().replace("\n", System.lineSeparator());
            Element codefragment = createElement(doc, "codefragment");
            // only remove invalid characters, escaping is not necessary in CDATA.
            // if the string contains the end marker of a CDATA section, then the DOM impl will
            // create two cdata sections automatically.
            codefragment.appendChild(doc.createCDATASection(StringUtil.removedInvalidXml10Characters(platformSpecific)));
            duplication.appendChild(codefragment);
        }
    }

    private Element createDuplicationElement(Document doc, Match match) {
        Element duplication = createElement(doc, "duplication");
        setAttribute(duplication, "lines", String.valueOf(match.getLineCount()));
        setAttribute(duplication, "tokens", String.valueOf(match.getTokenCount()));
        return duplication;
    }

    private Element createElement(Document doc, String name) {
        if (newFormat) {
            return doc.createElementNS(NAMESPACE_URI, name);
        }
        return doc.createElement(name);
    }

    private void setAttribute(Element element, String name, String value) {
        if (newFormat) {
            element.setAttributeNS(NAMESPACE_URI, name, value);
        } else {
            element.setAttribute(name, value);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy