001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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 * <pre> 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 * </pre> 060 * <ul> 061 * <li> 062 * Property {@code ignoreAnnotatedBy} - Ignore classes annotated 063 * with the specified annotation(s). Annotation names provided in this property 064 * must exactly match the annotation names on the classes. If the target class has annotations 065 * specified with their fully qualified names (including package), the annotations in this 066 * property should also be specified with their fully qualified names. Similarly, if the target 067 * class has annotations specified with their simple names, this property should contain the 068 * annotations with the same simple names. 069 * Type is {@code java.lang.String[]}. 070 * Default value is {@code ""}. 071 * </li> 072 * </ul> 073 * 074 * <p> 075 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 076 * </p> 077 * 078 * <p> 079 * Violation Message Keys: 080 * </p> 081 * <ul> 082 * <li> 083 * {@code hide.utility.class} 084 * </li> 085 * </ul> 086 * 087 * @since 3.1 088 */ 089@StatelessCheck 090public class HideUtilityClassConstructorCheck extends AbstractCheck { 091 092 /** 093 * A key is pointing to the warning message text in "messages.properties" 094 * file. 095 */ 096 public static final String MSG_KEY = "hide.utility.class"; 097 098 /** 099 * 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}