001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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.ArrayDeque;
023import java.util.Deque;
024
025import com.puppycrawl.tools.checkstyle.api.Check;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
029
030/**
031 * <p>
032 * Checks that class which has only private ctors
033 * is declared as final. Doesn't check for classes nested in interfaces
034 * or annotations, as they are always <code>final</code> there.
035 * </p>
036 * <p>
037 * An example of how to configure the check is:
038 * </p>
039 * <pre>
040 * &lt;module name="FinalClass"/&gt;
041 * </pre>
042 * @author o_sukhodolsky
043 */
044public class FinalClassCheck
045    extends Check {
046
047    /**
048     * A key is pointing to the warning message text in "messages.properties"
049     * file.
050     */
051    public static final String MSG_KEY = "final.class";
052
053    /** Keeps ClassDesc objects for stack of declared classes. */
054    private final Deque<ClassDesc> classes = new ArrayDeque<>();
055
056    @Override
057    public int[] getDefaultTokens() {
058        return getAcceptableTokens();
059    }
060
061    @Override
062    public int[] getAcceptableTokens() {
063        return new int[]{TokenTypes.CLASS_DEF, TokenTypes.CTOR_DEF};
064    }
065
066    @Override
067    public int[] getRequiredTokens() {
068        return getAcceptableTokens();
069    }
070
071    @Override
072    public void visitToken(DetailAST ast) {
073        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
074
075        if (ast.getType() == TokenTypes.CLASS_DEF) {
076            final boolean isFinal = modifiers.branchContains(TokenTypes.FINAL);
077            final boolean isAbstract = modifiers.branchContains(TokenTypes.ABSTRACT);
078            classes.push(new ClassDesc(isFinal, isAbstract));
079        }
080        // ctors in enums don't matter
081        else if (!ScopeUtils.isInEnumBlock(ast)) {
082            final ClassDesc desc = classes.peek();
083            if (modifiers.branchContains(TokenTypes.LITERAL_PRIVATE)) {
084                desc.reportPrivateCtor();
085            }
086            else {
087                desc.reportNonPrivateCtor();
088            }
089        }
090    }
091
092    @Override
093    public void leaveToken(DetailAST ast) {
094        if (ast.getType() != TokenTypes.CLASS_DEF) {
095            return;
096        }
097
098        final ClassDesc desc = classes.pop();
099        if (!desc.isDeclaredAsFinal()
100            && !desc.isDeclaredAsAbstract()
101            && desc.isWithPrivateCtor()
102            && !desc.isWithNonPrivateCtor()
103            && !ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) {
104            final String className =
105                ast.findFirstToken(TokenTypes.IDENT).getText();
106            log(ast.getLineNo(), MSG_KEY, className);
107        }
108    }
109
110    /** Maintains information about class' ctors. */
111    private static final class ClassDesc {
112        /** Is class declared as final. */
113        private final boolean declaredAsFinal;
114
115        /** Is class declared as abstract. */
116        private final boolean declaredAsAbstract;
117
118        /** Does class have non-private ctors. */
119        private boolean withNonPrivateCtor;
120
121        /** Does class have private ctors. */
122        private boolean withPrivateCtor;
123
124        /**
125         *  Create a new ClassDesc instance.
126         *  @param declaredAsFinal indicates if the
127         *         class declared as final
128         *  @param declaredAsAbstract indicates if the
129         *         class declared as abstract
130         */
131        ClassDesc(boolean declaredAsFinal, boolean declaredAsAbstract) {
132            this.declaredAsFinal = declaredAsFinal;
133            this.declaredAsAbstract = declaredAsAbstract;
134        }
135
136        /** Adds private ctor. */
137        private void reportPrivateCtor() {
138            withPrivateCtor = true;
139        }
140
141        /** Adds non-private ctor. */
142        private void reportNonPrivateCtor() {
143            withNonPrivateCtor = true;
144        }
145
146        /**
147         *  Does class have private ctors.
148         *  @return true if class has private ctors
149         */
150        private boolean isWithPrivateCtor() {
151            return withPrivateCtor;
152        }
153
154        /**
155         *  Does class have non-private ctors.
156         *  @return true if class has non-private ctors
157         */
158        private boolean isWithNonPrivateCtor() {
159            return withNonPrivateCtor;
160        }
161
162        /**
163         *  Is class declared as final.
164         *  @return true if class is declared as final
165         */
166        private boolean isDeclaredAsFinal() {
167            return declaredAsFinal;
168        }
169
170        /**
171         *  Is class declared as abstract.
172         *  @return true if class is declared as final
173         */
174        private boolean isDeclaredAsAbstract() {
175            return declaredAsAbstract;
176        }
177    }
178}