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 (classes with names conforming to some regular 028 * expression and explicitly extending classes with names conforming to other 029 * regular expression) are immutable. That is, they have only final 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 and extendedClassNameFormat properties. */ 042 private static final String DEFAULT_FORMAT = "^.*Exception$|^.*Error$|^.*Throwable$"; 043 /** Pattern for class name that is being extended */ 044 private String extendedClassNameFormat; 045 /** Stack of checking information for classes. */ 046 private final FastStack<Boolean> checkingStack = FastStack.newInstance(); 047 /** Should we check current class or not. */ 048 private boolean checking; 049 050 /** Creates new instance of the check. */ 051 public MutableExceptionCheck() 052 { 053 super(DEFAULT_FORMAT); 054 setExtendedClassNameFormat(DEFAULT_FORMAT); 055 } 056 057 /** 058 * Sets the format of extended class name to the specified regular expression. 059 * @param extendedClassNameFormat a <code>String</code> value 060 */ 061 public void setExtendedClassNameFormat(String extendedClassNameFormat) 062 { 063 this.extendedClassNameFormat = extendedClassNameFormat; 064 } 065 066 @Override 067 public int[] getDefaultTokens() 068 { 069 return new int[] {TokenTypes.CLASS_DEF, TokenTypes.VARIABLE_DEF}; 070 } 071 072 @Override 073 public int[] getRequiredTokens() 074 { 075 return getDefaultTokens(); 076 } 077 078 @Override 079 public void visitToken(DetailAST ast) 080 { 081 switch (ast.getType()) { 082 case TokenTypes.CLASS_DEF: 083 visitClassDef(ast); 084 break; 085 case TokenTypes.VARIABLE_DEF: 086 visitVariableDef(ast); 087 break; 088 default: 089 throw new IllegalStateException(ast.toString()); 090 } 091 } 092 093 @Override 094 public void leaveToken(DetailAST ast) 095 { 096 switch (ast.getType()) { 097 case TokenTypes.CLASS_DEF: 098 leaveClassDef(); 099 break; 100 default: 101 // Do nothing 102 } 103 } 104 105 /** 106 * Called when we start processing class definition. 107 * @param ast class definition node 108 */ 109 private void visitClassDef(DetailAST ast) 110 { 111 checkingStack.push(checking ? Boolean.TRUE : Boolean.FALSE); 112 checking = isNamedAsException(ast) && isExtendedClassNamedAsException(ast); 113 } 114 115 /** Called when we leave class definition. */ 116 private void leaveClassDef() 117 { 118 checking = checkingStack.pop(); 119 } 120 121 /** 122 * Checks variable definition. 123 * @param ast variable def node for check 124 */ 125 private void visitVariableDef(DetailAST ast) 126 { 127 if (checking && (ast.getParent().getType() == TokenTypes.OBJBLOCK)) { 128 final DetailAST modifiersAST = 129 ast.findFirstToken(TokenTypes.MODIFIERS); 130 131 if (modifiersAST.findFirstToken(TokenTypes.FINAL) == null) { 132 log(ast.getLineNo(), ast.getColumnNo(), "mutable.exception", 133 ast.findFirstToken(TokenTypes.IDENT).getText()); 134 } 135 } 136 } 137 138 /** 139 * @param ast class definition node 140 * @return true if a class name conforms to specified format 141 */ 142 private boolean isNamedAsException(DetailAST ast) 143 { 144 final String className = ast.findFirstToken(TokenTypes.IDENT).getText(); 145 return getRegexp().matcher(className).find(); 146 } 147 148 /** 149 * @param ast class definition node 150 * @return true if extended class name conforms to specified format 151 */ 152 private boolean isExtendedClassNamedAsException(DetailAST ast) 153 { 154 final DetailAST extendsClause = ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE); 155 if (extendsClause != null) { 156 final DetailAST extendedClass = extendsClause.findFirstToken(TokenTypes.IDENT); 157 if (extendedClass != null) { 158 final String extendedClassName = extendedClass.getText(); 159 return extendedClassName.matches(extendedClassNameFormat); 160 } 161 } 162 return false; 163 } 164}