001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2021 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.ReflectionsException; 037import org.reflections.scanners.ResourcesScanner; 038import org.w3c.dom.Document; 039import org.w3c.dom.Element; 040import org.w3c.dom.NodeList; 041import org.xml.sax.SAXException; 042 043/** 044 * Class having utilities required to read module details from an XML metadata file of a module. 045 * This class is used by plugins that need load of metadata from XML files. 046 */ 047public final class XmlMetaReader { 048 049 /** Name tag of metadata XML files. */ 050 private static final String XML_TAG_NAME = "name"; 051 052 /** Description tag of metadata XML files. */ 053 private static final String XML_TAG_DESCRIPTION = "description"; 054 055 /** 056 * Do no allow {@code XmlMetaReader} instances to be created. 057 */ 058 private XmlMetaReader() { 059 } 060 061 /** 062 * Utility to load all the metadata files present in the checkstyle JAR including third parties' 063 * module metadata files. 064 * checkstyle metadata files are grouped in a folder hierarchy similar to that of their 065 * corresponding source files. 066 * Third party(e.g. SevNTU Checks) metadata files are prefixed with {@code checkstylemeta-} 067 * to their file names. 068 * 069 * @param thirdPartyPackages list of fully qualified third party package names(can be only a 070 * hint, e.g. for SevNTU it can be com.github.sevntu / com.github) 071 * @return list of module details found in the classpath satisfying the above conditions 072 * @throws IllegalStateException if there was a problem reading the module metadata files 073 */ 074 public static List<ModuleDetails> readAllModulesIncludingThirdPartyIfAny( 075 String... thirdPartyPackages) { 076 final Set<String> standardModuleFileNames = 077 new Reflections("com.puppycrawl.tools.checkstyle.meta", 078 new ResourcesScanner()).getResources(Pattern.compile(".*\\.xml")); 079 final Set<String> allMetadataSources = new HashSet<>(standardModuleFileNames); 080 for (String packageName : thirdPartyPackages) { 081 try { 082 final Set<String> thirdPartyModuleFileNames = 083 new Reflections(packageName, new ResourcesScanner()) 084 .getResources(Pattern.compile(".*checkstylemeta-.*\\.xml")); 085 allMetadataSources.addAll(thirdPartyModuleFileNames); 086 } 087 catch (ReflectionsException ex) { 088 if (propagateResourceScannerException(ex)) { 089 throw ex; 090 } 091 } 092 } 093 094 final List<ModuleDetails> result = new ArrayList<>(); 095 for (String fileName : allMetadataSources) { 096 final ModuleType moduleType; 097 if (fileName.endsWith("FileFilter.xml")) { 098 moduleType = ModuleType.FILEFILTER; 099 } 100 else if (fileName.endsWith("Filter.xml")) { 101 moduleType = ModuleType.FILTER; 102 } 103 else { 104 moduleType = ModuleType.CHECK; 105 } 106 final ModuleDetails moduleDetails; 107 try { 108 moduleDetails = read(XmlMetaReader.class.getResourceAsStream("/" + fileName), 109 moduleType); 110 } 111 catch (ParserConfigurationException | IOException | SAXException ex) { 112 throw new IllegalStateException("Problem to read all modules including third " 113 + "party if any. Problem detected at file: " + fileName, ex); 114 } 115 result.add(moduleDetails); 116 } 117 118 return result; 119 } 120 121 /** 122 * Checks if exception should be suppressed that matches the following 123 * because the reflections library throws this exception wrongly on valid 124 * packages with no resources to be found. Packages may be only checks with 125 * no resources or checks with resources. This issue is until 126 * https://github.com/ronmamo/reflections/issues/273. 127 * 128 * @param exception The exception to examine. 129 * @return {@code true} if the exception should be propagated. 130 */ 131 private static boolean propagateResourceScannerException(ReflectionsException exception) { 132 return !"Scanner ResourcesScanner was not configured".equals(exception.getMessage()); 133 } 134 135 /** 136 * Read the module details from the supplied input stream of the module's XML metadata file. 137 * 138 * @param moduleMetadataStream input stream object of a module's metadata file 139 * @param moduleType type of module 140 * @return module detail object extracted from the XML metadata file 141 * @throws ParserConfigurationException if a parser configuration exception occurs 142 * @throws IOException if a IO exception occurs 143 * @throws SAXException if a SAX exception occurs during parsing the XML file 144 */ 145 public static ModuleDetails read(InputStream moduleMetadataStream, ModuleType moduleType) 146 throws ParserConfigurationException, IOException, SAXException { 147 ModuleDetails result = null; 148 if (moduleType != null) { 149 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 150 factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); 151 factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); 152 final DocumentBuilder builder = factory.newDocumentBuilder(); 153 final Document document = builder.parse(moduleMetadataStream); 154 final Element root = document.getDocumentElement(); 155 final Element element = getDirectChildsByTag(root, "module").get(0); 156 final ModuleDetails moduleDetails = new ModuleDetails(); 157 final Element module = getDirectChildsByTag(element, moduleType.getLabel()).get(0); 158 moduleDetails.setModuleType(moduleType); 159 result = createModule(module, moduleDetails); 160 } 161 return result; 162 } 163 164 /** 165 * Create the module detail object from XML metadata. 166 * 167 * @param mod root XML document element 168 * @param moduleDetails module detail object, which is to be updated 169 * @return module detail object containing all metadata 170 */ 171 private static ModuleDetails createModule(Element mod, ModuleDetails moduleDetails) { 172 moduleDetails.setName(getAttributeValue(mod, XML_TAG_NAME)); 173 moduleDetails.setFullQualifiedName(getAttributeValue(mod, "fully-qualified-name")); 174 moduleDetails.setParent(getAttributeValue(mod, "parent")); 175 moduleDetails.setDescription(getDirectChildsByTag(mod, XML_TAG_DESCRIPTION).get(0) 176 .getFirstChild().getNodeValue()); 177 final List<Element> properties = getDirectChildsByTag(mod, "properties"); 178 if (!properties.isEmpty()) { 179 final List<ModulePropertyDetails> modulePropertyDetailsList = 180 createProperties(properties.get(0)); 181 moduleDetails.addToProperties(modulePropertyDetailsList); 182 } 183 final List<String> messageKeys = 184 getListContentByAttribute(mod, 185 "message-keys", "message-key", "key"); 186 if (messageKeys != null) { 187 moduleDetails.addToViolationMessages(messageKeys); 188 } 189 return moduleDetails; 190 } 191 192 /** 193 * Create module property details from the XML metadata. 194 * 195 * @param properties parent document element which contains property's metadata 196 * @return list of property details object created 197 */ 198 private static List<ModulePropertyDetails> createProperties(Element properties) { 199 final List<ModulePropertyDetails> result = new ArrayList<>(); 200 final NodeList propertyList = properties.getElementsByTagName("property"); 201 for (int i = 0; i < propertyList.getLength(); i++) { 202 final ModulePropertyDetails propertyDetails = new ModulePropertyDetails(); 203 final Element prop = (Element) propertyList.item(i); 204 propertyDetails.setName(getAttributeValue(prop, XML_TAG_NAME)); 205 propertyDetails.setType(getAttributeValue(prop, "type")); 206 final String defaultValueTag = "default-value"; 207 if (prop.hasAttribute(defaultValueTag)) { 208 propertyDetails.setDefaultValue(getAttributeValue(prop, defaultValueTag)); 209 } 210 final String validationTypeTag = "validation-type"; 211 if (prop.hasAttribute(validationTypeTag)) { 212 propertyDetails.setValidationType(getAttributeValue(prop, validationTypeTag)); 213 } 214 propertyDetails.setDescription(getDirectChildsByTag(prop, XML_TAG_DESCRIPTION) 215 .get(0).getFirstChild().getNodeValue()); 216 result.add(propertyDetails); 217 } 218 return result; 219 } 220 221 /** 222 * Utility to get the list contents by the attribute specified. 223 * 224 * @param element doc element 225 * @param listParent parent element of list 226 * @param listOption child list element 227 * @param attribute attribute key 228 * @return list of strings containing the XML list data 229 */ 230 private static List<String> getListContentByAttribute(Element element, String listParent, 231 String listOption, String attribute) { 232 final List<Element> children = getDirectChildsByTag(element, listParent); 233 List<String> result = null; 234 if (!children.isEmpty()) { 235 final NodeList nodeList = children.get(0).getElementsByTagName(listOption); 236 final List<String> listContent = new ArrayList<>(); 237 for (int j = 0; j < nodeList.getLength(); j++) { 238 listContent.add(getAttributeValue((Element) nodeList.item(j), attribute)); 239 } 240 result = listContent; 241 } 242 return result; 243 } 244 245 /** 246 * Utility to get the children of an element by tag name. 247 * 248 * @param element parent element 249 * @param sTagName tag name of children required 250 * @return list of elements retrieved 251 */ 252 private static List<Element> getDirectChildsByTag(Element element, String sTagName) { 253 final NodeList children = element.getElementsByTagName(sTagName); 254 final List<Element> res = new ArrayList<>(); 255 for (int i = 0; i < children.getLength(); i++) { 256 if (children.item(i).getParentNode().equals(element)) { 257 res.add((Element) children.item(i)); 258 } 259 } 260 return res; 261 } 262 263 /** 264 * Utility to get attribute value of an element. 265 * 266 * @param element target element 267 * @param attribute attribute key 268 * @return attribute value 269 */ 270 private static String getAttributeValue(Element element, String attribute) { 271 return element.getAttributes().getNamedItem(attribute).getNodeValue(); 272 } 273 274}