001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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.Collections; 023import java.util.Set; 024 025import com.puppycrawl.tools.checkstyle.StatelessCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 030 031/** 032 * <div> 033 * Makes sure that utility classes (classes that contain only static methods or fields in their API) 034 * do not have a public constructor. 035 * </div> 036 * 037 * <p> 038 * Rationale: Instantiating utility classes does not make sense. 039 * Hence, the constructors should either be private or (if you want to allow subclassing) protected. 040 * A common mistake is forgetting to hide the default constructor. 041 * </p> 042 * 043 * <p> 044 * If you make the constructor protected you may want to consider the following constructor 045 * implementation technique to disallow instantiating subclasses: 046 * </p> 047 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 048 * public class StringUtils // not final to allow subclassing 049 * { 050 * protected StringUtils() { 051 * // prevents calls from subclass 052 * throw new UnsupportedOperationException(); 053 * } 054 * 055 * public static int count(char c, String s) { 056 * // ... 057 * } 058 * } 059 * </code></pre></div> 060 * 061 * @since 3.1 062 */ 063@StatelessCheck 064public class HideUtilityClassConstructorCheck extends AbstractCheck { 065 066 /** 067 * A key is pointing to the warning message text in "messages.properties" 068 * file. 069 */ 070 public static final String MSG_KEY = "hide.utility.class"; 071 072 /** 073 * Ignore classes annotated with the specified annotation(s). Annotation names 074 * provided in this property must exactly match the annotation names on the classes. 075 * If the target class has annotations specified with their fully qualified names 076 * (including package), the annotations in this property should also be specified with 077 * their fully qualified names. Similarly, if the target class has annotations specified 078 * with their simple names, this property should contain the annotations with the same 079 * simple names. 080 */ 081 private Set<String> ignoreAnnotatedBy = Collections.emptySet(); 082 083 /** 084 * Setter to ignore classes annotated with the specified annotation(s). Annotation names 085 * provided in this property must exactly match the annotation names on the classes. 086 * If the target class has annotations specified with their fully qualified names 087 * (including package), the annotations in this property should also be specified with 088 * their fully qualified names. Similarly, if the target class has annotations specified 089 * with their simple names, this property should contain the annotations with the same 090 * simple names. 091 * 092 * @param annotationNames specified annotation(s) 093 * @since 10.20.0 094 */ 095 public void setIgnoreAnnotatedBy(String... annotationNames) { 096 ignoreAnnotatedBy = Set.of(annotationNames); 097 } 098 099 @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}