View Javadoc
1   /*
2    * Copyright (c) 2011-2025 Yegor Bugayenko
3    *
4    * All rights reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions
8    * are met: 1) Redistributions of source code must retain the above
9    * copyright notice, this list of conditions and the following
10   * disclaimer. 2) Redistributions in binary form must reproduce the above
11   * copyright notice, this list of conditions and the following
12   * disclaimer in the documentation and/or other materials provided
13   * with the distribution. 3) Neither the name of the Qulice.com nor
14   * the names of its contributors may be used to endorse or promote
15   * products derived from this software without specific prior written
16   * permission.
17   *
18   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
20   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29   * OF THE POSSIBILITY OF SUCH DAMAGE.
30   */
31  package com.qulice.checkstyle;
32  
33  import java.util.HashMap;
34  import java.util.Map;
35  import java.util.regex.Matcher;
36  import java.util.regex.Pattern;
37  
38  /**
39   * Check the required JavaDoc tag in the lines.
40   * <p>Correct format is the following (of a class javadoc):
41   *
42   * <pre>
43   * &#47;**
44   *  * This is my new class.
45   *  *
46   *  * &#64;since 0.3
47   *  *&#47;
48   * public final class Foo {
49   *     &#47;**
50   *      * This is my other class.
51   *      *
52   *      *    &#64;since    0.3
53   *      *&#47;
54   *     public final class Boo {
55   *     // ...
56   * </pre>
57   *
58   * <p>"&#36;Id&#36;" will be replaced by a full text automatically
59   * by Subversion as explained in their documentation (see link below).
60   *
61   * @see <a href="http://svnbook.red-bean.com/en/1.4/svn.advanced.props.special.keywords.html">Keywords substitution in Subversion</a>
62  
63   * @since 0.23.1
64   */
65  final class RequiredJavaDocTag {
66      /**
67       * Tag name.
68       */
69      private final String name;
70  
71      /**
72       * Pattern for searching a tag in a string.
73       */
74      private final Pattern tag;
75  
76      /**
77       * Pattern for checking the contents of a tag in a string.
78       */
79      private final Pattern content;
80  
81      /**
82       * Reference to a method for writing a message to the log.
83       */
84      private final Reporter reporter;
85  
86      /**
87       * Ctor.
88       * @param name Tag name.
89       * @param patt Pattern for checking the contents of a tag in a string.
90       * @param rep Reference to a method for writing a message to the log.
91       */
92      RequiredJavaDocTag(
93          final String name,
94          final Pattern patt,
95          final Reporter rep
96      ) {
97          this(
98              name,
99              Pattern.compile(
100                 String.format(
101                     "(?<name>^ +\\* +@%s)( +)(?<cont>.*)",
102                     name
103                 )
104             ),
105             patt,
106             rep
107         );
108     }
109 
110     /**
111      * Ctor.
112      * @param cname Tag name.
113      * @param ptag Pattern for searching a tag in a string.
114      * @param patt Pattern for checking the contents of a tag in a string.
115      * @param rep Reference to a method for writing a message to the log.
116      * @checkstyle ParameterNumberCheck (3 lines)
117      */
118     RequiredJavaDocTag(
119         final String cname,
120         final Pattern ptag,
121         final Pattern patt,
122         final Reporter rep
123     ) {
124         this.name = cname;
125         this.tag = ptag;
126         this.content = patt;
127         this.reporter = rep;
128     }
129 
130     /**
131      * Check if the tag text matches the format from pattern.
132      * @param lines List of all lines.
133      * @param start Line number where comment starts.
134      * @param end Line number where comment ends.
135      */
136     public void matchTagFormat(
137         final String[] lines,
138         final int start,
139         final int end
140     ) {
141         final Map<Integer, String> found = new HashMap<>(1);
142         for (int pos = start; pos <= end; pos += 1) {
143             final String line = lines[pos];
144             final Matcher matcher = this.tag.matcher(line);
145             if (RequiredJavaDocTag.tagFound(matcher)) {
146                 found.put(pos, matcher.group("cont"));
147                 break;
148             }
149         }
150         if (found.isEmpty()) {
151             this.reporter.log(
152                 start + 1,
153                 "Missing ''@{0}'' tag in class/interface comment",
154                 this.name
155             );
156         } else {
157             for (final Map.Entry<Integer, String> item : found.entrySet()) {
158                 if (!this.content.matcher(item.getValue()).matches()) {
159                     this.reporter.log(
160                         item.getKey() + 1,
161                         "Tag text ''{0}'' does not match the pattern ''{1}''",
162                         item.getValue(),
163                         this.content.toString()
164                     );
165                 }
166             }
167         }
168     }
169 
170     /**
171      * Finds the tag name and the following sentences.
172      * @param matcher Tag name matcher.
173      * @return True if the tag and its clauses are found.
174      */
175     private static boolean tagFound(final Matcher matcher) {
176         return matcher.matches()
177             && !RequiredJavaDocTag.empty(matcher.group("name"))
178             && !RequiredJavaDocTag.empty(matcher.group("cont"));
179     }
180 
181     /**
182      * Checks for an empty string.
183      * @param str Line to check.
184      * @return True if str is empty.
185      */
186     private static boolean empty(final String str) {
187         return str == null || str.isBlank();
188     }
189 
190     /**
191      * Logger.
192      * @see com.puppycrawl.tools.checkstyle.api.AbstractCheck#log(int, String, Object...)
193      * @since 0.23.1
194      */
195     interface Reporter {
196         /**
197          * Log a message that has no column information.
198          *
199          * @param line The line number where the audit event was found.
200          * @param msg The message that describes the audit event.
201          * @param args The details of the message.
202          * @see java.text.MessageFormat
203          */
204         void log(int line, String msg, Object... args);
205     }
206 }