View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 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 
118         /** Class ast. */
119         private final DetailAST ast;
120         /** Result of details gathering. */
121         private boolean hasMethodOrField;
122         /** Result of details gathering. */
123         private boolean hasNonStaticMethodOrField;
124         /** Result of details gathering. */
125         private boolean hasNonPrivateStaticMethodOrField;
126         /** Result of details gathering. */
127         private boolean hasDefaultCtor;
128         /** Result of details gathering. */
129         private boolean hasPublicCtor;
130 
131         /**
132          * C-tor.
133          * @param ast class ast
134          * */
135         Details(DetailAST ast) {
136             this.ast = ast;
137         }
138 
139         /**
140          * Getter.
141          * @return boolean
142          */
143         public boolean isHasMethodOrField() {
144             return hasMethodOrField;
145         }
146 
147         /**
148          * Getter.
149          * @return boolean
150          */
151         public boolean isHasNonStaticMethodOrField() {
152             return hasNonStaticMethodOrField;
153         }
154 
155         /**
156          * Getter.
157          * @return boolean
158          */
159         public boolean isHasNonPrivateStaticMethodOrField() {
160             return hasNonPrivateStaticMethodOrField;
161         }
162 
163         /**
164          * Getter.
165          * @return boolean
166          */
167         public boolean isHasDefaultCtor() {
168             return hasDefaultCtor;
169         }
170 
171         /**
172          * Getter.
173          * @return boolean
174          */
175         public boolean isHasPublicCtor() {
176             return hasPublicCtor;
177         }
178 
179         /**
180          * Main method to gather statistics.
181          */
182         public void invoke() {
183             final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
184             hasMethodOrField = false;
185             hasNonStaticMethodOrField = false;
186             hasNonPrivateStaticMethodOrField = false;
187             hasDefaultCtor = true;
188             hasPublicCtor = false;
189             DetailAST child = objBlock.getFirstChild();
190 
191             while (child != null) {
192                 final int type = child.getType();
193                 if (type == TokenTypes.METHOD_DEF
194                         || type == TokenTypes.VARIABLE_DEF) {
195                     hasMethodOrField = true;
196                     final DetailAST modifiers =
197                         child.findFirstToken(TokenTypes.MODIFIERS);
198                     final boolean isStatic =
199                         modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
200                     final boolean isPrivate =
201                         modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
202 
203                     if (!isStatic) {
204                         hasNonStaticMethodOrField = true;
205                     }
206                     if (isStatic && !isPrivate) {
207                         hasNonPrivateStaticMethodOrField = true;
208                     }
209                 }
210                 if (type == TokenTypes.CTOR_DEF) {
211                     hasDefaultCtor = false;
212                     final DetailAST modifiers =
213                         child.findFirstToken(TokenTypes.MODIFIERS);
214                     if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null
215                         && modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) == null) {
216                         // treat package visible as public
217                         // for the purpose of this Check
218                         hasPublicCtor = true;
219                     }
220                 }
221                 child = child.getNextSibling();
222             }
223         }
224 
225     }
226 
227 }