001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.meta;
021
022import java.io.File;
023import java.util.Locale;
024import java.util.regex.Pattern;
025
026import javax.xml.parsers.DocumentBuilder;
027import javax.xml.parsers.DocumentBuilderFactory;
028import javax.xml.parsers.ParserConfigurationException;
029import javax.xml.transform.OutputKeys;
030import javax.xml.transform.Transformer;
031import javax.xml.transform.TransformerException;
032import javax.xml.transform.TransformerFactory;
033import javax.xml.transform.dom.DOMSource;
034import javax.xml.transform.stream.StreamResult;
035
036import org.w3c.dom.Document;
037import org.w3c.dom.Element;
038import org.w3c.dom.Node;
039
040/**
041 * Class to write module details object into an XML file.
042 */
043public final class XmlMetaWriter {
044
045    /** Compiled pattern for {@code .} used for generating file paths from package names. */
046    private static final Pattern FILEPATH_CONVERSION = Pattern.compile("\\.");
047
048    /** Name tag of metadata XML files. */
049    private static final String XML_TAG_NAME = "name";
050
051    /** Description tag of metadata XML files. */
052    private static final String XML_TAG_DESCRIPTION = "description";
053
054    /** Default(UNIX) file separator. */
055    private static final String DEFAULT_FILE_SEPARATOR = "/";
056
057    /**
058     * Do no allow {@code XmlMetaWriter} instances to be created.
059     */
060    private XmlMetaWriter() {
061    }
062
063    /**
064     * Helper function to write module details to XML file.
065     *
066     * @param moduleDetails module details
067     * @throws TransformerException if a transformer exception occurs
068     * @throws ParserConfigurationException if a parser configuration exception occurs
069     */
070    public static void write(ModuleDetails moduleDetails) throws TransformerException,
071            ParserConfigurationException {
072        final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
073        final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
074        final Document doc = dBuilder.newDocument();
075
076        final Element rootElement = doc.createElement("checkstyle-metadata");
077        final Element rootChild = doc.createElement("module");
078        rootElement.appendChild(rootChild);
079
080        doc.appendChild(rootElement);
081
082        final Element checkModule = doc.createElement(moduleDetails.getModuleType().getLabel());
083        rootChild.appendChild(checkModule);
084
085        checkModule.setAttribute(XML_TAG_NAME, moduleDetails.getName());
086        checkModule.setAttribute("fully-qualified-name",
087                moduleDetails.getFullQualifiedName());
088        checkModule.setAttribute("parent", moduleDetails.getParent());
089
090        final Element desc = doc.createElement(XML_TAG_DESCRIPTION);
091        final Node cdataDesc = doc.createCDATASection(moduleDetails.getDescription());
092        desc.appendChild(cdataDesc);
093        checkModule.appendChild(desc);
094        createPropertySection(moduleDetails, checkModule, doc);
095        if (!moduleDetails.getViolationMessageKeys().isEmpty()) {
096            final Element messageKeys = doc.createElement("message-keys");
097            for (String msg : moduleDetails.getViolationMessageKeys()) {
098                final Element messageKey = doc.createElement("message-key");
099                messageKey.setAttribute("key", msg);
100                messageKeys.appendChild(messageKey);
101            }
102            checkModule.appendChild(messageKeys);
103        }
104
105        writeToFile(doc, moduleDetails);
106    }
107
108    /**
109     * Create the property section of the module detail object.
110     *
111     * @param moduleDetails module details
112     * @param checkModule root doc element
113     * @param doc document object
114     */
115    private static void createPropertySection(ModuleDetails moduleDetails, Element checkModule,
116                                              Document doc) {
117        if (!moduleDetails.getProperties().isEmpty()) {
118            final Element properties = doc.createElement("properties");
119            checkModule.appendChild(properties);
120            for (ModulePropertyDetails modulePropertyDetails : moduleDetails.getProperties()) {
121                final Element property = doc.createElement("property");
122                properties.appendChild(property);
123                property.setAttribute(XML_TAG_NAME, modulePropertyDetails.getName());
124                property.setAttribute("type", modulePropertyDetails.getType());
125                if (modulePropertyDetails.getDefaultValue() != null) {
126                    property.setAttribute("default-value",
127                            modulePropertyDetails.getDefaultValue());
128                }
129                if (modulePropertyDetails.getValidationType() != null) {
130                    property.setAttribute("validation-type",
131                            modulePropertyDetails.getValidationType());
132                }
133                final Element propertyDesc = doc.createElement(XML_TAG_DESCRIPTION);
134                propertyDesc.appendChild(doc.createCDATASection(
135                        modulePropertyDetails.getDescription()));
136                property.appendChild(propertyDesc);
137            }
138        }
139    }
140
141    /**
142     * Function to write the prepared document object into an XML file.
143     *
144     * @param document document updated with all module metadata
145     * @param moduleDetails the corresponding module details object
146     * @throws TransformerException if a transformer exception occurs
147     */
148    private static void writeToFile(Document document, ModuleDetails moduleDetails)
149            throws TransformerException {
150        String fileSeparator = DEFAULT_FILE_SEPARATOR;
151        if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win")) {
152            fileSeparator = "\\" + fileSeparator;
153        }
154        final String modifiedPath;
155        final String xmlExtension = ".xml";
156        final String rootOutputPath = System.getProperty("user.dir") + "/src/main/resources";
157        if (moduleDetails.getFullQualifiedName().startsWith("com.puppycrawl.tools.checkstyle")) {
158            final String moduleFilePath = FILEPATH_CONVERSION
159                    .matcher(moduleDetails.getFullQualifiedName())
160                    .replaceAll(fileSeparator);
161            final String checkstyleString = "checkstyle";
162            final int indexOfCheckstyle =
163                    moduleFilePath.indexOf(checkstyleString) + checkstyleString.length();
164
165            modifiedPath = rootOutputPath + DEFAULT_FILE_SEPARATOR
166                    + moduleFilePath.substring(0, indexOfCheckstyle) + "/meta/"
167                    + moduleFilePath.substring(indexOfCheckstyle + 1) + xmlExtension;
168        }
169        else {
170            String moduleName = moduleDetails.getName();
171            if (moduleDetails.getModuleType() == ModuleType.CHECK) {
172                moduleName += "Check";
173            }
174            modifiedPath = rootOutputPath + "/checkstylemeta-" + moduleName + xmlExtension;
175        }
176        if (!moduleDetails.getDescription().isEmpty()) {
177            final TransformerFactory transformerFactory = TransformerFactory.newInstance();
178            final Transformer transformer = transformerFactory.newTransformer();
179            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
180            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
181
182            final DOMSource source = new DOMSource(document);
183            final StreamResult result = new StreamResult(new File(modifiedPath));
184            transformer.transform(source, result);
185        }
186    }
187}
188