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.Collection; 029import java.util.List; 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.CheckstyleException; 040import com.puppycrawl.tools.checkstyle.site.JavadocScraperResultUtil; 041import com.puppycrawl.tools.checkstyle.site.ModuleJavadocParsingUtil; 042import com.puppycrawl.tools.checkstyle.site.PropertyDetails; 043import com.puppycrawl.tools.checkstyle.site.SiteUtil; 044 045/** Class which handles all the metadata generation and writing calls. */ 046public final class MetadataGeneratorUtil { 047 048 /** Stop instances being created. **/ 049 private MetadataGeneratorUtil() { 050 } 051 052 /** 053 * Generate metadata from the module source files available in the input argument path. 054 * 055 * @param path arguments 056 * @param moduleFolders folders to check 057 * @throws IOException ioException 058 * @throws CheckstyleException checkstyleException 059 */ 060 public static void generate(String path, String... moduleFolders) 061 throws IOException, CheckstyleException { 062 final List<File> modulesToProcess = 063 getTargetFiles(path, moduleFolders); 064 065 try { 066 for (File file : modulesToProcess) { 067 final String fileName = file.getName(); 068 069 if (fileName.startsWith("Abstract") 070 && !"AbstractClassNameCheck.java".equals(fileName)) { 071 continue; 072 } 073 074 final ModuleDetails moduleDetails = getModuleDetails(file); 075 writeMetadataFile(moduleDetails); 076 } 077 } 078 catch (MacroExecutionException macroException) { 079 throw new CheckstyleException("Failed to execute macro", macroException); 080 } 081 } 082 083 /** 084 * Generate metadata for the given file. 085 * 086 * @param file file to generate metadata for. 087 * @return module details. 088 * @throws MacroExecutionException macroExecutionException 089 */ 090 private static ModuleDetails getModuleDetails(File file) throws MacroExecutionException { 091 final String moduleName = SiteUtil.FINAL_CHECK.matcher(SiteUtil.getModuleName(file)) 092 .replaceAll(""); 093 094 final Object instance = SiteUtil.getModuleInstance(moduleName); 095 final Class<?> clss = instance.getClass(); 096 final String fullyQualifiedName = clss.getName(); 097 098 final String parentModule = SiteUtil.getParentModule(clss); 099 final Object parentModuleInstance = SiteUtil.getModuleInstance(parentModule); 100 final String parentModuleString = parentModuleInstance.getClass().getName(); 101 102 final ModuleType moduleType = getModuleType(moduleName); 103 104 final Set<String> messageKeys = SiteUtil.getMessageKeys(clss); 105 106 final String className = SiteUtil.getModuleName(file); 107 final Set<String> properties = SiteUtil.getPropertiesForDocumentation(clss, instance); 108 final Map<String, PropertyDetails> scrapedPropertyDetails = SiteUtil 109 .buildPropertyDetails(properties, className, file.toPath(), instance); 110 String description = JavadocScraperResultUtil.getModuleDescription(); 111 112 final String notes = JavadocScraperResultUtil.getModuleNotes(); 113 if (!notes.isEmpty()) { 114 description = description + "\n\n " + notes; 115 } 116 117 final List<ModulePropertyDetails> propertiesDetails = getPropertiesDetails( 118 scrapedPropertyDetails.values(), className, instance); 119 120 return new ModuleDetails(moduleName, fullyQualifiedName, parentModuleString, 121 description, moduleType, propertiesDetails, new ArrayList<>(messageKeys)); 122 } 123 124 /** 125 * Get module type(check/filter/filefilter) based on module name. 126 * 127 * @param moduleName module name. 128 * @return module type. 129 */ 130 private static ModuleType getModuleType(String moduleName) { 131 final ModuleType result; 132 if (moduleName.endsWith("FileFilter")) { 133 result = ModuleType.FILEFILTER; 134 } 135 else if (moduleName.endsWith("Filter")) { 136 result = ModuleType.FILTER; 137 } 138 else { 139 result = ModuleType.CHECK; 140 } 141 return result; 142 } 143 144 /** 145 * Get property details for the given property - name, description, type, default value. 146 * 147 * @param propertiesDetails property details list. 148 * @param className the class name of the module. 149 * @param instance the instance of the module. 150 * @return property details. 151 * @throws MacroExecutionException if an error occurs. 152 */ 153 private static List<ModulePropertyDetails> getPropertiesDetails( 154 Collection<PropertyDetails> propertiesDetails, 155 String className, Object instance) 156 throws MacroExecutionException { 157 final List<ModulePropertyDetails> result = new ArrayList<>(propertiesDetails.size()); 158 for (PropertyDetails details : propertiesDetails) { 159 final String property = details.getName(); 160 final String description = details.getDescription(); 161 final Field propertyField = SiteUtil.getField(instance.getClass(), property); 162 163 final String type = SiteUtil.getType(propertyField, property, className, instance); 164 165 final String defaultValue = getPropertyDefaultValue(details); 166 final String validationType = getValidationType(property, propertyField); 167 168 result.add(new ModulePropertyDetails(property, type, defaultValue, 169 validationType, description)); 170 } 171 return result; 172 } 173 174 /** 175 * Get default value for the given property. 176 * 177 * @param details the property details. 178 * @return default value. 179 */ 180 private static String getPropertyDefaultValue(PropertyDetails details) { 181 final String defaultValue; 182 if (details.getDefaultValueTokens().isEmpty()) { 183 final String raw = details.getDefaultValue(); 184 if ("{}".equals(raw)) { 185 defaultValue = ""; 186 } 187 else { 188 defaultValue = raw; 189 } 190 } 191 else { 192 defaultValue = String.join(SiteUtil.COMMA, details.getDefaultValueTokens()); 193 } 194 return defaultValue; 195 } 196 197 /** 198 * Write metadata file for the given module. 199 * 200 * @param moduleDetails module details. 201 * @throws CheckstyleException if an error occurs during writing metadata file. 202 */ 203 private static void writeMetadataFile(ModuleDetails moduleDetails) 204 throws CheckstyleException { 205 try { 206 XmlMetaWriter.write(moduleDetails); 207 } 208 catch (TransformerException | ParserConfigurationException example) { 209 throw new CheckstyleException( 210 "Failed to write metadata into XML file for module: " 211 + moduleDetails.getName(), example); 212 } 213 } 214 215 /** 216 * Get validation type for the given property. 217 * 218 * @param propertyName name of property. 219 * @param propertyField field of property. 220 * @return validation type. 221 */ 222 private static String getValidationType(String propertyName, Field propertyField) { 223 final String validationType; 224 if (SiteUtil.TOKENS.equals(propertyName) || SiteUtil.JAVADOC_TOKENS.equals(propertyName)) { 225 validationType = "tokenSet"; 226 } 227 else if (propertyField != null 228 && ModuleJavadocParsingUtil.isPropertySpecialTokenProp(propertyField)) { 229 validationType = "tokenTypesSet"; 230 } 231 else { 232 validationType = null; 233 } 234 return validationType; 235 } 236 237 /** 238 * Get files that represent modules. 239 * 240 * @param moduleFolders folders to check 241 * @param path rootPath 242 * @return files for scrapping javadoc and generation of metadata files 243 * @throws IOException ioException 244 */ 245 private static List<File> getTargetFiles(String path, String... moduleFolders) 246 throws IOException { 247 final List<File> validFiles = new ArrayList<>(); 248 for (String folder : moduleFolders) { 249 try (Stream<Path> files = Files.walk(Path.of(path + "/" + folder))) { 250 validFiles.addAll( 251 files.map(Path::toFile) 252 .filter(file -> { 253 final String fileName = file.getName(); 254 return fileName.endsWith("SuppressWarningsHolder.java") 255 || fileName.endsWith("Check.java") 256 || fileName.endsWith("Filter.java"); 257 }) 258 .toList()); 259 } 260 } 261 262 return validFiles; 263 } 264 265}