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