View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 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   *
15   * <p>Correct format is the following (of a class javadoc):</p>
16   *
17   * <pre>
18   * &#47;**
19   *  * This is my new class.
20   *  *
21   *  * &#64;since 0.3
22   *  *&#47;
23   * public final class Foo {
24   *     &#47;**
25   *      * This is my other class.
26   *      *
27   *      *    &#64;since    0.3
28   *      *&#47;
29   *     public final class Boo {
30   *     // ...
31   * </pre>
32   *
33   * @since 0.23.1
34   */
35  final class RequiredJavaDocTag {
36  
37      /**
38       * Tag name.
39       */
40      private final String name;
41  
42      /**
43       * Pattern for searching a tag in a string.
44       */
45      private final Pattern tag;
46  
47      /**
48       * Pattern for checking the contents of a tag in a string.
49       */
50      private final Pattern content;
51  
52      /**
53       * Reference to a method for writing a message to the log.
54       */
55      private final Reporter reporter;
56  
57      /**
58       * Ctor.
59       *
60       * @param cname Tag name
61       * @param ptag Pattern for searching a tag in a string
62       * @param patt Pattern for checking the contents of a tag in a string
63       * @param rep Reference to a method for writing a message to the log
64       */
65      RequiredJavaDocTag(
66          final String cname,
67          final Pattern ptag,
68          final Pattern patt,
69          final Reporter rep
70      ) {
71          this.name = cname;
72          this.tag = ptag;
73          this.content = patt;
74          this.reporter = rep;
75      }
76  
77      /**
78       * Check if the tag text matches the format from pattern.
79       *
80       * @param lines List of all lines
81       * @param start Line number where comment starts
82       * @param end Line number where comment ends
83       */
84      void matchTagFormat(
85          final String[] lines,
86          final int start,
87          final int end
88      ) {
89          final Pattern loose = Pattern.compile(
90              String.format("@%s\\b", this.name)
91          );
92          Integer looseline = null;
93          final Map<Integer, String> found = new HashMap<>(1);
94          for (int pos = start; pos <= end; pos += 1) {
95              final String line = lines[pos];
96              final Matcher matcher = this.tag.matcher(line);
97              if (RequiredJavaDocTag.tagFound(matcher)) {
98                  found.put(pos, matcher.group("cont"));
99                  break;
100             }
101             if (looseline == null && loose.matcher(line).find()) {
102                 looseline = pos;
103             }
104         }
105         if (found.isEmpty()) {
106             this.logMissing(start, looseline);
107         } else {
108             this.logMismatches(found);
109         }
110     }
111 
112     private void logMissing(final int start, final Integer looseline) {
113         if (looseline == null) {
114             this.reporter.log(
115                 start + 1,
116                 "Missing ''@{0}'' tag in class/interface comment",
117                 this.name
118             );
119         } else {
120             this.reporter.log(
121                 looseline + 1,
122                 "Malformed ''@{0}'' tag, expected '' * @{0} <value>'' format",
123                 this.name
124             );
125         }
126     }
127 
128     private void logMismatches(final Map<Integer, String> found) {
129         for (final Map.Entry<Integer, String> item : found.entrySet()) {
130             if (!this.content.matcher(item.getValue()).matches()) {
131                 this.reporter.log(
132                     item.getKey() + 1,
133                     "Tag text ''{0}'' does not match the pattern ''{1}''",
134                     item.getValue(),
135                     this.content.toString()
136                 );
137             }
138         }
139     }
140 
141     private static boolean tagFound(final Matcher matcher) {
142         return matcher.matches()
143             && !RequiredJavaDocTag.empty(matcher.group("name"))
144             && !RequiredJavaDocTag.empty(matcher.group("cont"));
145     }
146 
147     private static boolean empty(final String str) {
148         return str == null || str.chars().allMatch(Character::isWhitespace);
149     }
150 
151     /**
152      * Logger.
153      *
154      * @see com.puppycrawl.tools.checkstyle.api.AbstractCheck#log(int, String, Object...)
155      * @since 0.23.1
156      */
157     @FunctionalInterface
158     interface Reporter {
159 
160         /**
161          * Log a message that has no column information.
162          *
163          * @param line The line number where the audit event was found
164          * @param msg The message that describes the audit event
165          * @param args The details of the message
166          * @see java.text.MessageFormat
167          */
168         void log(int line, String msg, Object... args);
169     }
170 }