View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.io.IOException;
23  import java.io.InputStream;
24  import java.util.ArrayList;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Set;
28  import java.util.regex.Pattern;
29  
30  import javax.xml.XMLConstants;
31  import javax.xml.parsers.DocumentBuilder;
32  import javax.xml.parsers.DocumentBuilderFactory;
33  import javax.xml.parsers.ParserConfigurationException;
34  
35  import org.reflections.Reflections;
36  import org.reflections.scanners.Scanners;
37  import org.w3c.dom.Document;
38  import org.w3c.dom.Element;
39  import org.w3c.dom.NodeList;
40  import org.xml.sax.SAXException;
41  
42  /**
43   * Class having utilities required to read module details from an XML metadata file of a module.
44   * This class is used by plugins that need load of metadata from XML files.
45   */
46  public final class XmlMetaReader {
47  
48      /** Name tag of metadata XML files. */
49      private static final String XML_TAG_NAME = "name";
50  
51      /** Description tag of metadata XML files. */
52      private static final String XML_TAG_DESCRIPTION = "description";
53  
54      /**
55       * Do no allow {@code XmlMetaReader} instances to be created.
56       */
57      private XmlMetaReader() {
58      }
59  
60      /**
61       * Utility to load all the metadata files present in the checkstyle JAR including third parties'
62       * module metadata files.
63       * checkstyle metadata files are grouped in a folder hierarchy similar to that of their
64       * corresponding source files.
65       * Third party(e.g. SevNTU Checks) metadata files are prefixed with {@code checkstylemeta-}
66       * to their file names.
67       *
68       * @param thirdPartyPackages fully qualified third party package names(can be only a
69       *                           hint, e.g. for SevNTU it can be com.github.sevntu / com.github)
70       * @return list of module details found in the classpath satisfying the above conditions
71       * @throws IllegalStateException if there was a problem reading the module metadata files
72       */
73      public static List<ModuleDetails> readAllModulesIncludingThirdPartyIfAny(
74              String... thirdPartyPackages) {
75          final Set<String> standardModuleFileNames = new Reflections(
76                  "com.puppycrawl.tools.checkstyle.meta", Scanners.Resources)
77                  .getResources(Pattern.compile(".*\\.xml"));
78          final Set<String> allMetadataSources = new HashSet<>(standardModuleFileNames);
79          for (String packageName : thirdPartyPackages) {
80              final Set<String> thirdPartyModuleFileNames =
81                      new Reflections(packageName, Scanners.Resources)
82                              .getResources(Pattern.compile(".*checkstylemeta-.*\\.xml"));
83              allMetadataSources.addAll(thirdPartyModuleFileNames);
84          }
85  
86          final List<ModuleDetails> result = new ArrayList<>(allMetadataSources.size());
87          for (String fileName : allMetadataSources) {
88              final ModuleType moduleType;
89              if (fileName.endsWith("FileFilter.xml")) {
90                  moduleType = ModuleType.FILEFILTER;
91              }
92              else if (fileName.endsWith("Filter.xml")) {
93                  moduleType = ModuleType.FILTER;
94              }
95              else {
96                  moduleType = ModuleType.CHECK;
97              }
98              final ModuleDetails moduleDetails;
99              try {
100                 moduleDetails = read(XmlMetaReader.class.getResourceAsStream("/" + fileName),
101                         moduleType);
102             }
103             catch (ParserConfigurationException | IOException | SAXException ex) {
104                 throw new IllegalStateException("Problem to read all modules including third "
105                         + "party if any. Problem detected at file: " + fileName, ex);
106             }
107             result.add(moduleDetails);
108         }
109 
110         return result;
111     }
112 
113     /**
114      * Read the module details from the supplied input stream of the module's XML metadata file.
115      *
116      * @param moduleMetadataStream input stream object of a module's metadata file
117      * @param moduleType type of module
118      * @return module detail object extracted from the XML metadata file
119      * @throws ParserConfigurationException if a parser configuration exception occurs
120      * @throws IOException if a IO exception occurs
121      * @throws SAXException if a SAX exception occurs during parsing the XML file
122      */
123     public static ModuleDetails read(InputStream moduleMetadataStream, ModuleType moduleType)
124             throws ParserConfigurationException, IOException, SAXException {
125         ModuleDetails result = null;
126         if (moduleType != null) {
127             final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
128             factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
129             factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
130             final DocumentBuilder builder = factory.newDocumentBuilder();
131             final Document document = builder.parse(moduleMetadataStream);
132             final Element root = document.getDocumentElement();
133             final Element element = getDirectChildsByTag(root, "module").get(0);
134             final Element module = getDirectChildsByTag(element, moduleType.getLabel()).get(0);
135             result = new ModuleDetails();
136 
137             result.setModuleType(moduleType);
138             populateModule(module, result);
139         }
140         return result;
141     }
142 
143     /**
144      * Populate the module detail object from XML metadata.
145      *
146      * @param mod root XML document element
147      * @param moduleDetails module detail object, which is to be updated
148      */
149     private static void populateModule(Element mod, ModuleDetails moduleDetails) {
150         moduleDetails.setName(getAttributeValue(mod, XML_TAG_NAME));
151         moduleDetails.setFullQualifiedName(getAttributeValue(mod, "fully-qualified-name"));
152         moduleDetails.setParent(getAttributeValue(mod, "parent"));
153         moduleDetails.setDescription(getDirectChildsByTag(mod, XML_TAG_DESCRIPTION).get(0)
154                 .getFirstChild().getNodeValue());
155         final List<Element> properties = getDirectChildsByTag(mod, "properties");
156         if (!properties.isEmpty()) {
157             final List<ModulePropertyDetails> modulePropertyDetailsList =
158                     createProperties(properties.get(0));
159             moduleDetails.addToProperties(modulePropertyDetailsList);
160         }
161         final List<String> messageKeys =
162                 getListContentByAttribute(mod,
163                         "message-keys", "message-key", "key");
164         if (messageKeys != null) {
165             moduleDetails.addToViolationMessages(messageKeys);
166         }
167     }
168 
169     /**
170      * Create module property details from the XML metadata.
171      *
172      * @param properties parent document element which contains property's metadata
173      * @return list of property details object created
174      */
175     private static List<ModulePropertyDetails> createProperties(Element properties) {
176         final NodeList propertyList = properties.getElementsByTagName("property");
177         final int propertyListLength = propertyList.getLength();
178         final List<ModulePropertyDetails> result = new ArrayList<>(propertyListLength);
179         for (int i = 0; i < propertyListLength; i++) {
180             final ModulePropertyDetails propertyDetails = new ModulePropertyDetails();
181             final Element prop = (Element) propertyList.item(i);
182             propertyDetails.setName(getAttributeValue(prop, XML_TAG_NAME));
183             propertyDetails.setType(getAttributeValue(prop, "type"));
184             final String defaultValueTag = "default-value";
185             if (prop.hasAttribute(defaultValueTag)) {
186                 propertyDetails.setDefaultValue(getAttributeValue(prop, defaultValueTag));
187             }
188             final String validationTypeTag = "validation-type";
189             if (prop.hasAttribute(validationTypeTag)) {
190                 propertyDetails.setValidationType(getAttributeValue(prop, validationTypeTag));
191             }
192             propertyDetails.setDescription(getDirectChildsByTag(prop, XML_TAG_DESCRIPTION)
193                     .get(0).getFirstChild().getNodeValue());
194             result.add(propertyDetails);
195         }
196         return result;
197     }
198 
199     /**
200      * Utility to get the list contents by the attribute specified.
201      *
202      * @param element doc element
203      * @param listParent parent element of list
204      * @param listOption child list element
205      * @param attribute attribute key
206      * @return list of strings containing the XML list data
207      */
208     private static List<String> getListContentByAttribute(Element element, String listParent,
209                                                          String listOption, String attribute) {
210         final List<Element> children = getDirectChildsByTag(element, listParent);
211         List<String> result = null;
212         if (!children.isEmpty()) {
213             final NodeList nodeList = children.get(0).getElementsByTagName(listOption);
214             final int nodeListLength = nodeList.getLength();
215             final List<String> listContent = new ArrayList<>(nodeListLength);
216             for (int j = 0; j < nodeListLength; j++) {
217                 listContent.add(getAttributeValue((Element) nodeList.item(j), attribute));
218             }
219             result = listContent;
220         }
221         return result;
222     }
223 
224     /**
225      * Utility to get the children of an element by tag name.
226      *
227      * @param element parent element
228      * @param sTagName tag name of children required
229      * @return list of elements retrieved
230      */
231     private static List<Element> getDirectChildsByTag(Element element, String sTagName) {
232         final NodeList children = element.getElementsByTagName(sTagName);
233         final List<Element> res = new ArrayList<>();
234         for (int i = 0; i < children.getLength(); i++) {
235             if (children.item(i).getParentNode().equals(element)) {
236                 res.add((Element) children.item(i));
237             }
238         }
239         return res;
240     }
241 
242     /**
243      * Utility to get attribute value of an element.
244      *
245      * @param element target element
246      * @param attribute attribute key
247      * @return attribute value
248      */
249     private static String getAttributeValue(Element element, String attribute) {
250         return element.getAttributes().getNamedItem(attribute).getNodeValue();
251     }
252 
253 }