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.coding;
020
021import com.google.common.collect.Lists;
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.FullIdent;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025import com.puppycrawl.tools.checkstyle.checks.AbstractTypeAwareCheck;
026import java.util.Iterator;
027import java.util.List;
028
029/**
030 * Checks for redundant exceptions declared in throws clause
031 * such as duplicates, unchecked exceptions or subclasses of
032 * another declared exception.
033 *
034 * <p>
035 * An example of how to configure the check is:
036 * </p>
037 * <pre>
038 * &lt;module name="RedundantThrows"&gt;
039 *    &lt;property name=&quot;allowUnchecked&quot; value=&quot;true&quot;/&gt;
040 *    &lt;property name=&quot;allowSubclasses&quot; value=&quot;true&quot;/&gt;
041 * &lt;/module&gt;
042 * </pre>
043 * @author o_sukhodolsky
044 */
045public class RedundantThrowsCheck extends AbstractTypeAwareCheck
046{
047    /**
048     * whether unchecked exceptions in throws
049     * are allowed or not
050     */
051    private boolean mAllowUnchecked;
052
053    /**
054     * whether subclass of another declared
055     * exception is allowed in throws clause
056     */
057    private boolean mAllowSubclasses;
058
059    /**
060     * Getter for allowUnchecked property.
061     * @param aAllowUnchecked whether unchecked excpetions in throws
062     *                         are allowed or not
063     */
064    public void setAllowUnchecked(boolean aAllowUnchecked)
065    {
066        mAllowUnchecked = aAllowUnchecked;
067    }
068
069    /**
070     * Getter for allowSubclasses property.
071     * @param aAllowSubclasses whether subclass of another declared
072     *                         exception is allowed in throws clause
073     */
074    public void setAllowSubclasses(boolean aAllowSubclasses)
075    {
076        mAllowSubclasses = aAllowSubclasses;
077    }
078
079    @Override
080    public int[] getDefaultTokens()
081    {
082        return new int[] {
083            TokenTypes.PACKAGE_DEF,
084            TokenTypes.IMPORT,
085            TokenTypes.CLASS_DEF,
086            TokenTypes.INTERFACE_DEF,
087            TokenTypes.ENUM_DEF,
088            TokenTypes.METHOD_DEF,
089            TokenTypes.CTOR_DEF,
090        };
091    }
092
093    @Override
094    protected final void processAST(DetailAST aAST)
095    {
096        final List<ClassInfo> knownExcs = Lists.newLinkedList();
097        final DetailAST throwsAST =
098            aAST.findFirstToken(TokenTypes.LITERAL_THROWS);
099        if (throwsAST != null) {
100            DetailAST child = throwsAST.getFirstChild();
101            while (child != null) {
102                if ((child.getType() == TokenTypes.IDENT)
103                    || (child.getType() == TokenTypes.DOT))
104                {
105                    final FullIdent fi = FullIdent.createFullIdent(child);
106                    checkException(fi, knownExcs);
107                }
108                child = child.getNextSibling();
109            }
110        }
111    }
112
113    @Override
114    protected final void logLoadError(Token aIdent)
115    {
116        logLoadErrorImpl(aIdent.getLineNo(), aIdent.getColumnNo(),
117                         "redundant.throws.classInfo", aIdent.getText());
118    }
119
120    /**
121     * Checks if an exception is already know (list of known
122     * exceptions contains it or its superclass) and it's not
123     * a superclass for some known exception and it's not
124     * an unchecked exception.
125     * If it's unknown then it will be added to ist of known exception.
126     * All subclasses of this exception will be deleted from known
127     * and the exception  will be added instead.
128     *
129     * @param aExc <code>FullIdent</code> of exception to check
130     * @param aKnownExcs list of already known exception
131     */
132    private void checkException(FullIdent aExc, List<ClassInfo> aKnownExcs)
133    {
134        // Let's try to load class.
135        final ClassInfo newClassInfo =
136            createClassInfo(new Token(aExc), getCurrentClassName());
137
138        if (!mAllowUnchecked && isUnchecked(newClassInfo.getClazz())) {
139            log(aExc.getLineNo(), aExc.getColumnNo(),
140                "redundant.throws.unchecked", aExc.getText());
141        }
142
143        boolean shouldAdd = true;
144        for (final Iterator<ClassInfo> known = aKnownExcs.iterator(); known
145                .hasNext();)
146        {
147            final ClassInfo ci = known.next();
148            final Token fi = ci.getName();
149
150            if (ci.getClazz() == newClassInfo.getClazz()) {
151                shouldAdd = false;
152                log(aExc.getLineNo(), aExc.getColumnNo(),
153                    "redundant.throws.duplicate", aExc.getText());
154            }
155            else if (!mAllowSubclasses) {
156                if (isSubclass(ci.getClazz(), newClassInfo.getClazz())) {
157                    known.remove();
158                    log(fi.getLineNo(), fi.getColumnNo(),
159                        "redundant.throws.subclass",
160                        fi.getText(), aExc.getText());
161                }
162                else if (isSubclass(newClassInfo.getClazz(), ci.getClazz())) {
163                    shouldAdd = false;
164                    log(aExc.getLineNo(), aExc.getColumnNo(),
165                        "redundant.throws.subclass",
166                        aExc.getText(), fi.getText());
167                }
168            }
169        }
170
171        if (shouldAdd) {
172            aKnownExcs.add(newClassInfo);
173        }
174    }
175}