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