001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.design;
021
022import java.util.Collections;
023import java.util.Set;
024
025import com.puppycrawl.tools.checkstyle.StatelessCheck;
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
030
031/**
032 * <div>
033 * Makes sure that utility classes (classes that contain only static methods or fields in their API)
034 * do not have a public constructor.
035 * </div>
036 *
037 * <p>
038 * Rationale: Instantiating utility classes does not make sense.
039 * Hence, the constructors should either be private or (if you want to allow subclassing) protected.
040 * A common mistake is forgetting to hide the default constructor.
041 * </p>
042 *
043 * <p>
044 * If you make the constructor protected you may want to consider the following constructor
045 * implementation technique to disallow instantiating subclasses:
046 * </p>
047 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
048 * public class StringUtils // not final to allow subclassing
049 * {
050 *   protected StringUtils() {
051 *     // prevents calls from subclass
052 *     throw new UnsupportedOperationException();
053 *   }
054 *
055 *   public static int count(char c, String s) {
056 *     // ...
057 *   }
058 * }
059 * </code></pre></div>
060 *
061 * @since 3.1
062 */
063@StatelessCheck
064public class HideUtilityClassConstructorCheck extends AbstractCheck {
065
066    /**
067     * A key is pointing to the warning message text in "messages.properties"
068     * file.
069     */
070    public static final String MSG_KEY = "hide.utility.class";
071
072    /**
073     * Ignore classes annotated with the specified annotation(s). Annotation names
074     * provided in this property must exactly match the annotation names on the classes.
075     * If the target class has annotations specified with their fully qualified names
076     * (including package), the annotations in this property should also be specified with
077     * their fully qualified names. Similarly, if the target class has annotations specified
078     * with their simple names, this property should contain the annotations with the same
079     * simple names.
080     */
081    private Set<String> ignoreAnnotatedBy = Collections.emptySet();
082
083    /**
084     * Setter to ignore classes annotated with the specified annotation(s). Annotation names
085     * provided in this property must exactly match the annotation names on the classes.
086     * If the target class has annotations specified with their fully qualified names
087     * (including package), the annotations in this property should also be specified with
088     * their fully qualified names. Similarly, if the target class has annotations specified
089     * with their simple names, this property should contain the annotations with the same
090     * simple names.
091     *
092     * @param annotationNames specified annotation(s)
093     * @since 10.20.0
094     */
095    public void setIgnoreAnnotatedBy(String... annotationNames) {
096        ignoreAnnotatedBy = Set.of(annotationNames);
097    }
098
099    @Override
100    public int[] getDefaultTokens() {
101        return getRequiredTokens();
102    }
103
104    @Override
105    public int[] getAcceptableTokens() {
106        return getRequiredTokens();
107    }
108
109    @Override
110    public int[] getRequiredTokens() {
111        return new int[] {TokenTypes.CLASS_DEF};
112    }
113
114    @Override
115    public void visitToken(DetailAST ast) {
116        // abstract class could not have private constructor
117        if (!isAbstract(ast) && !shouldIgnoreClass(ast)) {
118            final boolean hasStaticModifier = isStatic(ast);
119
120            final Details details = new Details(ast);
121            details.invoke();
122
123            final boolean hasDefaultCtor = details.isHasDefaultCtor();
124            final boolean hasPublicCtor = details.isHasPublicCtor();
125            final boolean hasNonStaticMethodOrField = details.isHasNonStaticMethodOrField();
126            final boolean hasNonPrivateStaticMethodOrField =
127                    details.isHasNonPrivateStaticMethodOrField();
128
129            final boolean hasAccessibleCtor = hasDefaultCtor || hasPublicCtor;
130
131            // figure out if class extends java.lang.object directly
132            // keep it simple for now and get a 99% solution
133            final boolean extendsJlo =
134                ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE) == null;
135
136            final boolean isUtilClass = extendsJlo
137                && !hasNonStaticMethodOrField && hasNonPrivateStaticMethodOrField;
138
139            if (isUtilClass && hasAccessibleCtor && !hasStaticModifier) {
140                log(ast, MSG_KEY);
141            }
142        }
143    }
144
145    /**
146     * Returns true if given class is abstract or false.
147     *
148     * @param ast class definition for check.
149     * @return true if a given class declared as abstract.
150     */
151    private static boolean isAbstract(DetailAST ast) {
152        return ast.findFirstToken(TokenTypes.MODIFIERS)
153            .findFirstToken(TokenTypes.ABSTRACT) != null;
154    }
155
156    /**
157     * Returns true if given class is static or false.
158     *
159     * @param ast class definition for check.
160     * @return true if a given class declared as static.
161     */
162    private static boolean isStatic(DetailAST ast) {
163        return ast.findFirstToken(TokenTypes.MODIFIERS)
164            .findFirstToken(TokenTypes.LITERAL_STATIC) != null;
165    }
166
167    /**
168     * Checks if class is annotated by specific annotation(s) to skip.
169     *
170     * @param ast class to check
171     * @return true if annotated by ignored annotations
172     */
173    private boolean shouldIgnoreClass(DetailAST ast) {
174        return AnnotationUtil.containsAnnotation(ast, ignoreAnnotatedBy);
175    }
176
177    /**
178     * Details of class that are required for validation.
179     */
180    private static final class Details {
181
182        /** Class ast. */
183        private final DetailAST ast;
184        /** Result of details gathering. */
185        private boolean hasNonStaticMethodOrField;
186        /** Result of details gathering. */
187        private boolean hasNonPrivateStaticMethodOrField;
188        /** Result of details gathering. */
189        private boolean hasDefaultCtor;
190        /** Result of details gathering. */
191        private boolean hasPublicCtor;
192
193        /**
194         * C-tor.
195         *
196         * @param ast class ast
197         */
198        private Details(DetailAST ast) {
199            this.ast = ast;
200        }
201
202        /**
203         * Getter.
204         *
205         * @return boolean
206         */
207        public boolean isHasNonStaticMethodOrField() {
208            return hasNonStaticMethodOrField;
209        }
210
211        /**
212         * Getter.
213         *
214         * @return boolean
215         */
216        public boolean isHasNonPrivateStaticMethodOrField() {
217            return hasNonPrivateStaticMethodOrField;
218        }
219
220        /**
221         * Getter.
222         *
223         * @return boolean
224         */
225        public boolean isHasDefaultCtor() {
226            return hasDefaultCtor;
227        }
228
229        /**
230         * Getter.
231         *
232         * @return boolean
233         */
234        public boolean isHasPublicCtor() {
235            return hasPublicCtor;
236        }
237
238        /**
239         * Main method to gather statistics.
240         */
241        public void invoke() {
242            final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
243            hasDefaultCtor = true;
244            DetailAST child = objBlock.getFirstChild();
245
246            while (child != null) {
247                final int type = child.getType();
248                if (type == TokenTypes.METHOD_DEF
249                        || type == TokenTypes.VARIABLE_DEF) {
250                    final DetailAST modifiers =
251                        child.findFirstToken(TokenTypes.MODIFIERS);
252                    final boolean isStatic =
253                        modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
254
255                    if (isStatic) {
256                        final boolean isPrivate =
257                                modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
258
259                        if (!isPrivate) {
260                            hasNonPrivateStaticMethodOrField = true;
261                        }
262                    }
263                    else {
264                        hasNonStaticMethodOrField = true;
265                    }
266                }
267                if (type == TokenTypes.CTOR_DEF) {
268                    hasDefaultCtor = false;
269                    final DetailAST modifiers =
270                        child.findFirstToken(TokenTypes.MODIFIERS);
271                    if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null
272                        && modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) == null) {
273                        // treat package visible as public
274                        // for the purpose of this Check
275                        hasPublicCtor = true;
276                    }
277                }
278                child = child.getNextSibling();
279            }
280        }
281
282    }
283
284}