001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.File; 023import java.io.IOException; 024import java.lang.reflect.Field; 025import java.nio.file.Files; 026import java.nio.file.Path; 027import java.util.ArrayList; 028import java.util.List; 029import java.util.Locale; 030import java.util.Map; 031import java.util.Set; 032import java.util.stream.Stream; 033 034import javax.xml.parsers.ParserConfigurationException; 035import javax.xml.transform.TransformerException; 036 037import org.apache.maven.doxia.macro.MacroExecutionException; 038 039import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 040import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 041import com.puppycrawl.tools.checkstyle.api.DetailNode; 042import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck; 043import com.puppycrawl.tools.checkstyle.site.ModuleJavadocParsingUtil; 044import com.puppycrawl.tools.checkstyle.site.SiteUtil; 045import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 046import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 047 048/** Class which handles all the metadata generation and writing calls. */ 049public final class MetadataGeneratorUtil { 050 051 /** Stop instances being created. **/ 052 private MetadataGeneratorUtil() { 053 } 054 055 /** 056 * Generate metadata from the module source files available in the input argument path. 057 * 058 * @param path arguments 059 * @param moduleFolders folders to check 060 * @throws IOException ioException 061 * @throws CheckstyleException checkstyleException 062 */ 063 public static void generate(String path, String... moduleFolders) 064 throws IOException, CheckstyleException { 065 final List<File> modulesToProcess = 066 getTargetFiles(path, moduleFolders); 067 068 try { 069 for (File file : modulesToProcess) { 070 final String fileName = file.getName(); 071 072 if (fileName.startsWith("Abstract") 073 && !"AbstractClassNameCheck.java".equals(fileName)) { 074 continue; 075 } 076 077 final ModuleDetails moduleDetails = getModuleDetails(file); 078 writeMetadataFile(moduleDetails); 079 } 080 } 081 catch (MacroExecutionException macroException) { 082 throw new CheckstyleException(macroException.getMessage(), macroException); 083 } 084 } 085 086 /** 087 * Generate metadata for the given file. 088 * 089 * @param file file to generate metadata for. 090 * @return module details. 091 * @throws MacroExecutionException macroExecutionException 092 */ 093 private static ModuleDetails getModuleDetails(File file) throws MacroExecutionException { 094 final String moduleName = SiteUtil.FINAL_CHECK.matcher(SiteUtil.getModuleName(file)) 095 .replaceAll(""); 096 097 final Object instance = SiteUtil.getModuleInstance(moduleName); 098 final Class<?> clss = instance.getClass(); 099 final String fullyQualifiedName = clss.getName(); 100 101 final String parentModule = SiteUtil.getParentModule(clss); 102 final Object parentModuleInstance = SiteUtil.getModuleInstance(parentModule); 103 final String parentModuleString = parentModuleInstance.getClass().getName(); 104 105 final ModuleType moduleType = getModuleType(moduleName); 106 107 final Set<String> messageKeys = SiteUtil.getMessageKeys(clss); 108 109 final String className = SiteUtil.getModuleName(file); 110 final Set<String> properties = SiteUtil.getPropertiesForDocumentation(clss, instance); 111 final Map<String, DetailNode> propertiesJavadocs = SiteUtil 112 .getPropertiesJavadocs(properties, className, file.toPath()); 113 final DetailNode moduleJavadoc = SiteUtil.getModuleJavadoc(className, file.toPath()); 114 String description = ModuleJavadocParsingUtil 115 .getModuleDescription(moduleJavadoc); 116 117 final String notes = ModuleJavadocParsingUtil.getModuleNotes(moduleJavadoc); 118 if (!notes.isEmpty()) { 119 description = description + "\n\n " + notes; 120 } 121 122 final List<ModulePropertyDetails> propertiesDetails = getPropertiesDetails( 123 properties, propertiesJavadocs, className, instance); 124 125 return new ModuleDetails(moduleName, fullyQualifiedName, parentModuleString, 126 description, moduleType, propertiesDetails, new ArrayList<>(messageKeys)); 127 } 128 129 /** 130 * Get module type(check/filter/filefilter) based on module name. 131 * 132 * @param moduleName module name. 133 * @return module type. 134 */ 135 private static ModuleType getModuleType(String moduleName) { 136 final ModuleType result; 137 if (moduleName.endsWith("FileFilter")) { 138 result = ModuleType.FILEFILTER; 139 } 140 else if (moduleName.endsWith("Filter")) { 141 result = ModuleType.FILTER; 142 } 143 else { 144 result = ModuleType.CHECK; 145 } 146 return result; 147 } 148 149 /** 150 * Get property details for the given property - name, description, type, default value. 151 * 152 * @param properties properties of the module. 153 * @param javadocs javadocs of the module. 154 * @param className the class name of the module. 155 * @param instance the instance of the module. 156 * @return property details. 157 * @throws MacroExecutionException if an error occurs. 158 */ 159 private static List<ModulePropertyDetails> getPropertiesDetails( 160 Set<String> properties, Map<String, DetailNode> javadocs, 161 String className, Object instance) 162 throws MacroExecutionException { 163 final List<ModulePropertyDetails> result = new ArrayList<>(properties.size()); 164 for (String property : properties) { 165 final String description = getPropertyDescription(property, 166 javadocs.get(property)); 167 final Field propertyField = SiteUtil.getField(instance.getClass(), property); 168 169 final String type = SiteUtil.getType(propertyField, property, className, instance); 170 171 final String defaultValue = getPropertyDefaultValue(property, propertyField, instance, 172 className); 173 final String validationType = getValidationType(property, propertyField); 174 175 result.add(new ModulePropertyDetails(property, type, defaultValue, 176 validationType, description)); 177 } 178 return result; 179 } 180 181 /** 182 * Get default value for the given property. 183 * 184 * @param property property name. 185 * @param field field. 186 * @param instance instance of the module. 187 * @param className class name of the module. 188 * @return default value. 189 * @throws MacroExecutionException if an error occurs. 190 */ 191 private static String getPropertyDefaultValue(String property, Field field, Object instance, 192 String className) throws MacroExecutionException { 193 final String defaultValue; 194 if (SiteUtil.TOKENS.equals(property)) { 195 final AbstractCheck check = (AbstractCheck) instance; 196 final List<String> configurableTokens = SiteUtil 197 .getDifference(check.getDefaultTokens(), 198 check.getRequiredTokens()) 199 .stream() 200 .map(TokenUtil::getTokenName) 201 .toList(); 202 defaultValue = String.join(SiteUtil.COMMA, configurableTokens); 203 } 204 else if (SiteUtil.JAVADOC_TOKENS.equals(property)) { 205 final AbstractJavadocCheck check = (AbstractJavadocCheck) instance; 206 final List<String> configurableTokens = SiteUtil 207 .getDifference(check.getDefaultJavadocTokens(), 208 check.getRequiredJavadocTokens()) 209 .stream() 210 .map(JavadocUtil::getTokenName) 211 .toList(); 212 defaultValue = String.join(SiteUtil.COMMA, configurableTokens); 213 } 214 else { 215 defaultValue = SiteUtil.getDefaultValue(property, field, instance, className); 216 } 217 return defaultValue; 218 } 219 220 /** 221 * Write metadata file for the given module. 222 * 223 * @param moduleDetails module details. 224 * @throws CheckstyleException if an error occurs during writing metadata file. 225 */ 226 private static void writeMetadataFile(ModuleDetails moduleDetails) 227 throws CheckstyleException { 228 try { 229 XmlMetaWriter.write(moduleDetails); 230 } 231 catch (TransformerException | ParserConfigurationException example) { 232 throw new CheckstyleException( 233 "Failed to write metadata into XML file for module: " 234 + moduleDetails.getName(), example); 235 } 236 } 237 238 /** 239 * Get validation type for the given property. 240 * 241 * @param propertyName name of property. 242 * @param propertyField field of property. 243 * @return validation type. 244 */ 245 private static String getValidationType(String propertyName, Field propertyField) { 246 final String validationType; 247 if (SiteUtil.TOKENS.equals(propertyName) || SiteUtil.JAVADOC_TOKENS.equals(propertyName)) { 248 validationType = "tokenSet"; 249 } 250 else if (propertyField != null 251 && ModuleJavadocParsingUtil.isPropertySpecialTokenProp(propertyField)) { 252 validationType = "tokenTypesSet"; 253 } 254 else { 255 validationType = null; 256 } 257 return validationType; 258 } 259 260 /** 261 * Get property description from property javadoc. 262 * 263 * @param property property name. 264 * @param propertyJavadoc property javadoc. 265 * @return property description. 266 */ 267 private static String getPropertyDescription(String property, DetailNode propertyJavadoc) { 268 final String propertyDescription; 269 if (SiteUtil.TOKENS.equals(property)) { 270 propertyDescription = "tokens to check"; 271 } 272 else if (SiteUtil.JAVADOC_TOKENS.equals(property)) { 273 propertyDescription = "javadoc tokens to check"; 274 } 275 else { 276 final String firstJavadocParagraph = SiteUtil 277 .getFirstParagraphFromJavadoc(propertyJavadoc); 278 final String setterToString = "Setter to "; 279 280 if (firstJavadocParagraph.contains(setterToString)) { 281 final String unprocessedPropertyDescription = firstJavadocParagraph 282 .substring(setterToString.length()); 283 final String firstLetterCapitalized = unprocessedPropertyDescription 284 .substring(0, 1) 285 .toUpperCase(Locale.ROOT); 286 287 propertyDescription = firstLetterCapitalized 288 + unprocessedPropertyDescription.substring(1); 289 } 290 else { 291 propertyDescription = firstJavadocParagraph; 292 } 293 294 } 295 return propertyDescription; 296 } 297 298 /** 299 * Get files that represent modules. 300 * 301 * @param moduleFolders folders to check 302 * @param path rootPath 303 * @return files for scrapping javadoc and generation of metadata files 304 * @throws IOException ioException 305 */ 306 private static List<File> getTargetFiles(String path, String... moduleFolders) 307 throws IOException { 308 final List<File> validFiles = new ArrayList<>(); 309 for (String folder : moduleFolders) { 310 try (Stream<Path> files = Files.walk(Path.of(path + "/" + folder))) { 311 validFiles.addAll( 312 files.map(Path::toFile) 313 .filter(file -> { 314 final String fileName = file.getName(); 315 return fileName.endsWith("SuppressWarningsHolder.java") 316 || fileName.endsWith("Check.java") 317 || fileName.endsWith("Filter.java"); 318 }) 319 .toList()); 320 } 321 } 322 323 return validFiles; 324 } 325}