001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 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.XMLConstants;
027import javax.xml.parsers.DocumentBuilder;
028import javax.xml.parsers.DocumentBuilderFactory;
029import javax.xml.parsers.ParserConfigurationException;
030import javax.xml.transform.OutputKeys;
031import javax.xml.transform.Transformer;
032import javax.xml.transform.TransformerException;
033import javax.xml.transform.TransformerFactory;
034import javax.xml.transform.dom.DOMSource;
035import javax.xml.transform.stream.StreamResult;
036
037import org.w3c.dom.Document;
038import org.w3c.dom.Element;
039import org.w3c.dom.Node;
040
041/**
042 * Class to write module details object into an XML file.
043 */
044public final class XmlMetaWriter {
045
046    /** Compiled pattern for {@code .} used for generating file paths from package names. */
047    private static final Pattern FILEPATH_CONVERSION = Pattern.compile("\\.");
048
049    /** Name tag of metadata XML files. */
050    private static final String XML_TAG_NAME = "name";
051
052    /** Description tag of metadata XML files. */
053    private static final String XML_TAG_DESCRIPTION = "description";
054
055    /** Default(UNIX) file separator. */
056    private static final String DEFAULT_FILE_SEPARATOR = "/";
057
058    /**
059     * Do no allow {@code XmlMetaWriter} instances to be created.
060     */
061    private XmlMetaWriter() {
062    }
063
064    /**
065     * Helper function to write module details to XML file.
066     *
067     * @param moduleDetails module details
068     * @throws TransformerException if a transformer exception occurs
069     * @throws ParserConfigurationException if a parser configuration exception occurs
070     */
071    public static void write(ModuleDetails moduleDetails) throws TransformerException,
072            ParserConfigurationException {
073        final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
074        dbFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
075        dbFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
076        final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
077        final Document doc = dBuilder.newDocument();
078
079        final Element rootElement = doc.createElement("checkstyle-metadata");
080        final Element rootChild = doc.createElement("module");
081        rootElement.appendChild(rootChild);
082
083        doc.appendChild(rootElement);
084
085        final Element checkModule = doc.createElement(moduleDetails.getModuleType().getLabel());
086        rootChild.appendChild(checkModule);
087
088        checkModule.setAttribute(XML_TAG_NAME, moduleDetails.getName());
089        checkModule.setAttribute("fully-qualified-name",
090                moduleDetails.getFullQualifiedName());
091        checkModule.setAttribute("parent", moduleDetails.getParent());
092
093        final Element desc = doc.createElement(XML_TAG_DESCRIPTION);
094        final Node cdataDesc = doc.createCDATASection(moduleDetails.getDescription());
095        desc.appendChild(cdataDesc);
096        checkModule.appendChild(desc);
097        createPropertySection(moduleDetails, checkModule, doc);
098        if (!moduleDetails.getViolationMessageKeys().isEmpty()) {
099            final Element messageKeys = doc.createElement("message-keys");
100            for (String msg : moduleDetails.getViolationMessageKeys()) {
101                final Element messageKey = doc.createElement("message-key");
102                messageKey.setAttribute("key", msg);
103                messageKeys.appendChild(messageKey);
104            }
105            checkModule.appendChild(messageKeys);
106        }
107
108        writeToFile(doc, moduleDetails);
109    }
110
111    /**
112     * Create the property section of the module detail object.
113     *
114     * @param moduleDetails module details
115     * @param checkModule root doc element
116     * @param doc document object
117     */
118    private static void createPropertySection(ModuleDetails moduleDetails, Element checkModule,
119                                              Document doc) {
120        if (!moduleDetails.getProperties().isEmpty()) {
121            final Element properties = doc.createElement("properties");
122            checkModule.appendChild(properties);
123            for (ModulePropertyDetails modulePropertyDetails : moduleDetails.getProperties()) {
124                final Element property = doc.createElement("property");
125                properties.appendChild(property);
126                property.setAttribute(XML_TAG_NAME, modulePropertyDetails.getName());
127                property.setAttribute("type", modulePropertyDetails.getType());
128                if (modulePropertyDetails.getDefaultValue() != null) {
129                    property.setAttribute("default-value",
130                            modulePropertyDetails.getDefaultValue());
131                }
132                if (modulePropertyDetails.getValidationType() != null) {
133                    property.setAttribute("validation-type",
134                            modulePropertyDetails.getValidationType());
135                }
136                final Element propertyDesc = doc.createElement(XML_TAG_DESCRIPTION);
137                propertyDesc.appendChild(doc.createCDATASection(
138                        modulePropertyDetails.getDescription()));
139                property.appendChild(propertyDesc);
140            }
141        }
142    }
143
144    /**
145     * Function to write the prepared document object into an XML file.
146     *
147     * @param document document updated with all module metadata
148     * @param moduleDetails the corresponding module details object
149     * @throws TransformerException if a transformer exception occurs
150     */
151    private static void writeToFile(Document document, ModuleDetails moduleDetails)
152            throws TransformerException {
153        String fileSeparator = DEFAULT_FILE_SEPARATOR;
154        if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win")) {
155            fileSeparator = "\\" + fileSeparator;
156        }
157        final String modifiedPath;
158        final String xmlExtension = ".xml";
159        final String rootOutputPath = System.getProperty("user.dir") + "/src/main/resources";
160        if (moduleDetails.getFullQualifiedName().startsWith("com.puppycrawl.tools.checkstyle")) {
161            final String moduleFilePath = FILEPATH_CONVERSION
162                    .matcher(moduleDetails.getFullQualifiedName())
163                    .replaceAll(fileSeparator);
164            final String checkstyleString = "checkstyle";
165            final int indexOfCheckstyle =
166                    moduleFilePath.indexOf(checkstyleString) + checkstyleString.length();
167
168            modifiedPath = rootOutputPath + DEFAULT_FILE_SEPARATOR
169                    + moduleFilePath.substring(0, indexOfCheckstyle) + "/meta/"
170                    + moduleFilePath.substring(indexOfCheckstyle + 1) + xmlExtension;
171        }
172        else {
173            String moduleName = moduleDetails.getName();
174            if (moduleDetails.getModuleType() == ModuleType.CHECK) {
175                moduleName += "Check";
176            }
177            modifiedPath = rootOutputPath + "/checkstylemeta-" + moduleName + xmlExtension;
178        }
179
180        final TransformerFactory transformerFactory = TransformerFactory.newInstance();
181        final Transformer transformer = transformerFactory.newTransformer();
182        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
183        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
184
185        final DOMSource source = new DOMSource(document);
186        final StreamResult result = new StreamResult(new File(modifiedPath));
187        transformer.transform(source, result);
188
189    }
190}
191