001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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.coding;
021
022import java.util.Deque;
023import java.util.LinkedList;
024
025import antlr.collections.AST;
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.ScopeUtils;
030
031/**
032 * <p>
033 * Abstract class for checking that an overriding method with no parameters
034 * invokes the super method.
035 * </p>
036 * @author Rick Giles
037 */
038public abstract class AbstractSuperCheck
039        extends AbstractCheck {
040
041    /**
042     * A key is pointing to the warning message text in "messages.properties"
043     * file.
044     */
045    public static final String MSG_KEY = "missing.super.call";
046
047    /** Stack of methods. */
048    private final Deque<MethodNode> methodStack = new LinkedList<>();
049
050    /**
051     * Returns the name of the overriding method.
052     * @return the name of the overriding method.
053     */
054    protected abstract String getMethodName();
055
056    @Override
057    public int[] getAcceptableTokens() {
058        return new int[] {
059            TokenTypes.METHOD_DEF,
060            TokenTypes.LITERAL_SUPER,
061        };
062    }
063
064    @Override
065    public int[] getDefaultTokens() {
066        return getAcceptableTokens();
067    }
068
069    @Override
070    public int[] getRequiredTokens() {
071        return getDefaultTokens();
072    }
073
074    @Override
075    public void beginTree(DetailAST rootAST) {
076        methodStack.clear();
077    }
078
079    @Override
080    public void visitToken(DetailAST ast) {
081        if (isOverridingMethod(ast)) {
082            methodStack.add(new MethodNode(ast));
083        }
084        else if (isSuperCall(ast)) {
085            final MethodNode methodNode = methodStack.getLast();
086            methodNode.setCallingSuper();
087        }
088    }
089
090    /**
091     * Determines whether a 'super' literal is a call to the super method
092     * for this check.
093     * @param literalSuperAst the AST node of a 'super' literal.
094     * @return true if ast is a call to the super method for this check.
095     */
096    private boolean isSuperCall(DetailAST literalSuperAst) {
097        boolean superCall = false;
098
099        if (literalSuperAst.getType() == TokenTypes.LITERAL_SUPER) {
100            // dot operator?
101            final DetailAST dotAst = literalSuperAst.getParent();
102
103            if (!isSameNameMethod(literalSuperAst)
104                && !hasArguments(dotAst)) {
105                superCall = isSuperCallInOverridingMethod(dotAst);
106            }
107        }
108        return superCall;
109    }
110
111    /**
112     * Determines whether a super call in overriding method.
113     *
114     * @param ast The AST node of a 'dot operator' in 'super' call.
115     * @return true if super call in overriding method.
116     */
117    private boolean isSuperCallInOverridingMethod(DetailAST ast) {
118        boolean inOverridingMethod = false;
119        DetailAST dotAst = ast;
120
121        while (dotAst.getType() != TokenTypes.CTOR_DEF
122                && dotAst.getType() != TokenTypes.INSTANCE_INIT) {
123
124            if (dotAst.getType() == TokenTypes.METHOD_DEF) {
125                inOverridingMethod = isOverridingMethod(dotAst);
126                break;
127            }
128            dotAst = dotAst.getParent();
129
130        }
131        return inOverridingMethod;
132    }
133
134    /**
135     * Does method have any arguments.
136     * @param methodCallDotAst DOT DetailAST
137     * @return true if any parameters found
138     */
139    private static boolean hasArguments(DetailAST methodCallDotAst) {
140        final DetailAST argumentsList = methodCallDotAst.getNextSibling();
141        return argumentsList.getChildCount() > 0;
142    }
143
144    /**
145     * Is same name of method.
146     * @param ast method AST
147     * @return true if method name is the same
148     */
149    private boolean isSameNameMethod(DetailAST ast) {
150
151        AST sibling = ast.getNextSibling();
152        // ignore type parameters
153        if (sibling != null
154            && sibling.getType() == TokenTypes.TYPE_ARGUMENTS) {
155            sibling = sibling.getNextSibling();
156        }
157        if (sibling == null) {
158            return true;
159        }
160        final String name = sibling.getText();
161        return !getMethodName().equals(name);
162    }
163
164    @Override
165    public void leaveToken(DetailAST ast) {
166        if (isOverridingMethod(ast)) {
167            final MethodNode methodNode =
168                methodStack.removeLast();
169            if (!methodNode.isCallingSuper()) {
170                final DetailAST methodAST = methodNode.getMethod();
171                final DetailAST nameAST =
172                    methodAST.findFirstToken(TokenTypes.IDENT);
173                log(nameAST.getLineNo(), nameAST.getColumnNo(),
174                    MSG_KEY, nameAST.getText());
175            }
176        }
177    }
178
179    /**
180     * Determines whether an AST is a method definition for this check,
181     * with 0 parameters.
182     * @param ast the method definition AST.
183     * @return true if the method of ast is a method for this check.
184     */
185    private boolean isOverridingMethod(DetailAST ast) {
186        boolean overridingMethod = false;
187
188        if (ast.getType() == TokenTypes.METHOD_DEF
189                && !ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) {
190            final DetailAST nameAST = ast.findFirstToken(TokenTypes.IDENT);
191            final String name = nameAST.getText();
192            final DetailAST modifiersAST = ast.findFirstToken(TokenTypes.MODIFIERS);
193
194            if (getMethodName().equals(name)
195                    && !modifiersAST.branchContains(TokenTypes.LITERAL_NATIVE)) {
196                final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
197                overridingMethod = params.getChildCount() == 0;
198            }
199        }
200        return overridingMethod;
201    }
202
203    /**
204     * Stack node for a method definition and a record of
205     * whether the method has a call to the super method.
206     * @author Rick Giles
207     */
208    private static class MethodNode {
209        /** Method definition. */
210        private final DetailAST method;
211
212        /** True if the overriding method calls the super method. */
213        private boolean callingSuper;
214
215        /**
216         * Constructs a stack node for a method definition.
217         * @param ast AST for the method definition.
218         */
219        MethodNode(DetailAST ast) {
220            method = ast;
221            callingSuper = false;
222        }
223
224        /**
225         * Records that the overriding method has a call to the super method.
226         */
227        public void setCallingSuper() {
228            callingSuper = true;
229        }
230
231        /**
232         * Determines whether the overriding method has a call to the super
233         * method.
234         * @return true if the overriding method has a call to the super method.
235         */
236        public boolean isCallingSuper() {
237            return callingSuper;
238        }
239
240        /**
241         * Returns the overriding method definition AST.
242         * @return the overriding method definition AST.
243         */
244        public DetailAST getMethod() {
245            return method;
246        }
247    }
248}