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