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