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.Sets; 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.AbstractFormatCheck; 026import com.puppycrawl.tools.checkstyle.checks.CheckUtils; 027import java.util.Set; 028 029/** 030 * <p> 031 * Checks that particular class are never used as types in variable 032 * declarations, return values or parameters. Includes 033 * a pattern check that by default disallows abstract classes. 034 * </p> 035 * <p> 036 * Rationale: 037 * Helps reduce coupling on concrete classes. In addition abstract 038 * classes should be thought of a convenience base class 039 * implementations of interfaces and as such are not types themsleves. 040 * </p> 041 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> 042 */ 043public final class IllegalTypeCheck extends AbstractFormatCheck 044{ 045 /** Default value of pattern for illegal class name. */ 046 private static final String DEFAULT_FORMAT = "^(.*[\\.])?Abstract.*$"; 047 /** Abstract classes legal by default. */ 048 private static final String[] DEFAULT_LEGAL_ABSTRACT_NAMES = {}; 049 /** Types illegal by default. */ 050 private static final String[] DEFAULT_ILLEGAL_TYPES = { 051 "GregorianCalendar", 052 "Hashtable", 053 "HashSet", 054 "HashMap", 055 "ArrayList", 056 "LinkedList", 057 "LinkedHashMap", 058 "LinkedHashSet", 059 "TreeSet", 060 "TreeMap", 061 "Vector", 062 "java.util.GregorianCalendar", 063 "java.util.Hashtable", 064 "java.util.HashSet", 065 "java.util.HashMap", 066 "java.util.ArrayList", 067 "java.util.LinkedList", 068 "java.util.LinkedHashMap", 069 "java.util.LinkedHashSet", 070 "java.util.TreeSet", 071 "java.util.TreeMap", 072 "java.util.Vector", 073 }; 074 075 /** Default ignored method names. */ 076 private static final String[] DEFAULT_IGNORED_METHOD_NAMES = { 077 "getInitialContext", 078 "getEnvironment", 079 }; 080 081 /** illegal classes. */ 082 private final Set<String> mIllegalClassNames = Sets.newHashSet(); 083 /** legal abstract classes. */ 084 private final Set<String> mLegalAbstractClassNames = Sets.newHashSet(); 085 /** methods which should be ignored. */ 086 private final Set<String> mIgnoredMethodNames = Sets.newHashSet(); 087 088 /** Creates new instance of the check. */ 089 public IllegalTypeCheck() 090 { 091 super(DEFAULT_FORMAT); 092 setIllegalClassNames(DEFAULT_ILLEGAL_TYPES); 093 setLegalAbstractClassNames(DEFAULT_LEGAL_ABSTRACT_NAMES); 094 setIgnoredMethodNames(DEFAULT_IGNORED_METHOD_NAMES); 095 } 096 097 @Override 098 public int[] getDefaultTokens() 099 { 100 return new int[] { 101 TokenTypes.VARIABLE_DEF, 102 TokenTypes.PARAMETER_DEF, 103 TokenTypes.METHOD_DEF, 104 }; 105 } 106 107 @Override 108 public void visitToken(DetailAST aAST) 109 { 110 switch (aAST.getType()) { 111 case TokenTypes.METHOD_DEF: 112 visitMethodDef(aAST); 113 break; 114 case TokenTypes.VARIABLE_DEF: 115 visitVariableDef(aAST); 116 break; 117 case TokenTypes.PARAMETER_DEF: 118 visitParameterDef(aAST); 119 break; 120 default: 121 throw new IllegalStateException(aAST.toString()); 122 } 123 } 124 125 /** 126 * Checks return type of a given method. 127 * @param aAST method for check. 128 */ 129 private void visitMethodDef(DetailAST aAST) 130 { 131 if (isCheckedMethod(aAST)) { 132 checkClassName(aAST); 133 } 134 } 135 136 /** 137 * Checks type of parameters. 138 * @param aAST parameter list for check. 139 */ 140 private void visitParameterDef(DetailAST aAST) 141 { 142 final DetailAST grandParentAST = aAST.getParent().getParent(); 143 144 if ((grandParentAST.getType() == TokenTypes.METHOD_DEF) 145 && isCheckedMethod(grandParentAST)) 146 { 147 checkClassName(aAST); 148 } 149 } 150 151 /** 152 * Checks type of given variable. 153 * @param aAST variable to check. 154 */ 155 private void visitVariableDef(DetailAST aAST) 156 { 157 checkClassName(aAST); 158 } 159 160 /** 161 * Checks type of given method, parameter or variable. 162 * @param aAST node to check. 163 */ 164 private void checkClassName(DetailAST aAST) 165 { 166 final DetailAST type = aAST.findFirstToken(TokenTypes.TYPE); 167 final FullIdent ident = CheckUtils.createFullType(type); 168 169 if (isMatchingClassName(ident.getText())) { 170 log(ident.getLineNo(), ident.getColumnNo(), 171 "illegal.type", ident.getText()); 172 } 173 } 174 175 /** 176 * @param aClassName class name to check. 177 * @return true if given class name is one of illegal classes 178 * or if it matches to abstract class names pattern. 179 */ 180 private boolean isMatchingClassName(String aClassName) 181 { 182 return mIllegalClassNames.contains(aClassName) 183 || (!mLegalAbstractClassNames.contains(aClassName) 184 && getRegexp().matcher(aClassName).find()); 185 } 186 187 /** 188 * @param aAST method def to check. 189 * @return true if we should check this method. 190 */ 191 private boolean isCheckedMethod(DetailAST aAST) 192 { 193 final String methodName = 194 aAST.findFirstToken(TokenTypes.IDENT).getText(); 195 return !mIgnoredMethodNames.contains(methodName); 196 } 197 198 /** 199 * Set the list of illegal variable types. 200 * @param aClassNames array of illegal variable types 201 */ 202 public void setIllegalClassNames(String[] aClassNames) 203 { 204 mIllegalClassNames.clear(); 205 for (String name : aClassNames) { 206 mIllegalClassNames.add(name); 207 final int lastDot = name.lastIndexOf("."); 208 if ((lastDot > 0) && (lastDot < (name.length() - 1))) { 209 final String shortName = 210 name.substring(name.lastIndexOf(".") + 1); 211 mIllegalClassNames.add(shortName); 212 } 213 } 214 } 215 216 /** 217 * Get the list of illegal variable types. 218 * @return array of illegal variable types 219 */ 220 public String[] getIllegalClassNames() 221 { 222 return mIllegalClassNames.toArray( 223 new String[mIllegalClassNames.size()]); 224 } 225 226 /** 227 * Set the list of ignore method names. 228 * @param aMethodNames array of ignored method names 229 */ 230 public void setIgnoredMethodNames(String[] aMethodNames) 231 { 232 mIgnoredMethodNames.clear(); 233 for (String element : aMethodNames) { 234 mIgnoredMethodNames.add(element); 235 } 236 } 237 238 /** 239 * Get the list of ignored method names. 240 * @return array of ignored method names 241 */ 242 public String[] getIgnoredMethodNames() 243 { 244 return mIgnoredMethodNames.toArray( 245 new String[mIgnoredMethodNames.size()]); 246 } 247 248 /** 249 * Set the list of legal abstract class names. 250 * @param aClassNames array of legal abstract class names 251 */ 252 public void setLegalAbstractClassNames(String[] aClassNames) 253 { 254 mLegalAbstractClassNames.clear(); 255 for (String element : aClassNames) { 256 mLegalAbstractClassNames.add(element); 257 } 258 } 259 260 /** 261 * Get the list of legal abstract class names. 262 * @return array of legal abstract class names 263 */ 264 public String[] getLegalAbstractClassNames() 265 { 266 return mLegalAbstractClassNames.toArray( 267 new String[mLegalAbstractClassNames.size()]); 268 } 269}