View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
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  
20  package com.puppycrawl.tools.checkstyle.checks.design;
21  
22  import com.puppycrawl.tools.checkstyle.StatelessCheck;
23  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24  import com.puppycrawl.tools.checkstyle.api.DetailAST;
25  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26  
27  /**
28   * Make sure that utility classes (classes that contain only static methods)
29   * do not have a public constructor.
30   * <p>
31   * Rationale: Instantiating utility classes does not make sense.
32   * A common mistake is forgetting to hide the default constructor.
33   * </p>
34   *
35   * @author lkuehne
36   */
37  @StatelessCheck
38  public class HideUtilityClassConstructorCheck extends AbstractCheck {
39  
40      /**
41       * A key is pointing to the warning message text in "messages.properties"
42       * file.
43       */
44      public static final String MSG_KEY = "hide.utility.class";
45  
46      @Override
47      public int[] getDefaultTokens() {
48          return getRequiredTokens();
49      }
50  
51      @Override
52      public int[] getAcceptableTokens() {
53          return getRequiredTokens();
54      }
55  
56      @Override
57      public int[] getRequiredTokens() {
58          return new int[] {TokenTypes.CLASS_DEF};
59      }
60  
61      @Override
62      public void visitToken(DetailAST ast) {
63          // abstract class could not have private constructor
64          if (!isAbstract(ast)) {
65              final boolean hasStaticModifier = isStatic(ast);
66  
67              final Details details = new Details(ast);
68              details.invoke();
69  
70              final boolean hasDefaultCtor = details.isHasDefaultCtor();
71              final boolean hasPublicCtor = details.isHasPublicCtor();
72              final boolean hasMethodOrField = details.isHasMethodOrField();
73              final boolean hasNonStaticMethodOrField = details.isHasNonStaticMethodOrField();
74              final boolean hasNonPrivateStaticMethodOrField =
75                      details.isHasNonPrivateStaticMethodOrField();
76  
77              final boolean hasAccessibleCtor = hasDefaultCtor || hasPublicCtor;
78  
79              // figure out if class extends java.lang.object directly
80              // keep it simple for now and get a 99% solution
81              final boolean extendsJlo =
82                  ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE) == null;
83  
84              final boolean isUtilClass = extendsJlo && hasMethodOrField
85                  && !hasNonStaticMethodOrField && hasNonPrivateStaticMethodOrField;
86  
87              if (isUtilClass && hasAccessibleCtor && !hasStaticModifier) {
88                  log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY);
89              }
90          }
91      }
92  
93      /**
94       * Returns true if given class is abstract or false.
95       * @param ast class definition for check.
96       * @return true if a given class declared as abstract.
97       */
98      private static boolean isAbstract(DetailAST ast) {
99          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         /** Class ast. */
118         private final DetailAST ast;
119         /** Result of details gathering. */
120         private boolean hasMethodOrField;
121         /** Result of details gathering. */
122         private boolean hasNonStaticMethodOrField;
123         /** Result of details gathering. */
124         private boolean hasNonPrivateStaticMethodOrField;
125         /** Result of details gathering. */
126         private boolean hasDefaultCtor;
127         /** Result of details gathering. */
128         private boolean hasPublicCtor;
129 
130         /**
131          * C-tor.
132          * @param ast class ast
133          * */
134         Details(DetailAST ast) {
135             this.ast = ast;
136         }
137 
138         /**
139          * Getter.
140          * @return boolean
141          */
142         public boolean isHasMethodOrField() {
143             return hasMethodOrField;
144         }
145 
146         /**
147          * Getter.
148          * @return boolean
149          */
150         public boolean isHasNonStaticMethodOrField() {
151             return hasNonStaticMethodOrField;
152         }
153 
154         /**
155          * Getter.
156          * @return boolean
157          */
158         public boolean isHasNonPrivateStaticMethodOrField() {
159             return hasNonPrivateStaticMethodOrField;
160         }
161 
162         /**
163          * Getter.
164          * @return boolean
165          */
166         public boolean isHasDefaultCtor() {
167             return hasDefaultCtor;
168         }
169 
170         /**
171          * Getter.
172          * @return boolean
173          */
174         public boolean isHasPublicCtor() {
175             return hasPublicCtor;
176         }
177 
178         /**
179          * Main method to gather statistics.
180          */
181         public void invoke() {
182             final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
183             hasMethodOrField = false;
184             hasNonStaticMethodOrField = false;
185             hasNonPrivateStaticMethodOrField = false;
186             hasDefaultCtor = true;
187             hasPublicCtor = false;
188             DetailAST child = objBlock.getFirstChild();
189 
190             while (child != null) {
191                 final int type = child.getType();
192                 if (type == TokenTypes.METHOD_DEF
193                         || type == TokenTypes.VARIABLE_DEF) {
194                     hasMethodOrField = true;
195                     final DetailAST modifiers =
196                         child.findFirstToken(TokenTypes.MODIFIERS);
197                     final boolean isStatic =
198                         modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
199                     final boolean isPrivate =
200                         modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
201 
202                     if (!isStatic) {
203                         hasNonStaticMethodOrField = true;
204                     }
205                     if (isStatic && !isPrivate) {
206                         hasNonPrivateStaticMethodOrField = true;
207                     }
208                 }
209                 if (type == TokenTypes.CTOR_DEF) {
210                     hasDefaultCtor = false;
211                     final DetailAST modifiers =
212                         child.findFirstToken(TokenTypes.MODIFIERS);
213                     if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null
214                         && modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) == null) {
215                         // treat package visible as public
216                         // for the purpose of this Check
217                         hasPublicCtor = true;
218                     }
219 
220                 }
221                 child = child.getNextSibling();
222             }
223         }
224     }
225 }