001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks.design; 020 021import com.puppycrawl.tools.checkstyle.api.Check; 022import com.puppycrawl.tools.checkstyle.api.TokenTypes; 023import com.puppycrawl.tools.checkstyle.api.DetailAST; 024 025/** 026 * Make sure that utility classes (classes that contain only static methods) 027 * do not have a public constructor. 028 * <p> 029 * Rationale: Instantiating utility classes does not make sense. 030 * A common mistake is forgetting to hide the default constructor. 031 * </p> 032 * 033 * @author lkuehne 034 */ 035public class HideUtilityClassConstructorCheck extends Check 036{ 037 @Override 038 public int[] getDefaultTokens() 039 { 040 return new int[] {TokenTypes.CLASS_DEF}; 041 } 042 043 @Override 044 public void visitToken(DetailAST ast) 045 { 046 if (isAbstract(ast)) { 047 // abstract class could not have private constructor 048 return; 049 } 050 051 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 052 DetailAST child = objBlock.getFirstChild(); 053 final boolean hasStaticModifier = isStatic(ast); 054 boolean hasMethodOrField = false; 055 boolean hasNonStaticMethodOrField = false; 056 boolean hasNonPrivateStaticMethodOrField = false; 057 boolean hasDefaultCtor = true; 058 boolean hasPublicCtor = false; 059 060 while (child != null) { 061 final int type = child.getType(); 062 if (type == TokenTypes.METHOD_DEF 063 || type == TokenTypes.VARIABLE_DEF) 064 { 065 hasMethodOrField = true; 066 final DetailAST modifiers = 067 child.findFirstToken(TokenTypes.MODIFIERS); 068 final boolean isStatic = 069 modifiers.branchContains(TokenTypes.LITERAL_STATIC); 070 final boolean isPrivate = 071 modifiers.branchContains(TokenTypes.LITERAL_PRIVATE); 072 073 if (!isStatic && !isPrivate) { 074 hasNonStaticMethodOrField = true; 075 } 076 if (isStatic && !isPrivate) { 077 hasNonPrivateStaticMethodOrField = true; 078 } 079 } 080 if (type == TokenTypes.CTOR_DEF) { 081 hasDefaultCtor = false; 082 final DetailAST modifiers = 083 child.findFirstToken(TokenTypes.MODIFIERS); 084 if (!modifiers.branchContains(TokenTypes.LITERAL_PRIVATE) 085 && !modifiers.branchContains(TokenTypes.LITERAL_PROTECTED)) 086 { 087 // treat package visible as public 088 // for the purpose of this Check 089 hasPublicCtor = true; 090 } 091 092 } 093 child = child.getNextSibling(); 094 } 095 096 final boolean hasAccessibleCtor = (hasDefaultCtor || hasPublicCtor); 097 098 // figure out if class extends java.lang.object directly 099 // keep it simple for now and get a 99% solution 100 // TODO: check for "extends java.lang.Object" and "extends Object" 101 // consider "import org.omg.CORBA.*" 102 final boolean extendsJLO = // J.Lo even made it into in our sources :-) 103 ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE) == null; 104 105 final boolean isUtilClass = extendsJLO && hasMethodOrField 106 && !hasNonStaticMethodOrField && hasNonPrivateStaticMethodOrField; 107 108 if (isUtilClass && (hasAccessibleCtor && !hasStaticModifier)) { 109 log(ast.getLineNo(), ast.getColumnNo(), "hide.utility.class"); 110 } 111 } 112 113 /** 114 * @param ast class definition for check. 115 * @return true if a given class declared as abstract. 116 */ 117 private boolean isAbstract(DetailAST ast) 118 { 119 return ast.findFirstToken(TokenTypes.MODIFIERS) 120 .branchContains(TokenTypes.ABSTRACT); 121 } 122 123 /** 124 * @param ast class definition for check. 125 * @return true if a given class declared as static. 126 */ 127 private boolean isStatic(DetailAST ast) 128 { 129 return ast.findFirstToken(TokenTypes.MODIFIERS) 130 .branchContains(TokenTypes.LITERAL_STATIC); 131 } 132}