1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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   */
36  @StatelessCheck
37  public class HideUtilityClassConstructorCheck extends AbstractCheck {
38  
39      /**
40       * A key is pointing to the warning message text in "messages.properties"
41       * file.
42       */
43      public static final String MSG_KEY = "hide.utility.class";
44  
45      @Override
46      public int[] getDefaultTokens() {
47          return getRequiredTokens();
48      }
49  
50      @Override
51      public int[] getAcceptableTokens() {
52          return getRequiredTokens();
53      }
54  
55      @Override
56      public int[] getRequiredTokens() {
57          return new int[] {TokenTypes.CLASS_DEF};
58      }
59  
60      @Override
61      public void visitToken(DetailAST ast) {
62          // abstract class could not have private constructor
63          if (!isAbstract(ast)) {
64              final boolean hasStaticModifier = isStatic(ast);
65  
66              final Details details = new Details(ast);
67              details.invoke();
68  
69              final boolean hasDefaultCtor = details.isHasDefaultCtor();
70              final boolean hasPublicCtor = details.isHasPublicCtor();
71              final boolean hasNonStaticMethodOrField = details.isHasNonStaticMethodOrField();
72              final boolean hasNonPrivateStaticMethodOrField =
73                      details.isHasNonPrivateStaticMethodOrField();
74  
75              final boolean hasAccessibleCtor = hasDefaultCtor || hasPublicCtor;
76  
77              // figure out if class extends java.lang.object directly
78              // keep it simple for now and get a 99% solution
79              final boolean extendsJlo =
80                  ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE) == null;
81  
82              final boolean isUtilClass = extendsJlo
83                  && !hasNonStaticMethodOrField && hasNonPrivateStaticMethodOrField;
84  
85              if (isUtilClass && hasAccessibleCtor && !hasStaticModifier) {
86                  log(ast, MSG_KEY);
87              }
88          }
89      }
90  
91      /**
92       * Returns true if given class is abstract or false.
93       * @param ast class definition for check.
94       * @return true if a given class declared as abstract.
95       */
96      private static boolean isAbstract(DetailAST ast) {
97          return ast.findFirstToken(TokenTypes.MODIFIERS)
98              .findFirstToken(TokenTypes.ABSTRACT) != null;
99      }
100 
101     /**
102      * Returns true if given class is static or false.
103      * @param ast class definition for check.
104      * @return true if a given class declared as static.
105      */
106     private static boolean isStatic(DetailAST ast) {
107         return ast.findFirstToken(TokenTypes.MODIFIERS)
108             .findFirstToken(TokenTypes.LITERAL_STATIC) != null;
109     }
110 
111     /**
112      * Details of class that are required for validation.
113      */
114     private static class Details {
115 
116         /** Class ast. */
117         private final DetailAST ast;
118         /** Result of details gathering. */
119         private boolean hasNonStaticMethodOrField;
120         /** Result of details gathering. */
121         private boolean hasNonPrivateStaticMethodOrField;
122         /** Result of details gathering. */
123         private boolean hasDefaultCtor;
124         /** Result of details gathering. */
125         private boolean hasPublicCtor;
126 
127         /**
128          * C-tor.
129          * @param ast class ast
130          * */
131         /* package */ Details(DetailAST ast) {
132             this.ast = ast;
133         }
134 
135         /**
136          * Getter.
137          * @return boolean
138          */
139         public boolean isHasNonStaticMethodOrField() {
140             return hasNonStaticMethodOrField;
141         }
142 
143         /**
144          * Getter.
145          * @return boolean
146          */
147         public boolean isHasNonPrivateStaticMethodOrField() {
148             return hasNonPrivateStaticMethodOrField;
149         }
150 
151         /**
152          * Getter.
153          * @return boolean
154          */
155         public boolean isHasDefaultCtor() {
156             return hasDefaultCtor;
157         }
158 
159         /**
160          * Getter.
161          * @return boolean
162          */
163         public boolean isHasPublicCtor() {
164             return hasPublicCtor;
165         }
166 
167         /**
168          * Main method to gather statistics.
169          */
170         public void invoke() {
171             final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
172             hasNonStaticMethodOrField = false;
173             hasNonPrivateStaticMethodOrField = false;
174             hasDefaultCtor = true;
175             hasPublicCtor = false;
176             DetailAST child = objBlock.getFirstChild();
177 
178             while (child != null) {
179                 final int type = child.getType();
180                 if (type == TokenTypes.METHOD_DEF
181                         || type == TokenTypes.VARIABLE_DEF) {
182                     final DetailAST modifiers =
183                         child.findFirstToken(TokenTypes.MODIFIERS);
184                     final boolean isStatic =
185                         modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
186 
187                     if (isStatic) {
188                         final boolean isPrivate =
189                                 modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
190 
191                         if (!isPrivate) {
192                             hasNonPrivateStaticMethodOrField = true;
193                         }
194                     }
195                     else {
196                         hasNonStaticMethodOrField = true;
197                     }
198                 }
199                 if (type == TokenTypes.CTOR_DEF) {
200                     hasDefaultCtor = false;
201                     final DetailAST modifiers =
202                         child.findFirstToken(TokenTypes.MODIFIERS);
203                     if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null
204                         && modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) == null) {
205                         // treat package visible as public
206                         // for the purpose of this Check
207                         hasPublicCtor = true;
208                     }
209                 }
210                 child = child.getNextSibling();
211             }
212         }
213 
214     }
215 
216 }