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 * /**
18 * * This is my new class.
19 * *
20 * * @since 0.3
21 * */
22 * public final class Foo {
23 * /**
24 * * This is my other class.
25 * *
26 * * @since 0.3
27 * */
28 * public final class Boo {
29 * // ...
30 * </pre>
31 *
32 * <p>"$Id$" 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 }