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}