1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2014 Oliver Burn
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ////////////////////////////////////////////////////////////////////////////////
19 package com.puppycrawl.tools.checkstyle.checks.design;
20
21 import com.puppycrawl.tools.checkstyle.api.Check;
22 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
23 import com.puppycrawl.tools.checkstyle.api.DetailAST;
24
25 /**
26 * Make sure that utility classes (classes that contain only static methods)
27 * do not have a public constructor.
28 * <p>
29 * Rationale: Instantiating utility classes does not make sense.
30 * A common mistake is forgetting to hide the default constructor.
31 * </p>
32 *
33 * @author lkuehne
34 */
35 public class HideUtilityClassConstructorCheck extends Check
36 {
37 @Override
38 public int[] getDefaultTokens()
39 {
40 return new int[] {TokenTypes.CLASS_DEF};
41 }
42
43 @Override
44 public void visitToken(DetailAST ast)
45 {
46 if (isAbstract(ast)) {
47 // abstract class could not have private constructor
48 return;
49 }
50
51 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
52 DetailAST child = objBlock.getFirstChild();
53 final boolean hasStaticModifier = isStatic(ast);
54 boolean hasMethodOrField = false;
55 boolean hasNonStaticMethodOrField = false;
56 boolean hasNonPrivateStaticMethodOrField = false;
57 boolean hasDefaultCtor = true;
58 boolean hasPublicCtor = false;
59
60 while (child != null) {
61 final int type = child.getType();
62 if (type == TokenTypes.METHOD_DEF
63 || type == TokenTypes.VARIABLE_DEF)
64 {
65 hasMethodOrField = true;
66 final DetailAST modifiers =
67 child.findFirstToken(TokenTypes.MODIFIERS);
68 final boolean isStatic =
69 modifiers.branchContains(TokenTypes.LITERAL_STATIC);
70 final boolean isPrivate =
71 modifiers.branchContains(TokenTypes.LITERAL_PRIVATE);
72
73 if (!isStatic && !isPrivate) {
74 hasNonStaticMethodOrField = true;
75 }
76 if (isStatic && !isPrivate) {
77 hasNonPrivateStaticMethodOrField = true;
78 }
79 }
80 if (type == TokenTypes.CTOR_DEF) {
81 hasDefaultCtor = false;
82 final DetailAST modifiers =
83 child.findFirstToken(TokenTypes.MODIFIERS);
84 if (!modifiers.branchContains(TokenTypes.LITERAL_PRIVATE)
85 && !modifiers.branchContains(TokenTypes.LITERAL_PROTECTED))
86 {
87 // treat package visible as public
88 // for the purpose of this Check
89 hasPublicCtor = true;
90 }
91
92 }
93 child = child.getNextSibling();
94 }
95
96 final boolean hasAccessibleCtor = (hasDefaultCtor || hasPublicCtor);
97
98 // figure out if class extends java.lang.object directly
99 // 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 }