View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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   * <p>
29   * Makes sure that utility classes (classes that contain only static methods or fields in their API)
30   * do not have a public constructor.
31   * </p>
32   * <p>
33   * Rationale: Instantiating utility classes does not make sense.
34   * Hence, the constructors should either be private or (if you want to allow subclassing) protected.
35   * A common mistake is forgetting to hide the default constructor.
36   * </p>
37   * <p>
38   * If you make the constructor protected you may want to consider the following constructor
39   * implementation technique to disallow instantiating subclasses:
40   * </p>
41   * <pre>
42   * public class StringUtils // not final to allow subclassing
43   * {
44   *   protected StringUtils() {
45   *     // prevents calls from subclass
46   *     throw new UnsupportedOperationException();
47   *   }
48   *
49   *   public static int count(char c, String s) {
50   *     // ...
51   *   }
52   * }
53   * </pre>
54   * <p>
55   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
56   * </p>
57   * <p>
58   * Violation Message Keys:
59   * </p>
60   * <ul>
61   * <li>
62   * {@code hide.utility.class}
63   * </li>
64   * </ul>
65   *
66   * @since 3.1
67   */
68  @StatelessCheck
69  public class HideUtilityClassConstructorCheck extends AbstractCheck {
70  
71      /**
72       * A key is pointing to the warning message text in "messages.properties"
73       * file.
74       */
75      public static final String MSG_KEY = "hide.utility.class";
76  
77      @Override
78      public int[] getDefaultTokens() {
79          return getRequiredTokens();
80      }
81  
82      @Override
83      public int[] getAcceptableTokens() {
84          return getRequiredTokens();
85      }
86  
87      @Override
88      public int[] getRequiredTokens() {
89          return new int[] {TokenTypes.CLASS_DEF};
90      }
91  
92      @Override
93      public void visitToken(DetailAST ast) {
94          // abstract class could not have private constructor
95          if (!isAbstract(ast)) {
96              final boolean hasStaticModifier = isStatic(ast);
97  
98              final Details details = new Details(ast);
99              details.invoke();
100 
101             final boolean hasDefaultCtor = details.isHasDefaultCtor();
102             final boolean hasPublicCtor = details.isHasPublicCtor();
103             final boolean hasNonStaticMethodOrField = details.isHasNonStaticMethodOrField();
104             final boolean hasNonPrivateStaticMethodOrField =
105                     details.isHasNonPrivateStaticMethodOrField();
106 
107             final boolean hasAccessibleCtor = hasDefaultCtor || hasPublicCtor;
108 
109             // figure out if class extends java.lang.object directly
110             // keep it simple for now and get a 99% solution
111             final boolean extendsJlo =
112                 ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE) == null;
113 
114             final boolean isUtilClass = extendsJlo
115                 && !hasNonStaticMethodOrField && hasNonPrivateStaticMethodOrField;
116 
117             if (isUtilClass && hasAccessibleCtor && !hasStaticModifier) {
118                 log(ast, MSG_KEY);
119             }
120         }
121     }
122 
123     /**
124      * Returns true if given class is abstract or false.
125      *
126      * @param ast class definition for check.
127      * @return true if a given class declared as abstract.
128      */
129     private static boolean isAbstract(DetailAST ast) {
130         return ast.findFirstToken(TokenTypes.MODIFIERS)
131             .findFirstToken(TokenTypes.ABSTRACT) != null;
132     }
133 
134     /**
135      * Returns true if given class is static or false.
136      *
137      * @param ast class definition for check.
138      * @return true if a given class declared as static.
139      */
140     private static boolean isStatic(DetailAST ast) {
141         return ast.findFirstToken(TokenTypes.MODIFIERS)
142             .findFirstToken(TokenTypes.LITERAL_STATIC) != null;
143     }
144 
145     /**
146      * Details of class that are required for validation.
147      */
148     private static final class Details {
149 
150         /** Class ast. */
151         private final DetailAST ast;
152         /** Result of details gathering. */
153         private boolean hasNonStaticMethodOrField;
154         /** Result of details gathering. */
155         private boolean hasNonPrivateStaticMethodOrField;
156         /** Result of details gathering. */
157         private boolean hasDefaultCtor;
158         /** Result of details gathering. */
159         private boolean hasPublicCtor;
160 
161         /**
162          * C-tor.
163          *
164          * @param ast class ast
165          */
166         private Details(DetailAST ast) {
167             this.ast = ast;
168         }
169 
170         /**
171          * Getter.
172          *
173          * @return boolean
174          */
175         public boolean isHasNonStaticMethodOrField() {
176             return hasNonStaticMethodOrField;
177         }
178 
179         /**
180          * Getter.
181          *
182          * @return boolean
183          */
184         public boolean isHasNonPrivateStaticMethodOrField() {
185             return hasNonPrivateStaticMethodOrField;
186         }
187 
188         /**
189          * Getter.
190          *
191          * @return boolean
192          */
193         public boolean isHasDefaultCtor() {
194             return hasDefaultCtor;
195         }
196 
197         /**
198          * Getter.
199          *
200          * @return boolean
201          */
202         public boolean isHasPublicCtor() {
203             return hasPublicCtor;
204         }
205 
206         /**
207          * Main method to gather statistics.
208          */
209         public void invoke() {
210             final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
211             hasDefaultCtor = true;
212             DetailAST child = objBlock.getFirstChild();
213 
214             while (child != null) {
215                 final int type = child.getType();
216                 if (type == TokenTypes.METHOD_DEF
217                         || type == TokenTypes.VARIABLE_DEF) {
218                     final DetailAST modifiers =
219                         child.findFirstToken(TokenTypes.MODIFIERS);
220                     final boolean isStatic =
221                         modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
222 
223                     if (isStatic) {
224                         final boolean isPrivate =
225                                 modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
226 
227                         if (!isPrivate) {
228                             hasNonPrivateStaticMethodOrField = true;
229                         }
230                     }
231                     else {
232                         hasNonStaticMethodOrField = true;
233                     }
234                 }
235                 if (type == TokenTypes.CTOR_DEF) {
236                     hasDefaultCtor = false;
237                     final DetailAST modifiers =
238                         child.findFirstToken(TokenTypes.MODIFIERS);
239                     if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null
240                         && modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) == null) {
241                         // treat package visible as public
242                         // for the purpose of this Check
243                         hasPublicCtor = true;
244                     }
245                 }
246                 child = child.getNextSibling();
247             }
248         }
249 
250     }
251 
252 }