001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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 * Make sure that utility classes (classes that contain only static methods) 029 * do not have a public constructor. 030 * <p> 031 * Rationale: Instantiating utility classes does not make sense. 032 * A common mistake is forgetting to hide the default constructor. 033 * </p> 034 * 035 */ 036@StatelessCheck 037public class HideUtilityClassConstructorCheck extends AbstractCheck { 038 039 /** 040 * A key is pointing to the warning message text in "messages.properties" 041 * file. 042 */ 043 public static final String MSG_KEY = "hide.utility.class"; 044 045 @Override 046 public int[] getDefaultTokens() { 047 return getRequiredTokens(); 048 } 049 050 @Override 051 public int[] getAcceptableTokens() { 052 return getRequiredTokens(); 053 } 054 055 @Override 056 public int[] getRequiredTokens() { 057 return new int[] {TokenTypes.CLASS_DEF}; 058 } 059 060 @Override 061 public void visitToken(DetailAST ast) { 062 // abstract class could not have private constructor 063 if (!isAbstract(ast)) { 064 final boolean hasStaticModifier = isStatic(ast); 065 066 final Details details = new Details(ast); 067 details.invoke(); 068 069 final boolean hasDefaultCtor = details.isHasDefaultCtor(); 070 final boolean hasPublicCtor = details.isHasPublicCtor(); 071 final boolean hasMethodOrField = details.isHasMethodOrField(); 072 final boolean hasNonStaticMethodOrField = details.isHasNonStaticMethodOrField(); 073 final boolean hasNonPrivateStaticMethodOrField = 074 details.isHasNonPrivateStaticMethodOrField(); 075 076 final boolean hasAccessibleCtor = hasDefaultCtor || hasPublicCtor; 077 078 // figure out if class extends java.lang.object directly 079 // keep it simple for now and get a 99% solution 080 final boolean extendsJlo = 081 ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE) == null; 082 083 final boolean isUtilClass = extendsJlo && hasMethodOrField 084 && !hasNonStaticMethodOrField && hasNonPrivateStaticMethodOrField; 085 086 if (isUtilClass && hasAccessibleCtor && !hasStaticModifier) { 087 log(ast, MSG_KEY); 088 } 089 } 090 } 091 092 /** 093 * Returns true if given class is abstract or false. 094 * @param ast class definition for check. 095 * @return true if a given class declared as abstract. 096 */ 097 private static boolean isAbstract(DetailAST ast) { 098 return ast.findFirstToken(TokenTypes.MODIFIERS) 099 .findFirstToken(TokenTypes.ABSTRACT) != null; 100 } 101 102 /** 103 * Returns true if given class is static or false. 104 * @param ast class definition for check. 105 * @return true if a given class declared as static. 106 */ 107 private static boolean isStatic(DetailAST ast) { 108 return ast.findFirstToken(TokenTypes.MODIFIERS) 109 .findFirstToken(TokenTypes.LITERAL_STATIC) != null; 110 } 111 112 /** 113 * Details of class that are required for validation. 114 */ 115 private static class Details { 116 117 /** Class ast. */ 118 private final DetailAST ast; 119 /** Result of details gathering. */ 120 private boolean hasMethodOrField; 121 /** Result of details gathering. */ 122 private boolean hasNonStaticMethodOrField; 123 /** Result of details gathering. */ 124 private boolean hasNonPrivateStaticMethodOrField; 125 /** Result of details gathering. */ 126 private boolean hasDefaultCtor; 127 /** Result of details gathering. */ 128 private boolean hasPublicCtor; 129 130 /** 131 * C-tor. 132 * @param ast class ast 133 * */ 134 Details(DetailAST ast) { 135 this.ast = ast; 136 } 137 138 /** 139 * Getter. 140 * @return boolean 141 */ 142 public boolean isHasMethodOrField() { 143 return hasMethodOrField; 144 } 145 146 /** 147 * Getter. 148 * @return boolean 149 */ 150 public boolean isHasNonStaticMethodOrField() { 151 return hasNonStaticMethodOrField; 152 } 153 154 /** 155 * Getter. 156 * @return boolean 157 */ 158 public boolean isHasNonPrivateStaticMethodOrField() { 159 return hasNonPrivateStaticMethodOrField; 160 } 161 162 /** 163 * Getter. 164 * @return boolean 165 */ 166 public boolean isHasDefaultCtor() { 167 return hasDefaultCtor; 168 } 169 170 /** 171 * Getter. 172 * @return boolean 173 */ 174 public boolean isHasPublicCtor() { 175 return hasPublicCtor; 176 } 177 178 /** 179 * Main method to gather statistics. 180 */ 181 public void invoke() { 182 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 183 hasMethodOrField = false; 184 hasNonStaticMethodOrField = false; 185 hasNonPrivateStaticMethodOrField = false; 186 hasDefaultCtor = true; 187 hasPublicCtor = false; 188 DetailAST child = objBlock.getFirstChild(); 189 190 while (child != null) { 191 final int type = child.getType(); 192 if (type == TokenTypes.METHOD_DEF 193 || type == TokenTypes.VARIABLE_DEF) { 194 hasMethodOrField = true; 195 final DetailAST modifiers = 196 child.findFirstToken(TokenTypes.MODIFIERS); 197 final boolean isStatic = 198 modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 199 final boolean isPrivate = 200 modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null; 201 202 if (!isStatic) { 203 hasNonStaticMethodOrField = true; 204 } 205 if (isStatic && !isPrivate) { 206 hasNonPrivateStaticMethodOrField = true; 207 } 208 } 209 if (type == TokenTypes.CTOR_DEF) { 210 hasDefaultCtor = false; 211 final DetailAST modifiers = 212 child.findFirstToken(TokenTypes.MODIFIERS); 213 if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null 214 && modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) == null) { 215 // treat package visible as public 216 // for the purpose of this Check 217 hasPublicCtor = true; 218 } 219 } 220 child = child.getNextSibling(); 221 } 222 } 223 224 } 225 226}