View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import java.util.HashMap;
8   import java.util.Map;
9   import java.util.regex.Matcher;
10  import java.util.regex.Pattern;
11  
12  /**
13   * Check the required JavaDoc tag in the lines.
14   * <p>Correct format is the following (of a class javadoc):
15   *
16   * <pre>
17   * &#47;**
18   *  * This is my new class.
19   *  *
20   *  * &#64;since 0.3
21   *  *&#47;
22   * public final class Foo {
23   *     &#47;**
24   *      * This is my other class.
25   *      *
26   *      *    &#64;since    0.3
27   *      *&#47;
28   *     public final class Boo {
29   *     // ...
30   * </pre>
31   *
32   * <p>"&#36;Id&#36;" will be replaced by a full text automatically
33   * by Subversion as explained in their documentation (see link below).
34   *
35   * @see <a href="http://svnbook.red-bean.com/en/1.4/svn.advanced.props.special.keywords.html">Keywords substitution in Subversion</a>
36  
37   * @since 0.23.1
38   */
39  final class RequiredJavaDocTag {
40      /**
41       * Tag name.
42       */
43      private final String name;
44  
45      /**
46       * Pattern for searching a tag in a string.
47       */
48      private final Pattern tag;
49  
50      /**
51       * Pattern for checking the contents of a tag in a string.
52       */
53      private final Pattern content;
54  
55      /**
56       * Reference to a method for writing a message to the log.
57       */
58      private final Reporter reporter;
59  
60      /**
61       * Ctor.
62       * @param name Tag name.
63       * @param patt Pattern for checking the contents of a tag in a string.
64       * @param rep Reference to a method for writing a message to the log.
65       */
66      RequiredJavaDocTag(
67          final String name,
68          final Pattern patt,
69          final Reporter rep
70      ) {
71          this(
72              name,
73              Pattern.compile(
74                  String.format(
75                      "(?<name>^ +\\* +@%s)( +)(?<cont>.*)",
76                      name
77                  )
78              ),
79              patt,
80              rep
81          );
82      }
83  
84      /**
85       * Ctor.
86       * @param cname Tag name.
87       * @param ptag Pattern for searching a tag in a string.
88       * @param patt Pattern for checking the contents of a tag in a string.
89       * @param rep Reference to a method for writing a message to the log.
90       * @checkstyle ParameterNumberCheck (3 lines)
91       */
92      RequiredJavaDocTag(
93          final String cname,
94          final Pattern ptag,
95          final Pattern patt,
96          final Reporter rep
97      ) {
98          this.name = cname;
99          this.tag = ptag;
100         this.content = patt;
101         this.reporter = rep;
102     }
103 
104     /**
105      * Check if the tag text matches the format from pattern.
106      * @param lines List of all lines.
107      * @param start Line number where comment starts.
108      * @param end Line number where comment ends.
109      */
110     public void matchTagFormat(
111         final String[] lines,
112         final int start,
113         final int end
114     ) {
115         final Map<Integer, String> found = new HashMap<>(1);
116         for (int pos = start; pos <= end; pos += 1) {
117             final String line = lines[pos];
118             final Matcher matcher = this.tag.matcher(line);
119             if (RequiredJavaDocTag.tagFound(matcher)) {
120                 found.put(pos, matcher.group("cont"));
121                 break;
122             }
123         }
124         if (found.isEmpty()) {
125             this.reporter.log(
126                 start + 1,
127                 "Missing ''@{0}'' tag in class/interface comment",
128                 this.name
129             );
130         } else {
131             for (final Map.Entry<Integer, String> item : found.entrySet()) {
132                 if (!this.content.matcher(item.getValue()).matches()) {
133                     this.reporter.log(
134                         item.getKey() + 1,
135                         "Tag text ''{0}'' does not match the pattern ''{1}''",
136                         item.getValue(),
137                         this.content.toString()
138                     );
139                 }
140             }
141         }
142     }
143 
144     /**
145      * Finds the tag name and the following sentences.
146      * @param matcher Tag name matcher.
147      * @return True if the tag and its clauses are found.
148      */
149     private static boolean tagFound(final Matcher matcher) {
150         return matcher.matches()
151             && !RequiredJavaDocTag.empty(matcher.group("name"))
152             && !RequiredJavaDocTag.empty(matcher.group("cont"));
153     }
154 
155     /**
156      * Checks for an empty string.
157      * @param str Line to check.
158      * @return True if str is empty.
159      */
160     private static boolean empty(final String str) {
161         return str == null || str.chars().allMatch(Character::isWhitespace);
162     }
163 
164     /**
165      * Logger.
166      * @see com.puppycrawl.tools.checkstyle.api.AbstractCheck#log(int, String, Object...)
167      * @since 0.23.1
168      */
169     interface Reporter {
170         /**
171          * Log a message that has no column information.
172          *
173          * @param line The line number where the audit event was found.
174          * @param msg The message that describes the audit event.
175          * @param args The details of the message.
176          * @see java.text.MessageFormat
177          */
178         void log(int line, String msg, Object... args);
179     }
180 }