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