1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.design;
21
22 import java.util.Set;
23
24 import com.puppycrawl.tools.checkstyle.StatelessCheck;
25 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26 import com.puppycrawl.tools.checkstyle.api.DetailAST;
27 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
29
30 /**
31 * <div>
32 * Makes sure that utility classes (classes that contain only static methods or fields in their API)
33 * do not have a public constructor.
34 * </div>
35 *
36 * <p>
37 * Rationale: Instantiating utility classes does not make sense.
38 * Hence, the constructors should either be private or (if you want to allow subclassing) protected.
39 * A common mistake is forgetting to hide the default constructor.
40 * </p>
41 *
42 * <p>
43 * If you make the constructor protected you may want to consider the following constructor
44 * implementation technique to disallow instantiating subclasses:
45 * </p>
46 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
47 * public class StringUtils // not final to allow subclassing
48 * {
49 * protected StringUtils() {
50 * // prevents calls from subclass
51 * throw new UnsupportedOperationException();
52 * }
53 *
54 * public static int count(char c, String s) {
55 * // ...
56 * }
57 * }
58 * </code></pre></div>
59 *
60 * @since 3.1
61 */
62 @StatelessCheck
63 public class HideUtilityClassConstructorCheck extends AbstractCheck {
64
65 /**
66 * A key is pointing to the warning message text in "messages.properties"
67 * file.
68 */
69 public static final String MSG_KEY = "hide.utility.class";
70
71 /**
72 * Ignore classes annotated with the specified annotation(s). Annotation names
73 * provided in this property must exactly match the annotation names on the classes.
74 * If the target class has annotations specified with their fully qualified names
75 * (including package), the annotations in this property should also be specified with
76 * their fully qualified names. Similarly, if the target class has annotations specified
77 * with their simple names, this property should contain the annotations with the same
78 * simple names.
79 */
80 private Set<String> ignoreAnnotatedBy = Set.of();
81
82 /**
83 * Creates a new {@code HideUtilityClassConstructorCheck} instance.
84 */
85 public HideUtilityClassConstructorCheck() {
86 // no code by default
87 }
88
89 /**
90 * Setter to ignore classes annotated with the specified annotation(s). Annotation names
91 * provided in this property must exactly match the annotation names on the classes.
92 * If the target class has annotations specified with their fully qualified names
93 * (including package), the annotations in this property should also be specified with
94 * their fully qualified names. Similarly, if the target class has annotations specified
95 * with their simple names, this property should contain the annotations with the same
96 * simple names.
97 *
98 * @param annotationNames specified annotation(s)
99 * @since 10.20.0
100 */
101 public void setIgnoreAnnotatedBy(String... annotationNames) {
102 ignoreAnnotatedBy = Set.of(annotationNames);
103 }
104
105 @Override
106 public int[] getDefaultTokens() {
107 return getRequiredTokens();
108 }
109
110 @Override
111 public int[] getAcceptableTokens() {
112 return getRequiredTokens();
113 }
114
115 @Override
116 public int[] getRequiredTokens() {
117 return new int[] {TokenTypes.CLASS_DEF};
118 }
119
120 @Override
121 public void visitToken(DetailAST ast) {
122 // abstract class could not have private constructor
123 if (!isAbstract(ast) && !shouldIgnoreClass(ast)) {
124 final boolean hasStaticModifier = isStatic(ast);
125
126 final Details details = new Details(ast);
127 details.invoke();
128
129 final boolean hasDefaultCtor = details.isHasDefaultCtor();
130 final boolean hasPublicCtor = details.isHasPublicCtor();
131 final boolean hasNonStaticMethodOrField = details.isHasNonStaticMethodOrField();
132 final boolean hasNonPrivateStaticMethodOrField =
133 details.isHasNonPrivateStaticMethodOrField();
134
135 final boolean hasAccessibleCtor = hasDefaultCtor || hasPublicCtor;
136
137 // figure out if class extends java.lang.object directly
138 // keep it simple for now and get a 99% solution
139 final boolean extendsJlo =
140 ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE) == null;
141
142 final boolean isUtilClass = extendsJlo
143 && !hasNonStaticMethodOrField && hasNonPrivateStaticMethodOrField;
144
145 if (isUtilClass && hasAccessibleCtor && !hasStaticModifier) {
146 log(ast, MSG_KEY);
147 }
148 }
149 }
150
151 /**
152 * Returns true if given class is abstract or false.
153 *
154 * @param ast class definition for check.
155 * @return true if a given class declared as abstract.
156 */
157 private static boolean isAbstract(DetailAST ast) {
158 return ast.findFirstToken(TokenTypes.MODIFIERS)
159 .findFirstToken(TokenTypes.ABSTRACT) != null;
160 }
161
162 /**
163 * Returns true if given class is static or false.
164 *
165 * @param ast class definition for check.
166 * @return true if a given class declared as static.
167 */
168 private static boolean isStatic(DetailAST ast) {
169 return ast.findFirstToken(TokenTypes.MODIFIERS)
170 .findFirstToken(TokenTypes.LITERAL_STATIC) != null;
171 }
172
173 /**
174 * Checks if class is annotated by specific annotation(s) to skip.
175 *
176 * @param ast class to check
177 * @return true if annotated by ignored annotations
178 */
179 private boolean shouldIgnoreClass(DetailAST ast) {
180 return AnnotationUtil.containsAnnotation(ast, ignoreAnnotatedBy);
181 }
182
183 /**
184 * Details of class that are required for validation.
185 */
186 private static final class Details {
187
188 /** Class ast. */
189 private final DetailAST ast;
190 /** Result of details gathering. */
191 private boolean hasNonStaticMethodOrField;
192 /** Result of details gathering. */
193 private boolean hasNonPrivateStaticMethodOrField;
194 /** Result of details gathering. */
195 private boolean hasDefaultCtor;
196 /** Result of details gathering. */
197 private boolean hasPublicCtor;
198
199 /**
200 * C-tor.
201 *
202 * @param ast class ast
203 */
204 private Details(DetailAST ast) {
205 this.ast = ast;
206 }
207
208 /**
209 * Getter.
210 *
211 * @return boolean
212 */
213 /* package */ boolean isHasNonStaticMethodOrField() {
214 return hasNonStaticMethodOrField;
215 }
216
217 /**
218 * Getter.
219 *
220 * @return boolean
221 */
222 /* package */ boolean isHasNonPrivateStaticMethodOrField() {
223 return hasNonPrivateStaticMethodOrField;
224 }
225
226 /**
227 * Getter.
228 *
229 * @return boolean
230 */
231 /* package */ boolean isHasDefaultCtor() {
232 return hasDefaultCtor;
233 }
234
235 /**
236 * Getter.
237 *
238 * @return boolean
239 */
240 /* package */ boolean isHasPublicCtor() {
241 return hasPublicCtor;
242 }
243
244 /**
245 * Main method to gather statistics.
246 */
247 /* package */ void invoke() {
248 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
249 hasDefaultCtor = true;
250 DetailAST child = objBlock.getFirstChild();
251
252 while (child != null) {
253 final int type = child.getType();
254 if (type == TokenTypes.METHOD_DEF
255 || type == TokenTypes.VARIABLE_DEF) {
256 final DetailAST modifiers =
257 child.findFirstToken(TokenTypes.MODIFIERS);
258 final boolean isStatic =
259 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
260
261 if (isStatic) {
262 final boolean isPrivate =
263 modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
264
265 if (!isPrivate) {
266 hasNonPrivateStaticMethodOrField = true;
267 }
268 }
269 else {
270 hasNonStaticMethodOrField = true;
271 }
272 }
273 if (type == TokenTypes.CTOR_DEF) {
274 hasDefaultCtor = false;
275 final DetailAST modifiers =
276 child.findFirstToken(TokenTypes.MODIFIERS);
277 if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null
278 && modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) == null) {
279 // treat package visible as public
280 // for the purpose of this Check
281 hasPublicCtor = true;
282 }
283 }
284 child = child.getNextSibling();
285 }
286 }
287
288 }
289
290 }