001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 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.site;
021
022import java.io.File;
023import java.lang.reflect.Field;
024import java.util.Arrays;
025import java.util.List;
026import java.util.Locale;
027import java.util.Map;
028import java.util.Set;
029import java.util.stream.Collectors;
030
031import org.apache.maven.doxia.macro.AbstractMacro;
032import org.apache.maven.doxia.macro.Macro;
033import org.apache.maven.doxia.macro.MacroExecutionException;
034import org.apache.maven.doxia.macro.MacroRequest;
035import org.apache.maven.doxia.module.xdoc.XdocSink;
036import org.apache.maven.doxia.sink.Sink;
037import org.codehaus.plexus.component.annotations.Component;
038
039import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
040import com.puppycrawl.tools.checkstyle.api.DetailNode;
041import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
042import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
043import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
044import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
045
046/**
047 * A macro that inserts a table of properties for the given checkstyle module.
048 */
049@Component(role = Macro.class, hint = "properties")
050public class PropertiesMacro extends AbstractMacro {
051
052    /** A newline with 10 spaces of indentation. */
053    private static final String INDENT_LEVEL_10 = SiteUtil.getNewlineAndIndentSpaces(10);
054    /** A newline with 12 spaces of indentation. */
055    private static final String INDENT_LEVEL_12 = SiteUtil.getNewlineAndIndentSpaces(12);
056    /** A newline with 14 spaces of indentation. */
057    private static final String INDENT_LEVEL_14 = SiteUtil.getNewlineAndIndentSpaces(14);
058    /** A newline with 16 spaces of indentation. */
059    private static final String INDENT_LEVEL_16 = SiteUtil.getNewlineAndIndentSpaces(16);
060    /** A newline with 18 spaces of indentation. */
061    private static final String INDENT_LEVEL_18 = SiteUtil.getNewlineAndIndentSpaces(18);
062    /** A newline with 20 spaces of indentation. */
063    private static final String INDENT_LEVEL_20 = SiteUtil.getNewlineAndIndentSpaces(20);
064
065    /** The name of the current module being processed. */
066    private static String currentModuleName = "";
067
068    /** The file of the current module being processed. */
069    private static File currentModuleFile = new File("");
070
071    @Override
072    public void execute(Sink sink, MacroRequest request) throws MacroExecutionException {
073        // until https://github.com/checkstyle/checkstyle/issues/13426
074        if (!(sink instanceof XdocSink)) {
075            throw new MacroExecutionException("Expected Sink to be an XdocSink.");
076        }
077
078        final String modulePath = (String) request.getParameter("modulePath");
079        configureGlobalProperties(modulePath);
080
081        writePropertiesTable((XdocSink) sink);
082    }
083
084    /**
085     * Configures the global properties for the current module.
086     *
087     * @param modulePath the path of the current module processed.
088     */
089    private static void configureGlobalProperties(String modulePath) {
090        final File moduleFile = new File(modulePath);
091        currentModuleFile = moduleFile;
092        currentModuleName = CommonUtil.getFileNameWithoutExtension(moduleFile.getName());
093    }
094
095    /**
096     * Writes the properties table for the given module. Expects that the module has been processed
097     * with the ClassAndPropertiesSettersJavadocScraper before calling this method.
098     *
099     * @param sink the sink to write to.
100     * @throws MacroExecutionException if an error occurs during writing.
101     */
102    private static void writePropertiesTable(XdocSink sink)
103            throws MacroExecutionException {
104        sink.table();
105        sink.setInsertNewline(false);
106        sink.tableRows(null, false);
107        sink.rawText(INDENT_LEVEL_12);
108        writeTableHeaderRow(sink);
109        writeTablePropertiesRows(sink);
110        sink.rawText(INDENT_LEVEL_10);
111        sink.tableRows_();
112        sink.table_();
113        sink.setInsertNewline(true);
114    }
115
116    /**
117     * Writes the table header row with 5 columns - name, description, type, default value, since.
118     *
119     * @param sink sink to write to.
120     */
121    private static void writeTableHeaderRow(Sink sink) {
122        sink.tableRow();
123        writeTableHeaderCell(sink, "name");
124        writeTableHeaderCell(sink, "description");
125        writeTableHeaderCell(sink, "type");
126        writeTableHeaderCell(sink, "default value");
127        writeTableHeaderCell(sink, "since");
128        sink.rawText(INDENT_LEVEL_12);
129        sink.tableRow_();
130    }
131
132    /**
133     * Writes a table header cell with the given text.
134     *
135     * @param sink sink to write to.
136     * @param text the text to write.
137     */
138    private static void writeTableHeaderCell(Sink sink, String text) {
139        sink.rawText(INDENT_LEVEL_14);
140        sink.tableHeaderCell();
141        sink.text(text);
142        sink.tableHeaderCell_();
143    }
144
145    /**
146     * Writes the rows of the table with the 5 columns - name, description, type, default value,
147     * since. Each row corresponds to a property of the module.
148     *
149     * @param sink sink to write to.
150     * @throws MacroExecutionException if an error occurs during writing.
151     */
152    private static void writeTablePropertiesRows(Sink sink)
153            throws MacroExecutionException {
154        final Object instance = SiteUtil.getModuleInstance(currentModuleName);
155        final Class<?> clss = instance.getClass();
156
157        final Set<String> properties = SiteUtil.getPropertiesForDocumentation(clss, instance);
158        final Map<String, DetailNode> propertiesJavadocs = SiteUtil
159                .getPropertiesJavadocs(properties, currentModuleName, currentModuleFile);
160
161        for (String property : properties) {
162            final DetailNode propertyJavadoc = propertiesJavadocs.get(property);
163            final DetailNode currentModuleJavadoc = propertiesJavadocs.get(currentModuleName);
164            writePropertyRow(sink, property, propertyJavadoc, instance, currentModuleJavadoc);
165        }
166    }
167
168    /**
169     * Writes a table row with 5 columns for the given property - name, description, type,
170     * default value, since.
171     *
172     * @param sink sink to write to.
173     * @param propertyName the name of the property.
174     * @param propertyJavadoc the Javadoc of the property.
175     * @param instance the instance of the module.
176     * @param moduleJavadoc the Javadoc of the module.
177     * @throws MacroExecutionException if an error occurs during writing.
178     */
179    private static void writePropertyRow(Sink sink, String propertyName,
180                                         DetailNode propertyJavadoc, Object instance,
181                                            DetailNode moduleJavadoc)
182            throws MacroExecutionException {
183        final Field field = SiteUtil.getField(instance.getClass(), propertyName);
184
185        sink.rawText(INDENT_LEVEL_12);
186        sink.tableRow();
187
188        writePropertyNameCell(sink, propertyName);
189        writePropertyDescriptionCell(sink, propertyName, propertyJavadoc);
190        writePropertyTypeCell(sink, propertyName, field, instance);
191        writePropertyDefaultValueCell(sink, propertyName, field, instance);
192        writePropertySinceVersionCell(
193                sink, propertyName, moduleJavadoc, propertyJavadoc);
194
195        sink.rawText(INDENT_LEVEL_12);
196        sink.tableRow_();
197    }
198
199    /**
200     * Writes a table cell with the given property name.
201     *
202     * @param sink sink to write to.
203     * @param propertyName the name of the property.
204     */
205    private static void writePropertyNameCell(Sink sink, String propertyName) {
206        sink.rawText(INDENT_LEVEL_14);
207        sink.tableCell();
208        sink.text(propertyName);
209        sink.tableCell_();
210    }
211
212    /**
213     * Writes a table cell with the property description.
214     *
215     * @param sink sink to write to.
216     * @param propertyName the name of the property.
217     * @param propertyJavadoc the Javadoc of the property containing the description.
218     * @throws MacroExecutionException if an error occurs during retrieval of the description.
219     */
220    private static void writePropertyDescriptionCell(Sink sink, String propertyName,
221                                                     DetailNode propertyJavadoc)
222            throws MacroExecutionException {
223        sink.rawText(INDENT_LEVEL_14);
224        sink.tableCell();
225        final String description = SiteUtil
226                .getPropertyDescription(propertyName, propertyJavadoc, currentModuleName);
227        sink.rawText(description);
228        sink.tableCell_();
229    }
230
231    /**
232     * Writes a table cell with the property type.
233     *
234     * @param sink sink to write to.
235     * @param propertyName the name of the property.
236     * @param field the field of the property.
237     * @param instance the instance of the module.
238     * @throws MacroExecutionException if link to the property_types.html file cannot be
239     *                                 constructed.
240     */
241    private static void writePropertyTypeCell(Sink sink, String propertyName,
242                                              Field field, Object instance)
243            throws MacroExecutionException {
244        sink.rawText(INDENT_LEVEL_14);
245        sink.tableCell();
246
247        if (SiteUtil.TOKENS.equals(propertyName)) {
248            final AbstractCheck check = (AbstractCheck) instance;
249            if (check.getRequiredTokens().length == 0
250                    && Arrays.equals(check.getAcceptableTokens(), TokenUtil.getAllTokenIds())) {
251                sink.text(SiteUtil.TOKEN_TYPES);
252            }
253            else {
254                final List<String> configurableTokens = SiteUtil
255                        .getDifference(check.getAcceptableTokens(),
256                                check.getRequiredTokens())
257                        .stream()
258                        .map(TokenUtil::getTokenName)
259                        .collect(Collectors.toList());
260                sink.text("subset of tokens");
261                writeTokensList(sink, configurableTokens, SiteUtil.PATH_TO_TOKEN_TYPES);
262            }
263        }
264        else if (SiteUtil.JAVADOC_TOKENS.equals(propertyName)) {
265            final AbstractJavadocCheck check = (AbstractJavadocCheck) instance;
266            final List<String> configurableTokens = SiteUtil
267                    .getDifference(check.getAcceptableJavadocTokens(),
268                            check.getRequiredJavadocTokens())
269                    .stream()
270                    .map(JavadocUtil::getTokenName)
271                    .collect(Collectors.toList());
272            sink.text("subset of javadoc tokens");
273            writeTokensList(sink, configurableTokens, SiteUtil.PATH_TO_JAVADOC_TOKEN_TYPES);
274        }
275        else {
276            final String type = SiteUtil.getType(field, propertyName, currentModuleName, instance);
277            final String relativePathToPropertyTypes =
278                    SiteUtil.getLinkToDocument(currentModuleName, "property_types.xml");
279            final String escapedType = type
280                    .replace("[", ".5B")
281                    .replace("]", ".5D");
282            final String url =
283                    String.format(Locale.ROOT, "%s#%s", relativePathToPropertyTypes, escapedType);
284            sink.link(url);
285            sink.text(type);
286            sink.link_();
287        }
288        sink.tableCell_();
289    }
290
291    /**
292     * Write a list of tokens with links to the tokenTypesLink file.
293     *
294     * @param sink sink to write to.
295     * @param tokens the list of tokens to write.
296     * @param tokenTypesLink the link to the token types file.
297     * @throws MacroExecutionException if link to the tokenTypesLink file cannot be constructed.
298     */
299    private static void writeTokensList(Sink sink, List<String> tokens, String tokenTypesLink)
300            throws MacroExecutionException {
301        for (int index = 0; index < tokens.size(); index++) {
302            final String token = tokens.get(index);
303            sink.rawText(INDENT_LEVEL_16);
304            if (index != 0) {
305                sink.text(SiteUtil.COMMA_SPACE);
306            }
307            writeLinkToToken(sink, tokenTypesLink, token);
308        }
309        sink.rawText(INDENT_LEVEL_18);
310        sink.text(SiteUtil.DOT);
311        sink.rawText(INDENT_LEVEL_14);
312    }
313
314    /**
315     * Writes a link to the given token.
316     *
317     * @param sink sink to write to.
318     * @param document the document to link to.
319     * @param tokenName the name of the token.
320     * @throws MacroExecutionException if link to the document file cannot be constructed.
321     */
322    private static void writeLinkToToken(Sink sink, String document, String tokenName)
323            throws MacroExecutionException {
324        final String link = SiteUtil.getLinkToDocument(currentModuleName, document)
325                        + "#" + tokenName;
326        sink.link(link);
327        sink.rawText(INDENT_LEVEL_20);
328        sink.text(tokenName);
329        sink.link_();
330    }
331
332    /**
333     * Writes a table cell with the property default value.
334     *
335     * @param sink sink to write to.
336     * @param propertyName the name of the property.
337     * @param field the field of the property.
338     * @param instance the instance of the module.
339     * @throws MacroExecutionException if an error occurs during retrieval of the default value.
340     */
341    private static void writePropertyDefaultValueCell(Sink sink, String propertyName,
342                                                      Field field, Object instance)
343            throws MacroExecutionException {
344        sink.rawText(INDENT_LEVEL_14);
345        sink.tableCell();
346
347        if (SiteUtil.TOKENS.equals(propertyName)) {
348            final AbstractCheck check = (AbstractCheck) instance;
349            if (check.getRequiredTokens().length == 0
350                    && Arrays.equals(check.getDefaultTokens(), TokenUtil.getAllTokenIds())) {
351                sink.text(SiteUtil.TOKEN_TYPES);
352            }
353            else {
354                final List<String> configurableTokens = SiteUtil
355                        .getDifference(check.getDefaultTokens(),
356                                check.getRequiredTokens())
357                        .stream()
358                        .map(TokenUtil::getTokenName)
359                        .collect(Collectors.toList());
360                writeTokensList(sink, configurableTokens, SiteUtil.PATH_TO_TOKEN_TYPES);
361            }
362        }
363        else if (SiteUtil.JAVADOC_TOKENS.equals(propertyName)) {
364            final AbstractJavadocCheck check = (AbstractJavadocCheck) instance;
365            final List<String> configurableTokens = SiteUtil
366                    .getDifference(check.getDefaultJavadocTokens(),
367                            check.getRequiredJavadocTokens())
368                    .stream()
369                    .map(JavadocUtil::getTokenName)
370                    .collect(Collectors.toList());
371            writeTokensList(sink, configurableTokens, SiteUtil.PATH_TO_JAVADOC_TOKEN_TYPES);
372        }
373        else {
374            final String defaultValue = SiteUtil.getDefaultValue(
375                    propertyName, field, instance, currentModuleName);
376            sink.rawText("<code>");
377            sink.text(defaultValue);
378            sink.rawText("</code>");
379        }
380
381        sink.tableCell_();
382    }
383
384    /**
385     * Writes a table cell with the property since version.
386     *
387     * @param sink sink to write to.
388     * @param propertyName the name of the property.
389     * @param moduleJavadoc the Javadoc of the module.
390     * @param propertyJavadoc the Javadoc of the property containing the since version.
391     * @throws MacroExecutionException if an error occurs during retrieval of the since version.
392     */
393    private static void writePropertySinceVersionCell(Sink sink, String propertyName,
394                                                      DetailNode moduleJavadoc,
395                                                      DetailNode propertyJavadoc)
396            throws MacroExecutionException {
397        sink.rawText(INDENT_LEVEL_14);
398        sink.tableCell();
399        final String sinceVersion = SiteUtil.getSinceVersion(
400                currentModuleName, moduleJavadoc, propertyName, propertyJavadoc);
401        sink.text(sinceVersion);
402        sink.tableCell_();
403    }
404}