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