001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.design;
020
021import com.puppycrawl.tools.checkstyle.api.Check;
022import com.puppycrawl.tools.checkstyle.api.TokenTypes;
023import com.puppycrawl.tools.checkstyle.api.DetailAST;
024
025/**
026 * Make sure that utility classes (classes that contain only static methods)
027 * do not have a public constructor.
028 * <p>
029 * Rationale: Instantiating utility classes does not make sense.
030 * A common mistake is forgetting to hide the default constructor.
031 * </p>
032 *
033 * @author lkuehne
034 */
035public class HideUtilityClassConstructorCheck extends Check
036{
037    @Override
038    public int[] getDefaultTokens()
039    {
040        return new int[] {TokenTypes.CLASS_DEF};
041    }
042
043    @Override
044    public void visitToken(DetailAST ast)
045    {
046        if (isAbstract(ast)) {
047            // abstract class could not have private constructor
048            return;
049        }
050
051        final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
052        DetailAST child = objBlock.getFirstChild();
053        final boolean hasStaticModifier = isStatic(ast);
054        boolean hasMethodOrField = false;
055        boolean hasNonStaticMethodOrField = false;
056        boolean hasNonPrivateStaticMethodOrField = false;
057        boolean hasDefaultCtor = true;
058        boolean hasPublicCtor = false;
059
060        while (child != null) {
061            final int type = child.getType();
062            if (type == TokenTypes.METHOD_DEF
063                    || type == TokenTypes.VARIABLE_DEF)
064            {
065                hasMethodOrField = true;
066                final DetailAST modifiers =
067                    child.findFirstToken(TokenTypes.MODIFIERS);
068                final boolean isStatic =
069                    modifiers.branchContains(TokenTypes.LITERAL_STATIC);
070                final boolean isPrivate =
071                    modifiers.branchContains(TokenTypes.LITERAL_PRIVATE);
072
073                if (!isStatic && !isPrivate) {
074                    hasNonStaticMethodOrField = true;
075                }
076                if (isStatic && !isPrivate) {
077                    hasNonPrivateStaticMethodOrField = true;
078                }
079            }
080            if (type == TokenTypes.CTOR_DEF) {
081                hasDefaultCtor = false;
082                final DetailAST modifiers =
083                    child.findFirstToken(TokenTypes.MODIFIERS);
084                if (!modifiers.branchContains(TokenTypes.LITERAL_PRIVATE)
085                    && !modifiers.branchContains(TokenTypes.LITERAL_PROTECTED))
086                {
087                    // treat package visible as public
088                    // for the purpose of this Check
089                    hasPublicCtor = true;
090                }
091
092            }
093            child = child.getNextSibling();
094        }
095
096        final boolean hasAccessibleCtor = (hasDefaultCtor || hasPublicCtor);
097
098        // figure out if class extends java.lang.object directly
099        // keep it simple for now and get a 99% solution
100        // TODO: check for "extends java.lang.Object" and "extends Object"
101        // consider "import org.omg.CORBA.*"
102        final boolean extendsJLO = // J.Lo even made it into in our sources :-)
103            ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE) == null;
104
105        final boolean isUtilClass = extendsJLO && hasMethodOrField
106            && !hasNonStaticMethodOrField && hasNonPrivateStaticMethodOrField;
107
108        if (isUtilClass && (hasAccessibleCtor && !hasStaticModifier)) {
109            log(ast.getLineNo(), ast.getColumnNo(), "hide.utility.class");
110        }
111    }
112
113    /**
114     * @param ast class definition for check.
115     * @return true if a given class declared as abstract.
116     */
117    private boolean isAbstract(DetailAST ast)
118    {
119        return ast.findFirstToken(TokenTypes.MODIFIERS)
120            .branchContains(TokenTypes.ABSTRACT);
121    }
122
123    /**
124     * @param ast class definition for check.
125     * @return true if a given class declared as static.
126     */
127    private boolean isStatic(DetailAST ast)
128    {
129        return ast.findFirstToken(TokenTypes.MODIFIERS)
130            .branchContains(TokenTypes.LITERAL_STATIC);
131    }
132}