View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.meta;
21  
22  import java.nio.file.Path;
23  import java.util.List;
24  import java.util.Locale;
25  import java.util.regex.Pattern;
26  
27  import javax.xml.XMLConstants;
28  import javax.xml.parsers.DocumentBuilder;
29  import javax.xml.parsers.DocumentBuilderFactory;
30  import javax.xml.parsers.ParserConfigurationException;
31  import javax.xml.transform.OutputKeys;
32  import javax.xml.transform.Transformer;
33  import javax.xml.transform.TransformerException;
34  import javax.xml.transform.TransformerFactory;
35  import javax.xml.transform.dom.DOMSource;
36  import javax.xml.transform.stream.StreamResult;
37  
38  import org.w3c.dom.Document;
39  import org.w3c.dom.Element;
40  import org.w3c.dom.Node;
41  
42  /**
43   * Class to write module details object into an XML file.
44   */
45  public final class XmlMetaWriter {
46  
47      /** Compiled pattern for {@code .} used for generating file paths from package names. */
48      private static final Pattern FILEPATH_CONVERSION = Pattern.compile("\\.");
49  
50      /** Name tag of metadata XML files. */
51      private static final String XML_TAG_NAME = "name";
52  
53      /** Description tag of metadata XML files. */
54      private static final String XML_TAG_DESCRIPTION = "description";
55  
56      /** Default(UNIX) file separator. */
57      private static final String DEFAULT_FILE_SEPARATOR = "/";
58  
59      /**
60       * Do no allow {@code XmlMetaWriter} instances to be created.
61       */
62      private XmlMetaWriter() {
63      }
64  
65      /**
66       * Helper function to write module details to XML file.
67       *
68       * @param moduleDetails module details
69       * @throws TransformerException if a transformer exception occurs
70       * @throws ParserConfigurationException if a parser configuration exception occurs
71       */
72      public static void write(ModuleDetails moduleDetails) throws TransformerException,
73              ParserConfigurationException {
74          final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
75          dbFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
76          dbFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
77          final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
78          final Document doc = dBuilder.newDocument();
79  
80          final Element rootElement = doc.createElement("checkstyle-metadata");
81          final Element rootChild = doc.createElement("module");
82          rootElement.appendChild(rootChild);
83  
84          doc.appendChild(rootElement);
85  
86          final Element checkModule = doc.createElement(moduleDetails.getModuleType().getLabel());
87          rootChild.appendChild(checkModule);
88  
89          checkModule.setAttribute(XML_TAG_NAME, moduleDetails.getName());
90          checkModule.setAttribute("fully-qualified-name",
91                  moduleDetails.getFullQualifiedName());
92          checkModule.setAttribute("parent", moduleDetails.getParent());
93  
94          final Element desc = doc.createElement(XML_TAG_DESCRIPTION);
95          final Node cdataDesc = doc.createCDATASection(moduleDetails.getDescription());
96          desc.appendChild(cdataDesc);
97          checkModule.appendChild(desc);
98          createPropertySection(moduleDetails, checkModule, doc);
99          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 }