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 * 41 * @since 3.2 42 */ 43 @StatelessCheck 44 public final class AbstractClassNameCheck extends AbstractCheck { 45 46 /** 47 * A key is pointing to the warning message text in "messages.properties" 48 * file. 49 */ 50 public static final String MSG_ILLEGAL_ABSTRACT_CLASS_NAME = "illegal.abstract.class.name"; 51 52 /** 53 * A key is pointing to the warning message text in "messages.properties" 54 * file. 55 */ 56 public static final String MSG_NO_ABSTRACT_CLASS_MODIFIER = "no.abstract.class.modifier"; 57 58 /** 59 * Control whether to ignore checking for the {@code abstract} modifier on 60 * classes that match the name. 61 */ 62 private boolean ignoreModifier; 63 64 /** 65 * Control whether to ignore checking the name. Realistically only useful 66 * if using the check to identify that match name and do not have the 67 * {@code abstract} modifier. 68 */ 69 private boolean ignoreName; 70 71 /** Specify valid identifiers. */ 72 private Pattern format = Pattern.compile("^Abstract.+$"); 73 74 /** 75 * Setter to control whether to ignore checking for the {@code abstract} modifier on 76 * classes that match the name. 77 * 78 * @param value new value 79 * @since 5.3 80 */ 81 public void setIgnoreModifier(boolean value) { 82 ignoreModifier = value; 83 } 84 85 /** 86 * Setter to control whether to ignore checking the name. Realistically only useful if 87 * using the check to identify that match name and do not have the {@code abstract} modifier. 88 * 89 * @param value new value. 90 * @since 5.3 91 */ 92 public void setIgnoreName(boolean value) { 93 ignoreName = value; 94 } 95 96 /** 97 * Setter to specify valid identifiers. 98 * 99 * @param pattern the new pattern 100 * @since 3.2 101 */ 102 public void setFormat(Pattern pattern) { 103 format = pattern; 104 } 105 106 @Override 107 public int[] getDefaultTokens() { 108 return getRequiredTokens(); 109 } 110 111 @Override 112 public int[] getRequiredTokens() { 113 return new int[] {TokenTypes.CLASS_DEF}; 114 } 115 116 @Override 117 public int[] getAcceptableTokens() { 118 return getRequiredTokens(); 119 } 120 121 @Override 122 public void visitToken(DetailAST ast) { 123 visitClassDef(ast); 124 } 125 126 /** 127 * Checks class definition. 128 * 129 * @param ast class definition for check. 130 */ 131 private void visitClassDef(DetailAST ast) { 132 final String className = 133 ast.findFirstToken(TokenTypes.IDENT).getText(); 134 if (isAbstract(ast)) { 135 // if class has abstract modifier 136 if (!ignoreName && !isMatchingClassName(className)) { 137 log(ast, MSG_ILLEGAL_ABSTRACT_CLASS_NAME, className, format.pattern()); 138 } 139 } 140 else if (!ignoreModifier && isMatchingClassName(className)) { 141 log(ast, MSG_NO_ABSTRACT_CLASS_MODIFIER, className); 142 } 143 } 144 145 /** 146 * Checks if declared class is abstract or not. 147 * 148 * @param ast class definition for check. 149 * @return true if a given class declared as abstract. 150 */ 151 private static boolean isAbstract(DetailAST ast) { 152 final DetailAST abstractAST = ast.findFirstToken(TokenTypes.MODIFIERS) 153 .findFirstToken(TokenTypes.ABSTRACT); 154 155 return abstractAST != null; 156 } 157 158 /** 159 * Returns true if class name matches format of abstract class names. 160 * 161 * @param className class name for check. 162 * @return true if class name matches format of abstract class names. 163 */ 164 private boolean isMatchingClassName(String className) { 165 return format.matcher(className).find(); 166 } 167 168 }