001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 * To configure the check: 056 * </p> 057 * <pre> 058 * <module name="HideUtilityClassConstructor"/> 059 * </pre> 060 * <p> 061 * Example: 062 * </p> 063 * <pre> 064 * class Test { // violation, class only has a static method and a constructor 065 * 066 * public Test() { 067 * } 068 * 069 * public static void fun() { 070 * } 071 * } 072 * 073 * class Foo { // OK 074 * 075 * private Foo() { 076 * } 077 * 078 * static int n; 079 * } 080 * 081 * class Bar { // OK 082 * 083 * protected Bar() { 084 * // prevents calls from subclass 085 * throw new UnsupportedOperationException(); 086 * } 087 * } 088 * 089 * class UtilityClass { // violation, class only has a static field 090 * 091 * static float f; 092 * } 093 * </pre> 094 * 095 * @since 3.1 096 */ 097@StatelessCheck 098public class HideUtilityClassConstructorCheck extends AbstractCheck { 099 100 /** 101 * A key is pointing to the warning message text in "messages.properties" 102 * file. 103 */ 104 public static final String MSG_KEY = "hide.utility.class"; 105 106 @Override 107 public int[] getDefaultTokens() { 108 return getRequiredTokens(); 109 } 110 111 @Override 112 public int[] getAcceptableTokens() { 113 return getRequiredTokens(); 114 } 115 116 @Override 117 public int[] getRequiredTokens() { 118 return new int[] {TokenTypes.CLASS_DEF}; 119 } 120 121 @Override 122 public void visitToken(DetailAST ast) { 123 // abstract class could not have private constructor 124 if (!isAbstract(ast)) { 125 final boolean hasStaticModifier = isStatic(ast); 126 127 final Details details = new Details(ast); 128 details.invoke(); 129 130 final boolean hasDefaultCtor = details.isHasDefaultCtor(); 131 final boolean hasPublicCtor = details.isHasPublicCtor(); 132 final boolean hasNonStaticMethodOrField = details.isHasNonStaticMethodOrField(); 133 final boolean hasNonPrivateStaticMethodOrField = 134 details.isHasNonPrivateStaticMethodOrField(); 135 136 final boolean hasAccessibleCtor = hasDefaultCtor || hasPublicCtor; 137 138 // figure out if class extends java.lang.object directly 139 // keep it simple for now and get a 99% solution 140 final boolean extendsJlo = 141 ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE) == null; 142 143 final boolean isUtilClass = extendsJlo 144 && !hasNonStaticMethodOrField && hasNonPrivateStaticMethodOrField; 145 146 if (isUtilClass && hasAccessibleCtor && !hasStaticModifier) { 147 log(ast, MSG_KEY); 148 } 149 } 150 } 151 152 /** 153 * Returns true if given class is abstract or false. 154 * 155 * @param ast class definition for check. 156 * @return true if a given class declared as abstract. 157 */ 158 private static boolean isAbstract(DetailAST ast) { 159 return ast.findFirstToken(TokenTypes.MODIFIERS) 160 .findFirstToken(TokenTypes.ABSTRACT) != null; 161 } 162 163 /** 164 * Returns true if given class is static or false. 165 * 166 * @param ast class definition for check. 167 * @return true if a given class declared as static. 168 */ 169 private static boolean isStatic(DetailAST ast) { 170 return ast.findFirstToken(TokenTypes.MODIFIERS) 171 .findFirstToken(TokenTypes.LITERAL_STATIC) != null; 172 } 173 174 /** 175 * Details of class that are required for validation. 176 */ 177 private static class Details { 178 179 /** Class ast. */ 180 private final DetailAST ast; 181 /** Result of details gathering. */ 182 private boolean hasNonStaticMethodOrField; 183 /** Result of details gathering. */ 184 private boolean hasNonPrivateStaticMethodOrField; 185 /** Result of details gathering. */ 186 private boolean hasDefaultCtor; 187 /** Result of details gathering. */ 188 private boolean hasPublicCtor; 189 190 /** 191 * C-tor. 192 * 193 * @param ast class ast 194 * */ 195 /* package */ Details(DetailAST ast) { 196 this.ast = ast; 197 } 198 199 /** 200 * Getter. 201 * 202 * @return boolean 203 */ 204 public boolean isHasNonStaticMethodOrField() { 205 return hasNonStaticMethodOrField; 206 } 207 208 /** 209 * Getter. 210 * 211 * @return boolean 212 */ 213 public boolean isHasNonPrivateStaticMethodOrField() { 214 return hasNonPrivateStaticMethodOrField; 215 } 216 217 /** 218 * Getter. 219 * 220 * @return boolean 221 */ 222 public boolean isHasDefaultCtor() { 223 return hasDefaultCtor; 224 } 225 226 /** 227 * Getter. 228 * 229 * @return boolean 230 */ 231 public boolean isHasPublicCtor() { 232 return hasPublicCtor; 233 } 234 235 /** 236 * Main method to gather statistics. 237 */ 238 public void invoke() { 239 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 240 hasNonStaticMethodOrField = false; 241 hasNonPrivateStaticMethodOrField = false; 242 hasDefaultCtor = true; 243 hasPublicCtor = false; 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}