001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 the original author or authors. 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 020package com.puppycrawl.tools.checkstyle.checks.naming; 021 022import java.util.regex.Pattern; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028 029/** 030 * <p> 031 * Ensures that the names of abstract classes conforming to some regular 032 * expression and check that {@code abstract} modifier exists. 033 * </p> 034 * <p> 035 * Rationale: Abstract classes are convenience base class implementations of 036 * interfaces, not types as such. As such they should be named to indicate this. 037 * Also if names of classes starts with 'Abstract' it's very convenient that 038 * they will have abstract modifier. 039 * </p> 040 * <ul> 041 * <li> 042 * Property {@code format} - Specify valid identifiers. 043 * Type is {@code java.util.regex.Pattern}. 044 * Default value is {@code "^Abstract.+$"}.</li> 045 * <li> 046 * Property {@code ignoreModifier} - Control whether to ignore checking for the 047 * {@code abstract} modifier on classes that match the name. 048 * Type is {@code boolean}. 049 * Default value is {@code false}.</li> 050 * <li> 051 * Property {@code ignoreName} - Control whether to ignore checking the name. 052 * Realistically only useful if using the check to identify that match name and 053 * do not have the {@code abstract} modifier. 054 * Type is {@code boolean}. 055 * Default value is {@code false}. 056 * </li> 057 * </ul> 058 * <p> 059 * To configure the check: 060 * </p> 061 * <pre> 062 * <module name="AbstractClassName"/> 063 * </pre> 064 * <p>Example:</p> 065 * <pre> 066 * abstract class AbstractFirstClass {} // OK 067 * abstract class SecondClass {} // violation, it should match pattern "^Abstract.+$" 068 * class AbstractThirdClass {} // violation, must be declared 'abstract' 069 * class FourthClass {} // OK 070 * </pre> 071 * <p> 072 * To configure the check so that it check name 073 * but ignore {@code abstract} modifier: 074 * </p> 075 * <pre> 076 * <module name="AbstractClassName"> 077 * <property name="ignoreModifier" value="true"/> 078 * </module> 079 * </pre> 080 * <p>Example:</p> 081 * <pre> 082 * abstract class AbstractFirstClass {} // OK 083 * abstract class SecondClass {} // violation, it should match pattern "^Abstract.+$" 084 * class AbstractThirdClass {} // OK, no "abstract" modifier 085 * class FourthClass {} // OK 086 * </pre> 087 * <p> 088 * To configure the check to ignore name 089 * validation when class declared as 'abstract' 090 * </p> 091 * <pre> 092 * <module name="AbstractClassName"> 093 * <property name="ignoreName" value="true"/> 094 * </module> 095 * </pre> 096 * <p>Example:</p> 097 * <pre> 098 * abstract class AbstractFirstClass {} // OK 099 * abstract class SecondClass {} // OK, name validation is ignored 100 * class AbstractThirdClass {} // violation, must be declared as 'abstract' 101 * class FourthClass {} // OK, no "abstract" modifier 102 * </pre> 103 * <p> 104 * To configure the check 105 * with {@code format}: 106 * </p> 107 * <pre> 108 * <module name="AbstractClassName"> 109 * <property name="format" value="^Generator.+$"/> 110 * </module> 111 * </pre> 112 * <p>Example:</p> 113 * <pre> 114 * abstract class GeneratorFirstClass {} // OK 115 * abstract class SecondClass {} // violation, must match pattern '^Generator.+$' 116 * class GeneratorThirdClass {} // violation, must be declared 'abstract' 117 * class FourthClass {} // OK, no "abstract" modifier 118 * </pre> 119 * <p> 120 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 121 * </p> 122 * <p> 123 * Violation Message Keys: 124 * </p> 125 * <ul> 126 * <li> 127 * {@code illegal.abstract.class.name} 128 * </li> 129 * <li> 130 * {@code no.abstract.class.modifier} 131 * </li> 132 * </ul> 133 * 134 * @since 3.2 135 */ 136@StatelessCheck 137public final class AbstractClassNameCheck extends AbstractCheck { 138 139 /** 140 * A key is pointing to the warning message text in "messages.properties" 141 * file. 142 */ 143 public static final String MSG_ILLEGAL_ABSTRACT_CLASS_NAME = "illegal.abstract.class.name"; 144 145 /** 146 * A key is pointing to the warning message text in "messages.properties" 147 * file. 148 */ 149 public static final String MSG_NO_ABSTRACT_CLASS_MODIFIER = "no.abstract.class.modifier"; 150 151 /** 152 * Control whether to ignore checking for the {@code abstract} modifier on 153 * classes that match the name. 154 */ 155 private boolean ignoreModifier; 156 157 /** 158 * Control whether to ignore checking the name. Realistically only useful 159 * if using the check to identify that match name and do not have the 160 * {@code abstract} modifier. 161 */ 162 private boolean ignoreName; 163 164 /** Specify valid identifiers. */ 165 private Pattern format = Pattern.compile("^Abstract.+$"); 166 167 /** 168 * Setter to control whether to ignore checking for the {@code abstract} modifier on 169 * classes that match the name. 170 * 171 * @param value new value 172 */ 173 public void setIgnoreModifier(boolean value) { 174 ignoreModifier = value; 175 } 176 177 /** 178 * Setter to control whether to ignore checking the name. Realistically only useful if 179 * using the check to identify that match name and do not have the {@code abstract} modifier. 180 * 181 * @param value new value. 182 */ 183 public void setIgnoreName(boolean value) { 184 ignoreName = value; 185 } 186 187 /** 188 * Setter to specify valid identifiers. 189 * 190 * @param pattern the new pattern 191 */ 192 public void setFormat(Pattern pattern) { 193 format = pattern; 194 } 195 196 @Override 197 public int[] getDefaultTokens() { 198 return getRequiredTokens(); 199 } 200 201 @Override 202 public int[] getRequiredTokens() { 203 return new int[] {TokenTypes.CLASS_DEF}; 204 } 205 206 @Override 207 public int[] getAcceptableTokens() { 208 return getRequiredTokens(); 209 } 210 211 @Override 212 public void visitToken(DetailAST ast) { 213 visitClassDef(ast); 214 } 215 216 /** 217 * Checks class definition. 218 * 219 * @param ast class definition for check. 220 */ 221 private void visitClassDef(DetailAST ast) { 222 final String className = 223 ast.findFirstToken(TokenTypes.IDENT).getText(); 224 if (isAbstract(ast)) { 225 // if class has abstract modifier 226 if (!ignoreName && !isMatchingClassName(className)) { 227 log(ast, MSG_ILLEGAL_ABSTRACT_CLASS_NAME, className, format.pattern()); 228 } 229 } 230 else if (!ignoreModifier && isMatchingClassName(className)) { 231 log(ast, MSG_NO_ABSTRACT_CLASS_MODIFIER, className); 232 } 233 } 234 235 /** 236 * Checks if declared class is abstract or not. 237 * 238 * @param ast class definition for check. 239 * @return true if a given class declared as abstract. 240 */ 241 private static boolean isAbstract(DetailAST ast) { 242 final DetailAST abstractAST = ast.findFirstToken(TokenTypes.MODIFIERS) 243 .findFirstToken(TokenTypes.ABSTRACT); 244 245 return abstractAST != null; 246 } 247 248 /** 249 * Returns true if class name matches format of abstract class names. 250 * 251 * @param className class name for check. 252 * @return true if class name matches format of abstract class names. 253 */ 254 private boolean isMatchingClassName(String className) { 255 return format.matcher(className).find(); 256 } 257 258}