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