1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2024 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 * <pre>
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 * </pre>
60 * <ul>
61 * <li>
62 * Property {@code ignoreAnnotatedBy} - Ignore classes annotated
63 * with the specified annotation(s). Annotation names provided in this property
64 * must exactly match the annotation names on the classes. If the target class has annotations
65 * specified with their fully qualified names (including package), the annotations in this
66 * property should also be specified with their fully qualified names. Similarly, if the target
67 * class has annotations specified with their simple names, this property should contain the
68 * annotations with the same simple names.
69 * Type is {@code java.lang.String[]}.
70 * Default value is {@code ""}.
71 * </li>
72 * </ul>
73 *
74 * <p>
75 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
76 * </p>
77 *
78 * <p>
79 * Violation Message Keys:
80 * </p>
81 * <ul>
82 * <li>
83 * {@code hide.utility.class}
84 * </li>
85 * </ul>
86 *
87 * @since 3.1
88 */
89 @StatelessCheck
90 public class HideUtilityClassConstructorCheck extends AbstractCheck {
91
92 /**
93 * A key is pointing to the warning message text in "messages.properties"
94 * file.
95 */
96 public static final String MSG_KEY = "hide.utility.class";
97
98 /**
99 * Ignore classes annotated with the specified annotation(s). Annotation names
100 * provided in this property must exactly match the annotation names on the classes.
101 * If the target class has annotations specified with their fully qualified names
102 * (including package), the annotations in this property should also be specified with
103 * their fully qualified names. Similarly, if the target class has annotations specified
104 * with their simple names, this property should contain the annotations with the same
105 * simple names.
106 */
107 private Set<String> ignoreAnnotatedBy = Collections.emptySet();
108
109 /**
110 * Setter to ignore classes annotated with the specified annotation(s). Annotation names
111 * provided in this property must exactly match the annotation names on the classes.
112 * If the target class has annotations specified with their fully qualified names
113 * (including package), the annotations in this property should also be specified with
114 * their fully qualified names. Similarly, if the target class has annotations specified
115 * with their simple names, this property should contain the annotations with the same
116 * simple names.
117 *
118 * @param annotationNames specified annotation(s)
119 * @since 10.20.0
120 */
121 public void setIgnoreAnnotatedBy(String... annotationNames) {
122 ignoreAnnotatedBy = Set.of(annotationNames);
123 }
124
125 @Override
126 public int[] getDefaultTokens() {
127 return getRequiredTokens();
128 }
129
130 @Override
131 public int[] getAcceptableTokens() {
132 return getRequiredTokens();
133 }
134
135 @Override
136 public int[] getRequiredTokens() {
137 return new int[] {TokenTypes.CLASS_DEF};
138 }
139
140 @Override
141 public void visitToken(DetailAST ast) {
142 // abstract class could not have private constructor
143 if (!isAbstract(ast) && !shouldIgnoreClass(ast)) {
144 final boolean hasStaticModifier = isStatic(ast);
145
146 final Details details = new Details(ast);
147 details.invoke();
148
149 final boolean hasDefaultCtor = details.isHasDefaultCtor();
150 final boolean hasPublicCtor = details.isHasPublicCtor();
151 final boolean hasNonStaticMethodOrField = details.isHasNonStaticMethodOrField();
152 final boolean hasNonPrivateStaticMethodOrField =
153 details.isHasNonPrivateStaticMethodOrField();
154
155 final boolean hasAccessibleCtor = hasDefaultCtor || hasPublicCtor;
156
157 // figure out if class extends java.lang.object directly
158 // keep it simple for now and get a 99% solution
159 final boolean extendsJlo =
160 ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE) == null;
161
162 final boolean isUtilClass = extendsJlo
163 && !hasNonStaticMethodOrField && hasNonPrivateStaticMethodOrField;
164
165 if (isUtilClass && hasAccessibleCtor && !hasStaticModifier) {
166 log(ast, MSG_KEY);
167 }
168 }
169 }
170
171 /**
172 * Returns true if given class is abstract or false.
173 *
174 * @param ast class definition for check.
175 * @return true if a given class declared as abstract.
176 */
177 private static boolean isAbstract(DetailAST ast) {
178 return ast.findFirstToken(TokenTypes.MODIFIERS)
179 .findFirstToken(TokenTypes.ABSTRACT) != null;
180 }
181
182 /**
183 * Returns true if given class is static or false.
184 *
185 * @param ast class definition for check.
186 * @return true if a given class declared as static.
187 */
188 private static boolean isStatic(DetailAST ast) {
189 return ast.findFirstToken(TokenTypes.MODIFIERS)
190 .findFirstToken(TokenTypes.LITERAL_STATIC) != null;
191 }
192
193 /**
194 * Checks if class is annotated by specific annotation(s) to skip.
195 *
196 * @param ast class to check
197 * @return true if annotated by ignored annotations
198 */
199 private boolean shouldIgnoreClass(DetailAST ast) {
200 return AnnotationUtil.containsAnnotation(ast, ignoreAnnotatedBy);
201 }
202
203 /**
204 * Details of class that are required for validation.
205 */
206 private static final class Details {
207
208 /** Class ast. */
209 private final DetailAST ast;
210 /** Result of details gathering. */
211 private boolean hasNonStaticMethodOrField;
212 /** Result of details gathering. */
213 private boolean hasNonPrivateStaticMethodOrField;
214 /** Result of details gathering. */
215 private boolean hasDefaultCtor;
216 /** Result of details gathering. */
217 private boolean hasPublicCtor;
218
219 /**
220 * C-tor.
221 *
222 * @param ast class ast
223 */
224 private Details(DetailAST ast) {
225 this.ast = ast;
226 }
227
228 /**
229 * Getter.
230 *
231 * @return boolean
232 */
233 public boolean isHasNonStaticMethodOrField() {
234 return hasNonStaticMethodOrField;
235 }
236
237 /**
238 * Getter.
239 *
240 * @return boolean
241 */
242 public boolean isHasNonPrivateStaticMethodOrField() {
243 return hasNonPrivateStaticMethodOrField;
244 }
245
246 /**
247 * Getter.
248 *
249 * @return boolean
250 */
251 public boolean isHasDefaultCtor() {
252 return hasDefaultCtor;
253 }
254
255 /**
256 * Getter.
257 *
258 * @return boolean
259 */
260 public boolean isHasPublicCtor() {
261 return hasPublicCtor;
262 }
263
264 /**
265 * Main method to gather statistics.
266 */
267 public void invoke() {
268 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
269 hasDefaultCtor = true;
270 DetailAST child = objBlock.getFirstChild();
271
272 while (child != null) {
273 final int type = child.getType();
274 if (type == TokenTypes.METHOD_DEF
275 || type == TokenTypes.VARIABLE_DEF) {
276 final DetailAST modifiers =
277 child.findFirstToken(TokenTypes.MODIFIERS);
278 final boolean isStatic =
279 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
280
281 if (isStatic) {
282 final boolean isPrivate =
283 modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
284
285 if (!isPrivate) {
286 hasNonPrivateStaticMethodOrField = true;
287 }
288 }
289 else {
290 hasNonStaticMethodOrField = true;
291 }
292 }
293 if (type == TokenTypes.CTOR_DEF) {
294 hasDefaultCtor = false;
295 final DetailAST modifiers =
296 child.findFirstToken(TokenTypes.MODIFIERS);
297 if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null
298 && modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) == null) {
299 // treat package visible as public
300 // for the purpose of this Check
301 hasPublicCtor = true;
302 }
303 }
304 child = child.getNextSibling();
305 }
306 }
307
308 }
309
310 }