001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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 * 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 @Override 073 public int[] getDefaultTokens() { 074 return getRequiredTokens(); 075 } 076 077 @Override 078 public int[] getAcceptableTokens() { 079 return getRequiredTokens(); 080 } 081 082 @Override 083 public int[] getRequiredTokens() { 084 return new int[] {TokenTypes.CLASS_DEF}; 085 } 086 087 @Override 088 public void visitToken(DetailAST ast) { 089 // abstract class could not have private constructor 090 if (!isAbstract(ast)) { 091 final boolean hasStaticModifier = isStatic(ast); 092 093 final Details details = new Details(ast); 094 details.invoke(); 095 096 final boolean hasDefaultCtor = details.isHasDefaultCtor(); 097 final boolean hasPublicCtor = details.isHasPublicCtor(); 098 final boolean hasNonStaticMethodOrField = details.isHasNonStaticMethodOrField(); 099 final boolean hasNonPrivateStaticMethodOrField = 100 details.isHasNonPrivateStaticMethodOrField(); 101 102 final boolean hasAccessibleCtor = hasDefaultCtor || hasPublicCtor; 103 104 // figure out if class extends java.lang.object directly 105 // keep it simple for now and get a 99% solution 106 final boolean extendsJlo = 107 ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE) == null; 108 109 final boolean isUtilClass = extendsJlo 110 && !hasNonStaticMethodOrField && hasNonPrivateStaticMethodOrField; 111 112 if (isUtilClass && hasAccessibleCtor && !hasStaticModifier) { 113 log(ast, MSG_KEY); 114 } 115 } 116 } 117 118 /** 119 * Returns true if given class is abstract or false. 120 * @param ast class definition for check. 121 * @return true if a given class declared as abstract. 122 */ 123 private static boolean isAbstract(DetailAST ast) { 124 return ast.findFirstToken(TokenTypes.MODIFIERS) 125 .findFirstToken(TokenTypes.ABSTRACT) != null; 126 } 127 128 /** 129 * Returns true if given class is static or false. 130 * @param ast class definition for check. 131 * @return true if a given class declared as static. 132 */ 133 private static boolean isStatic(DetailAST ast) { 134 return ast.findFirstToken(TokenTypes.MODIFIERS) 135 .findFirstToken(TokenTypes.LITERAL_STATIC) != null; 136 } 137 138 /** 139 * Details of class that are required for validation. 140 */ 141 private static class Details { 142 143 /** Class ast. */ 144 private final DetailAST ast; 145 /** Result of details gathering. */ 146 private boolean hasNonStaticMethodOrField; 147 /** Result of details gathering. */ 148 private boolean hasNonPrivateStaticMethodOrField; 149 /** Result of details gathering. */ 150 private boolean hasDefaultCtor; 151 /** Result of details gathering. */ 152 private boolean hasPublicCtor; 153 154 /** 155 * C-tor. 156 * @param ast class ast 157 * */ 158 /* package */ Details(DetailAST ast) { 159 this.ast = ast; 160 } 161 162 /** 163 * Getter. 164 * @return boolean 165 */ 166 public boolean isHasNonStaticMethodOrField() { 167 return hasNonStaticMethodOrField; 168 } 169 170 /** 171 * Getter. 172 * @return boolean 173 */ 174 public boolean isHasNonPrivateStaticMethodOrField() { 175 return hasNonPrivateStaticMethodOrField; 176 } 177 178 /** 179 * Getter. 180 * @return boolean 181 */ 182 public boolean isHasDefaultCtor() { 183 return hasDefaultCtor; 184 } 185 186 /** 187 * Getter. 188 * @return boolean 189 */ 190 public boolean isHasPublicCtor() { 191 return hasPublicCtor; 192 } 193 194 /** 195 * Main method to gather statistics. 196 */ 197 public void invoke() { 198 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 199 hasNonStaticMethodOrField = false; 200 hasNonPrivateStaticMethodOrField = false; 201 hasDefaultCtor = true; 202 hasPublicCtor = false; 203 DetailAST child = objBlock.getFirstChild(); 204 205 while (child != null) { 206 final int type = child.getType(); 207 if (type == TokenTypes.METHOD_DEF 208 || type == TokenTypes.VARIABLE_DEF) { 209 final DetailAST modifiers = 210 child.findFirstToken(TokenTypes.MODIFIERS); 211 final boolean isStatic = 212 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 213 214 if (isStatic) { 215 final boolean isPrivate = 216 modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null; 217 218 if (!isPrivate) { 219 hasNonPrivateStaticMethodOrField = true; 220 } 221 } 222 else { 223 hasNonStaticMethodOrField = true; 224 } 225 } 226 if (type == TokenTypes.CTOR_DEF) { 227 hasDefaultCtor = false; 228 final DetailAST modifiers = 229 child.findFirstToken(TokenTypes.MODIFIERS); 230 if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null 231 && modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) == null) { 232 // treat package visible as public 233 // for the purpose of this Check 234 hasPublicCtor = true; 235 } 236 } 237 child = child.getNextSibling(); 238 } 239 } 240 241 } 242 243}