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.site;
021
022import java.net.URLEncoder;
023import java.nio.charset.StandardCharsets;
024import java.util.Set;
025
026import org.apache.maven.doxia.macro.AbstractMacro;
027import org.apache.maven.doxia.macro.Macro;
028import org.apache.maven.doxia.macro.MacroExecutionException;
029import org.apache.maven.doxia.macro.MacroRequest;
030import org.apache.maven.doxia.module.xdoc.XdocSink;
031import org.apache.maven.doxia.sink.Sink;
032import org.codehaus.plexus.component.annotations.Component;
033
034/**
035 * A macro that inserts a list of the violation messages.
036 */
037@Component(role = Macro.class, hint = "violation-messages")
038public class ViolationMessagesMacro extends AbstractMacro {
039    @Override
040    public void execute(Sink sink, MacroRequest request) throws MacroExecutionException {
041        // until https://github.com/checkstyle/checkstyle/issues/13426
042        if (!(sink instanceof XdocSink xdocSink)) {
043            throw new MacroExecutionException("Expected Sink to be an XdocSink.");
044        }
045        final String checkName = (String) request.getParameter("checkName");
046        final Object instance = SiteUtil.getModuleInstance(checkName);
047        final Class<?> clss = instance.getClass();
048        final Set<String> messageKeys = SiteUtil.getMessageKeys(clss);
049        createListOfMessages(xdocSink, clss, messageKeys);
050    }
051
052    /**
053     * Iterates through the fields of the class and creates an unordered list.
054     *
055     * @param sink the sink to write to.
056     * @param clss the class of the fields.
057     * @param messageKeys the List of message keys to iterate through.
058     */
059    private static void createListOfMessages(
060            XdocSink sink, Class<?> clss, Set<String> messageKeys) {
061        final String indentLevel8 = SiteUtil.getNewlineAndIndentSpaces(8);
062
063        // This is a hack to prevent a newline from being inserted by the default sink.
064        // Once we get rid of the custom parser, we can remove this.
065        // until https://github.com/checkstyle/checkstyle/issues/13426
066        sink.setInsertNewline(false);
067        sink.list();
068        sink.setInsertNewline(true);
069
070        for (String messageKey : messageKeys) {
071            createListItem(sink, clss, messageKey);
072        }
073        sink.rawText(indentLevel8);
074        sink.list_();
075    }
076
077    /**
078     * Creates a list item for the given field.
079     *
080     * @param sink the sink to write to.
081     * @param clss the class of the field.
082     * @param messageKey the message key.
083     */
084    private static void createListItem(XdocSink sink, Class<?> clss, String messageKey) {
085        final String messageKeyUrl = constructMessageKeyUrl(clss, messageKey);
086        final String indentLevel10 = SiteUtil.getNewlineAndIndentSpaces(10);
087        final String indentLevel12 = SiteUtil.getNewlineAndIndentSpaces(12);
088        final String indentLevel14 = SiteUtil.getNewlineAndIndentSpaces(14);
089        // Place the <li>.
090        sink.rawText(indentLevel10);
091        // This is a hack to prevent a newline from being inserted by the default sink.
092        // Once we get rid of the custom parser, we can remove this.
093        // until https://github.com/checkstyle/checkstyle/issues/13426
094        sink.setInsertNewline(false);
095        sink.listItem();
096        sink.setInsertNewline(true);
097
098        // Place an <a>.
099        sink.rawText(indentLevel12);
100        sink.link(messageKeyUrl);
101        // Further indent the text.
102        sink.rawText(indentLevel14);
103        sink.rawText(messageKey);
104
105        // Place closing </a> and </li> tags.
106        sink.rawText(indentLevel12);
107        sink.link_();
108        sink.rawText(indentLevel10);
109        sink.listItem_();
110    }
111
112    /**
113     * Constructs a URL to GitHub that searches for the message key.
114     *
115     * @param clss the class of the module.
116     * @param messageKey the message key.
117     * @return the URL to GitHub.
118     */
119    private static String constructMessageKeyUrl(Class<?> clss, String messageKey) {
120        final String query = "path:src/main/resources/"
121                + clss.getPackage().getName().replace('.', '/')
122                + " path:**/messages*.properties repo:checkstyle/checkstyle \""
123                + messageKey + "\"";
124
125        return "https://github.com/search?q="
126                + URLEncoder.encode(query, StandardCharsets.UTF_8);
127    }
128}