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