View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.meta;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.lang.reflect.Field;
25  import java.nio.file.Files;
26  import java.nio.file.Path;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.Map;
31  import java.util.Set;
32  import java.util.stream.Stream;
33  
34  import javax.xml.parsers.ParserConfigurationException;
35  import javax.xml.transform.TransformerException;
36  
37  import org.apache.maven.doxia.macro.MacroExecutionException;
38  
39  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
40  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
41  import com.puppycrawl.tools.checkstyle.api.DetailNode;
42  import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
43  import com.puppycrawl.tools.checkstyle.site.ModuleJavadocParsingUtil;
44  import com.puppycrawl.tools.checkstyle.site.SiteUtil;
45  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
46  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
47  
48  /** Class which handles all the metadata generation and writing calls. */
49  public final class MetadataGeneratorUtil {
50  
51      /** Stop instances being created. **/
52      private MetadataGeneratorUtil() {
53      }
54  
55      /**
56       * Generate metadata from the module source files available in the input argument path.
57       *
58       * @param path arguments
59       * @param moduleFolders folders to check
60       * @throws IOException ioException
61       * @throws CheckstyleException checkstyleException
62       */
63      public static void generate(String path, String... moduleFolders)
64              throws IOException, CheckstyleException {
65          final List<File> modulesToProcess =
66              getTargetFiles(path, moduleFolders);
67  
68          try {
69              for (File file : modulesToProcess) {
70                  final String fileName = file.getName();
71  
72                  if (fileName.startsWith("Abstract")
73                      && !"AbstractClassNameCheck.java".equals(fileName)) {
74                      continue;
75                  }
76  
77                  final ModuleDetails moduleDetails = getModuleDetails(file);
78                  writeMetadataFile(moduleDetails);
79              }
80          }
81          catch (MacroExecutionException macroException) {
82              throw new CheckstyleException(macroException.getMessage(), macroException);
83          }
84      }
85  
86      /**
87       * Generate metadata for the given file.
88       *
89       * @param file file to generate metadata for.
90       * @return module details.
91       * @throws MacroExecutionException macroExecutionException
92       */
93      private static ModuleDetails getModuleDetails(File file) throws MacroExecutionException {
94          final String moduleName = SiteUtil.FINAL_CHECK.matcher(SiteUtil.getModuleName(file))
95              .replaceAll("");
96  
97          final Object instance = SiteUtil.getModuleInstance(moduleName);
98          final Class<?> clss = instance.getClass();
99          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 }