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.design;
020:
021: import com.puppycrawl.tools.checkstyle.api.DetailAST;
022: import com.puppycrawl.tools.checkstyle.api.TokenTypes;
023: import com.puppycrawl.tools.checkstyle.checks.AbstractFormatCheck;
024:
025: import java.util.Stack;
026:
027: /**
028: * <p> Ensures that exceptions (defined as any class name conforming
029: * to some regular expression) are immutable. That is, have only final
030: * fields.</p>
031: * <p> Rationale: Exception instances should represent an error
032: * condition. Having non final fields not only allows the state to be
033: * modified by accident and therefore mask the original condition but
034: * also allows developers to accidentally forget to initialise state
035: * thereby leading to code catching the exception to draw incorrect
036: * conclusions based on the state.</p>
037: *
038: * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
039: */
040: public final class MutableExceptionCheck extends AbstractFormatCheck {
041: /** Default value for format property. */
042: private static final String DEFAULT_FORMAT = "^.*Exception$|^.*Error$";
043: /** Stack of checking information for classes. */
044: private final Stack mCheckingStack = new Stack();
045: /** Should we check current class or not. */
046: private boolean mChecking;
047:
048: /** Creates new instance of the check. */
049: public MutableExceptionCheck() {
050: super (DEFAULT_FORMAT);
051: }
052:
053: /** {@inheritDoc} */
054: public int[] getDefaultTokens() {
055: return new int[] { TokenTypes.CLASS_DEF,
056: TokenTypes.VARIABLE_DEF };
057: }
058:
059: /** {@inheritDoc} */
060: public int[] getRequiredTokens() {
061: return getDefaultTokens();
062: }
063:
064: /** {@inheritDoc} */
065: public void visitToken(DetailAST aAST) {
066: switch (aAST.getType()) {
067: case TokenTypes.CLASS_DEF:
068: visitClassDef(aAST);
069: break;
070: case TokenTypes.VARIABLE_DEF:
071: visitVariableDef(aAST);
072: break;
073: default:
074: throw new IllegalStateException(aAST.toString());
075: }
076: }
077:
078: /** {@inheritDoc} */
079: public void leaveToken(DetailAST aAST) {
080: switch (aAST.getType()) {
081: case TokenTypes.CLASS_DEF:
082: leaveClassDef();
083: break;
084: default:
085: // Do nothing
086: }
087: }
088:
089: /**
090: * Called when we start processing class definition.
091: * @param aAST class definition node
092: */
093: private void visitClassDef(DetailAST aAST) {
094: mCheckingStack.push(mChecking ? Boolean.TRUE : Boolean.FALSE);
095: mChecking = isExceptionClass(aAST.findFirstToken(
096: TokenTypes.IDENT).getText());
097: }
098:
099: /** Called when we leave class definition. */
100: private void leaveClassDef() {
101: mChecking = ((Boolean) mCheckingStack.pop()).booleanValue();
102: }
103:
104: /**
105: * Checks variable definition.
106: * @param aAST variable def node for check.
107: */
108: private void visitVariableDef(DetailAST aAST) {
109: if (mChecking
110: && (aAST.getParent().getType() == TokenTypes.OBJBLOCK)) {
111: final DetailAST modifiersAST = aAST
112: .findFirstToken(TokenTypes.MODIFIERS);
113:
114: if (!(modifiersAST.findFirstToken(TokenTypes.FINAL) != null)) {
115: log(aAST.getLineNo(), aAST.getColumnNo(),
116: "mutable.exception", aAST.findFirstToken(
117: TokenTypes.IDENT).getText());
118: }
119: }
120: }
121:
122: /**
123: * @param aClassName class name to check
124: * @return true if a given class name confirms specified format
125: */
126: private boolean isExceptionClass(String aClassName) {
127: return getRegexp().matcher(aClassName).find();
128: }
129: }
|