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 * {@snippet : 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 * } 059 * 060 * @since 3.1 061 * @noinspection EmptyClass, NonFinalUtilityClass, UtilityClassWithoutPrivateConstructor, 062 * NewExceptionWithoutArguments 063 * @noinspectionreason EmptyClass - until IDEA-394242 064 * @noinspectionreason NonFinalUtilityClass - until IDEA-394413 065 * @noinspectionreason UtilityClassWithoutPrivateConstructor - until IDEA-394416 066 * @noinspectionreason NewExceptionWithoutArguments - until IDEA-394415 067 */ 068@StatelessCheck 069public class HideUtilityClassConstructorCheck extends AbstractCheck { 070 071 /** 072 * A key is pointing to the warning message text in "messages.properties" file. 073 */ 074 public static final String MSG_KEY = "hide.utility.class"; 075 076 /** 077 * Ignore classes annotated with the specified annotation(s). Annotation names 078 * provided in this property must exactly match the annotation names on the classes. 079 * If the target class has annotations specified with their fully qualified names 080 * (including package), the annotations in this property should also be specified with 081 * their fully qualified names. Similarly, if the target class has annotations specified 082 * with their simple names, this property should contain the annotations with the same 083 * simple names. 084 */ 085 private Set<String> ignoreAnnotatedBy = Set.of(); 086 087 /** 088 * Creates a new {@code HideUtilityClassConstructorCheck} instance. 089 */ 090 public HideUtilityClassConstructorCheck() { 091 // no code by default 092 } 093 094 /** 095 * Setter to ignore classes annotated with the specified annotation(s). Annotation names 096 * provided in this property must exactly match the annotation names on the classes. 097 * If the target class has annotations specified with their fully qualified names 098 * (including package), the annotations in this property should also be specified with 099 * their fully qualified names. Similarly, if the target class has annotations specified 100 * with their simple names, this property should contain the annotations with the same 101 * simple names. 102 * 103 * @param annotationNames specified annotation(s) 104 * @since 10.20.0 105 */ 106 public void setIgnoreAnnotatedBy(String... annotationNames) { 107 ignoreAnnotatedBy = Set.of(annotationNames); 108 } 109 110 @Override 111 public int[] getDefaultTokens() { 112 return getRequiredTokens(); 113 } 114 115 @Override 116 public int[] getAcceptableTokens() { 117 return getRequiredTokens(); 118 } 119 120 @Override 121 public int[] getRequiredTokens() { 122 return new int[] {TokenTypes.CLASS_DEF}; 123 } 124 125 @Override 126 public void visitToken(DetailAST ast) { 127 // abstract class could not have private constructor 128 if (!isAbstract(ast) && !shouldIgnoreClass(ast)) { 129 final boolean hasStaticModifier = isStatic(ast); 130 131 final Details details = new Details(ast); 132 details.invoke(); 133 134 final boolean hasDefaultCtor = details.isHasDefaultCtor(); 135 final boolean hasPublicCtor = details.isHasPublicCtor(); 136 final boolean hasNonStaticMethodOrField = details.isHasNonStaticMethodOrField(); 137 final boolean hasNonPrivateStaticMethodOrField = 138 details.isHasNonPrivateStaticMethodOrField(); 139 140 final boolean hasAccessibleCtor = hasDefaultCtor || hasPublicCtor; 141 142 // figure out if class extends java.lang.object directly 143 // keep it simple for now and get a 99% solution 144 final boolean extendsJlo = 145 ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE) == null; 146 147 final boolean isUtilClass = extendsJlo 148 && !hasNonStaticMethodOrField && hasNonPrivateStaticMethodOrField; 149 150 if (isUtilClass && hasAccessibleCtor && !hasStaticModifier) { 151 log(ast, MSG_KEY); 152 } 153 } 154 } 155 156 /** 157 * Returns true if given class is abstract or false. 158 * 159 * @param ast class definition for check. 160 * @return true if a given class declared as abstract. 161 */ 162 private static boolean isAbstract(DetailAST ast) { 163 return ast.findFirstToken(TokenTypes.MODIFIERS) 164 .findFirstToken(TokenTypes.ABSTRACT) != null; 165 } 166 167 /** 168 * Returns true if given class is static or false. 169 * 170 * @param ast class definition for check. 171 * @return true if a given class declared as static. 172 */ 173 private static boolean isStatic(DetailAST ast) { 174 return ast.findFirstToken(TokenTypes.MODIFIERS) 175 .findFirstToken(TokenTypes.LITERAL_STATIC) != null; 176 } 177 178 /** 179 * Checks if class is annotated by specific annotation(s) to skip. 180 * 181 * @param ast class to check 182 * @return true if annotated by ignored annotations 183 */ 184 private boolean shouldIgnoreClass(DetailAST ast) { 185 return AnnotationUtil.containsAnnotation(ast, ignoreAnnotatedBy); 186 } 187 188 /** 189 * Details of class that are required for validation. 190 */ 191 private static final class Details { 192 193 /** Class ast. */ 194 private final DetailAST ast; 195 /** Result of details gathering. */ 196 private boolean hasNonStaticMethodOrField; 197 /** Result of details gathering. */ 198 private boolean hasNonPrivateStaticMethodOrField; 199 /** Result of details gathering. */ 200 private boolean hasDefaultCtor; 201 /** Result of details gathering. */ 202 private boolean hasPublicCtor; 203 204 /** 205 * C-tor. 206 * 207 * @param ast class ast 208 */ 209 private Details(DetailAST ast) { 210 this.ast = ast; 211 } 212 213 /** 214 * Getter. 215 * 216 * @return boolean 217 */ 218 /* package */ boolean isHasNonStaticMethodOrField() { 219 return hasNonStaticMethodOrField; 220 } 221 222 /** 223 * Getter. 224 * 225 * @return boolean 226 */ 227 /* package */ boolean isHasNonPrivateStaticMethodOrField() { 228 return hasNonPrivateStaticMethodOrField; 229 } 230 231 /** 232 * Getter. 233 * 234 * @return boolean 235 */ 236 /* package */ boolean isHasDefaultCtor() { 237 return hasDefaultCtor; 238 } 239 240 /** 241 * Getter. 242 * 243 * @return boolean 244 */ 245 /* package */ boolean isHasPublicCtor() { 246 return hasPublicCtor; 247 } 248 249 /** 250 * Main method to gather statistics. 251 */ 252 /* package */ void invoke() { 253 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 254 hasDefaultCtor = true; 255 DetailAST child = objBlock.getFirstChild(); 256 257 while (child != null) { 258 final int type = child.getType(); 259 if (type == TokenTypes.METHOD_DEF 260 || type == TokenTypes.VARIABLE_DEF) { 261 final DetailAST modifiers = 262 child.findFirstToken(TokenTypes.MODIFIERS); 263 final boolean isStatic = 264 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 265 266 if (isStatic) { 267 final boolean isPrivate = 268 modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null; 269 270 if (!isPrivate) { 271 hasNonPrivateStaticMethodOrField = true; 272 } 273 } 274 else { 275 hasNonStaticMethodOrField = true; 276 } 277 } 278 if (type == TokenTypes.CTOR_DEF) { 279 hasDefaultCtor = false; 280 final DetailAST modifiers = 281 child.findFirstToken(TokenTypes.MODIFIERS); 282 if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null 283 && modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) == null) { 284 // treat package visible as public 285 // for the purpose of this Check 286 hasPublicCtor = true; 287 } 288 } 289 child = child.getNextSibling(); 290 } 291 } 292 293 } 294 295}