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.IOException; 023import java.io.InputStream; 024import java.util.ArrayList; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Set; 028import java.util.regex.Pattern; 029 030import javax.xml.XMLConstants; 031import javax.xml.parsers.DocumentBuilder; 032import javax.xml.parsers.DocumentBuilderFactory; 033import javax.xml.parsers.ParserConfigurationException; 034 035import org.reflections.Reflections; 036import org.reflections.scanners.Scanners; 037import org.w3c.dom.Document; 038import org.w3c.dom.Element; 039import org.w3c.dom.NodeList; 040import org.xml.sax.SAXException; 041 042/** 043 * Class having utilities required to read module details from an XML metadata file of a module. 044 * This class is used by plugins that need load of metadata from XML files. 045 */ 046public final class XmlMetaReader { 047 048 /** Name tag of metadata XML files. */ 049 private static final String XML_TAG_NAME = "name"; 050 051 /** Description tag of metadata XML files. */ 052 private static final String XML_TAG_DESCRIPTION = "description"; 053 054 /** 055 * Do no allow {@code XmlMetaReader} instances to be created. 056 */ 057 private XmlMetaReader() { 058 } 059 060 /** 061 * Utility to load all the metadata files present in the checkstyle JAR including third parties' 062 * module metadata files. 063 * checkstyle metadata files are grouped in a folder hierarchy similar to that of their 064 * corresponding source files. 065 * Third party(e.g. SevNTU Checks) metadata files are prefixed with {@code checkstylemeta-} 066 * to their file names. 067 * 068 * @param thirdPartyPackages fully qualified third party package names(can be only a 069 * hint, e.g. for SevNTU it can be com.github.sevntu / com.github) 070 * @return list of module details found in the classpath satisfying the above conditions 071 * @throws IllegalStateException if there was a problem reading the module metadata files 072 */ 073 public static List<ModuleDetails> readAllModulesIncludingThirdPartyIfAny( 074 String... thirdPartyPackages) { 075 final Set<String> standardModuleFileNames = new Reflections( 076 "com.puppycrawl.tools.checkstyle.meta", Scanners.Resources) 077 .getResources(Pattern.compile(".*\\.xml")); 078 final Set<String> allMetadataSources = new HashSet<>(standardModuleFileNames); 079 for (String packageName : thirdPartyPackages) { 080 final Set<String> thirdPartyModuleFileNames = 081 new Reflections(packageName, Scanners.Resources) 082 .getResources(Pattern.compile(".*checkstylemeta-.*\\.xml")); 083 allMetadataSources.addAll(thirdPartyModuleFileNames); 084 } 085 086 final List<ModuleDetails> result = new ArrayList<>(allMetadataSources.size()); 087 for (String fileName : allMetadataSources) { 088 final ModuleType moduleType; 089 if (fileName.endsWith("FileFilter.xml")) { 090 moduleType = ModuleType.FILEFILTER; 091 } 092 else if (fileName.endsWith("Filter.xml")) { 093 moduleType = ModuleType.FILTER; 094 } 095 else { 096 moduleType = ModuleType.CHECK; 097 } 098 final ModuleDetails moduleDetails; 099 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}