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