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