001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.design; 021 022import java.util.Set; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 029 030/** 031 * <div> 032 * Makes sure that utility classes (classes that contain only static methods or fields in their API) 033 * do not have a public constructor. 034 * </div> 035 * 036 * <p> 037 * Rationale: Instantiating utility classes does not make sense. 038 * Hence, the constructors should either be private or (if you want to allow subclassing) protected. 039 * A common mistake is forgetting to hide the default constructor. 040 * </p> 041 * 042 * <p> 043 * If you make the constructor protected you may want to consider the following constructor 044 * implementation technique to disallow instantiating subclasses: 045 * </p> 046 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 047 * public class StringUtils // not final to allow subclassing 048 * { 049 * protected StringUtils() { 050 * // prevents calls from subclass 051 * throw new UnsupportedOperationException(); 052 * } 053 * 054 * public static int count(char c, String s) { 055 * // ... 056 * } 057 * } 058 * </code></pre></div> 059 * 060 * @since 3.1 061 */ 062@StatelessCheck 063public class HideUtilityClassConstructorCheck extends AbstractCheck { 064 065 /** 066 * A key is pointing to the warning message text in "messages.properties" 067 * file. 068 */ 069 public static final String MSG_KEY = "hide.utility.class"; 070 071 /** 072 * Ignore classes annotated with the specified annotation(s). Annotation names 073 * provided in this property must exactly match the annotation names on the classes. 074 * If the target class has annotations specified with their fully qualified names 075 * (including package), the annotations in this property should also be specified with 076 * their fully qualified names. Similarly, if the target class has annotations specified 077 * with their simple names, this property should contain the annotations with the same 078 * simple names. 079 */ 080 private Set<String> ignoreAnnotatedBy = Set.of(); 081 082 /** 083 * Creates a new {@code HideUtilityClassConstructorCheck} instance. 084 */ 085 public HideUtilityClassConstructorCheck() { 086 // no code by default 087 } 088 089 /** 090 * Setter to ignore classes annotated with the specified annotation(s). Annotation names 091 * provided in this property must exactly match the annotation names on the classes. 092 * If the target class has annotations specified with their fully qualified names 093 * (including package), the annotations in this property should also be specified with 094 * their fully qualified names. Similarly, if the target class has annotations specified 095 * with their simple names, this property should contain the annotations with the same 096 * simple names. 097 * 098 * @param annotationNames specified annotation(s) 099 * @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}