1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2025 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.naming; 21 22 import java.util.regex.Pattern; 23 24 import com.puppycrawl.tools.checkstyle.StatelessCheck; 25 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 26 import com.puppycrawl.tools.checkstyle.api.DetailAST; 27 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 28 29 /** 30 * <div> 31 * Ensures that the names of abstract classes conforming to some pattern 32 * and check that {@code abstract} modifier exists. 33 * </div> 34 * 35 * <p> 36 * Rationale: Abstract classes are convenience base class implementations of 37 * interfaces. For this reason, it should be made obvious that a given class 38 * is abstract by prefacing the class name with 'Abstract'. 39 * </p> 40 * <ul> 41 * <li> 42 * Property {@code format} - Specify valid identifiers. 43 * Type is {@code java.util.regex.Pattern}. 44 * Default value is {@code "^Abstract.+$"}.</li> 45 * <li> 46 * Property {@code ignoreModifier} - Control whether to ignore checking for the 47 * {@code abstract} modifier on classes that match the name. 48 * Type is {@code boolean}. 49 * Default value is {@code false}.</li> 50 * <li> 51 * Property {@code ignoreName} - Control whether to ignore checking the name. 52 * Realistically only useful if using the check to identify that match name and 53 * do not have the {@code abstract} modifier. 54 * Type is {@code boolean}. 55 * Default value is {@code false}. 56 * </li> 57 * </ul> 58 * 59 * <p> 60 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 61 * </p> 62 * 63 * <p> 64 * Violation Message Keys: 65 * </p> 66 * <ul> 67 * <li> 68 * {@code illegal.abstract.class.name} 69 * </li> 70 * <li> 71 * {@code no.abstract.class.modifier} 72 * </li> 73 * </ul> 74 * 75 * @since 3.2 76 */ 77 @StatelessCheck 78 public final class AbstractClassNameCheck extends AbstractCheck { 79 80 /** 81 * A key is pointing to the warning message text in "messages.properties" 82 * file. 83 */ 84 public static final String MSG_ILLEGAL_ABSTRACT_CLASS_NAME = "illegal.abstract.class.name"; 85 86 /** 87 * A key is pointing to the warning message text in "messages.properties" 88 * file. 89 */ 90 public static final String MSG_NO_ABSTRACT_CLASS_MODIFIER = "no.abstract.class.modifier"; 91 92 /** 93 * Control whether to ignore checking for the {@code abstract} modifier on 94 * classes that match the name. 95 */ 96 private boolean ignoreModifier; 97 98 /** 99 * Control whether to ignore checking the name. Realistically only useful 100 * if using the check to identify that match name and do not have the 101 * {@code abstract} modifier. 102 */ 103 private boolean ignoreName; 104 105 /** Specify valid identifiers. */ 106 private Pattern format = Pattern.compile("^Abstract.+$"); 107 108 /** 109 * Setter to control whether to ignore checking for the {@code abstract} modifier on 110 * classes that match the name. 111 * 112 * @param value new value 113 * @since 5.3 114 */ 115 public void setIgnoreModifier(boolean value) { 116 ignoreModifier = value; 117 } 118 119 /** 120 * Setter to control whether to ignore checking the name. Realistically only useful if 121 * using the check to identify that match name and do not have the {@code abstract} modifier. 122 * 123 * @param value new value. 124 * @since 5.3 125 */ 126 public void setIgnoreName(boolean value) { 127 ignoreName = value; 128 } 129 130 /** 131 * Setter to specify valid identifiers. 132 * 133 * @param pattern the new pattern 134 * @since 3.2 135 */ 136 public void setFormat(Pattern pattern) { 137 format = pattern; 138 } 139 140 @Override 141 public int[] getDefaultTokens() { 142 return getRequiredTokens(); 143 } 144 145 @Override 146 public int[] getRequiredTokens() { 147 return new int[] {TokenTypes.CLASS_DEF}; 148 } 149 150 @Override 151 public int[] getAcceptableTokens() { 152 return getRequiredTokens(); 153 } 154 155 @Override 156 public void visitToken(DetailAST ast) { 157 visitClassDef(ast); 158 } 159 160 /** 161 * Checks class definition. 162 * 163 * @param ast class definition for check. 164 */ 165 private void visitClassDef(DetailAST ast) { 166 final String className = 167 ast.findFirstToken(TokenTypes.IDENT).getText(); 168 if (isAbstract(ast)) { 169 // if class has abstract modifier 170 if (!ignoreName && !isMatchingClassName(className)) { 171 log(ast, MSG_ILLEGAL_ABSTRACT_CLASS_NAME, className, format.pattern()); 172 } 173 } 174 else if (!ignoreModifier && isMatchingClassName(className)) { 175 log(ast, MSG_NO_ABSTRACT_CLASS_MODIFIER, className); 176 } 177 } 178 179 /** 180 * Checks if declared class is abstract or not. 181 * 182 * @param ast class definition for check. 183 * @return true if a given class declared as abstract. 184 */ 185 private static boolean isAbstract(DetailAST ast) { 186 final DetailAST abstractAST = ast.findFirstToken(TokenTypes.MODIFIERS) 187 .findFirstToken(TokenTypes.ABSTRACT); 188 189 return abstractAST != null; 190 } 191 192 /** 193 * Returns true if class name matches format of abstract class names. 194 * 195 * @param className class name for check. 196 * @return true if class name matches format of abstract class names. 197 */ 198 private boolean isMatchingClassName(String className) { 199 return format.matcher(className).find(); 200 } 201 202 }