001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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.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        if (!moduleDetails.getViolationMessageKeys().isEmpty()) {
100            final Element messageKeys = doc.createElement("message-keys");
101            for (String msg : moduleDetails.getViolationMessageKeys()) {
102                final Element messageKey = doc.createElement("message-key");
103                messageKey.setAttribute("key", msg);
104                messageKeys.appendChild(messageKey);
105            }
106            checkModule.appendChild(messageKeys);
107        }
108
109        writeToFile(doc, moduleDetails);
110    }
111
112    /**
113     * Create the property section of the module detail object.
114     *
115     * @param moduleDetails module details
116     * @param checkModule root doc element
117     * @param doc document object
118     */
119    private static void createPropertySection(ModuleDetails moduleDetails, Element checkModule,
120                                              Document doc) {
121        final List<ModulePropertyDetails> moduleProperties = moduleDetails.getProperties();
122        if (!moduleProperties.isEmpty()) {
123            final Element properties = doc.createElement("properties");
124            checkModule.appendChild(properties);
125            for (ModulePropertyDetails modulePropertyDetails : moduleProperties) {
126                final Element property = doc.createElement("property");
127                properties.appendChild(property);
128                property.setAttribute(XML_TAG_NAME, modulePropertyDetails.getName());
129                property.setAttribute("type", modulePropertyDetails.getType());
130                final String defaultValue = modulePropertyDetails.getDefaultValue();
131                if (defaultValue != null) {
132                    property.setAttribute("default-value", defaultValue);
133                }
134                final String validationType = modulePropertyDetails.getValidationType();
135                if (validationType != null) {
136                    property.setAttribute("validation-type", validationType);
137                }
138                final Element propertyDesc = doc.createElement(XML_TAG_DESCRIPTION);
139                propertyDesc.appendChild(doc.createCDATASection(
140                        modulePropertyDetails.getDescription()));
141                property.appendChild(propertyDesc);
142            }
143        }
144    }
145
146    /**
147     * Function to write the prepared document object into an XML file.
148     *
149     * @param document document updated with all module metadata
150     * @param moduleDetails the corresponding module details object
151     * @throws TransformerException if a transformer exception occurs
152     */
153    private static void writeToFile(Document document, ModuleDetails moduleDetails)
154            throws TransformerException {
155        String fileSeparator = DEFAULT_FILE_SEPARATOR;
156        if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win")) {
157            fileSeparator = "\\" + fileSeparator;
158        }
159        final String modifiedPath;
160        final String xmlExtension = ".xml";
161        final String rootOutputPath = System.getProperty("user.dir") + "/src/main/resources";
162        if (moduleDetails.getFullQualifiedName().startsWith("com.puppycrawl.tools.checkstyle")) {
163            final String moduleFilePath = FILEPATH_CONVERSION
164                    .matcher(moduleDetails.getFullQualifiedName())
165                    .replaceAll(fileSeparator);
166            final String checkstyleString = "checkstyle";
167            final int indexOfCheckstyle =
168                    moduleFilePath.indexOf(checkstyleString) + checkstyleString.length();
169
170            modifiedPath = rootOutputPath + DEFAULT_FILE_SEPARATOR
171                    + moduleFilePath.substring(0, indexOfCheckstyle) + "/meta/"
172                    + moduleFilePath.substring(indexOfCheckstyle + 1) + xmlExtension;
173        }
174        else {
175            String moduleName = moduleDetails.getName();
176            if (moduleDetails.getModuleType() == ModuleType.CHECK) {
177                moduleName += "Check";
178            }
179            modifiedPath = rootOutputPath + "/checkstylemeta-" + moduleName + xmlExtension;
180        }
181
182        final TransformerFactory transformerFactory = TransformerFactory.newInstance();
183        final Transformer transformer = transformerFactory.newTransformer();
184        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
185        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
186
187        final DOMSource source = new DOMSource(document);
188        final StreamResult result = new StreamResult(new File(modifiedPath));
189        transformer.transform(source, result);
190
191    }
192}
193