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 com.puppycrawl.tools.checkstyle.StatelessCheck; 023import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026 027/** 028 * <p> 029 * Makes sure that utility classes (classes that contain only static methods or fields in their API) 030 * do not have a public constructor. 031 * </p> 032 * <p> 033 * Rationale: Instantiating utility classes does not make sense. 034 * Hence, the constructors should either be private or (if you want to allow subclassing) protected. 035 * A common mistake is forgetting to hide the default constructor. 036 * </p> 037 * <p> 038 * If you make the constructor protected you may want to consider the following constructor 039 * implementation technique to disallow instantiating subclasses: 040 * </p> 041 * <pre> 042 * public class StringUtils // not final to allow subclassing 043 * { 044 * protected StringUtils() { 045 * // prevents calls from subclass 046 * throw new UnsupportedOperationException(); 047 * } 048 * 049 * public static int count(char c, String s) { 050 * // ... 051 * } 052 * } 053 * </pre> 054 * <p> 055 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 056 * </p> 057 * <p> 058 * Violation Message Keys: 059 * </p> 060 * <ul> 061 * <li> 062 * {@code hide.utility.class} 063 * </li> 064 * </ul> 065 * 066 * @since 3.1 067 */ 068@StatelessCheck 069public class HideUtilityClassConstructorCheck extends AbstractCheck { 070 071 /** 072 * A key is pointing to the warning message text in "messages.properties" 073 * file. 074 */ 075 public static final String MSG_KEY = "hide.utility.class"; 076 077 @Override 078 public int[] getDefaultTokens() { 079 return getRequiredTokens(); 080 } 081 082 @Override 083 public int[] getAcceptableTokens() { 084 return getRequiredTokens(); 085 } 086 087 @Override 088 public int[] getRequiredTokens() { 089 return new int[] {TokenTypes.CLASS_DEF}; 090 } 091 092 @Override 093 public void visitToken(DetailAST ast) { 094 // abstract class could not have private constructor 095 if (!isAbstract(ast)) { 096 final boolean hasStaticModifier = isStatic(ast); 097 098 final Details details = new Details(ast); 099 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}