1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 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 * <p> 31 * Ensures that the names of abstract classes conforming to some pattern 32 * and check that {@code abstract} modifier exists. 33 * </p> 34 * <p> 35 * Rationale: Abstract classes are convenience base class implementations of 36 * interfaces. For this reason, it should be made obvious that a given class 37 * is abstract by prefacing the class name with 'Abstract'. 38 * </p> 39 * <ul> 40 * <li> 41 * Property {@code format} - Specify valid identifiers. 42 * Type is {@code java.util.regex.Pattern}. 43 * Default value is {@code "^Abstract.+$"}.</li> 44 * <li> 45 * Property {@code ignoreModifier} - Control whether to ignore checking for the 46 * {@code abstract} modifier on classes that match the name. 47 * Type is {@code boolean}. 48 * Default value is {@code false}.</li> 49 * <li> 50 * Property {@code ignoreName} - Control whether to ignore checking the name. 51 * Realistically only useful if using the check to identify that match name and 52 * do not have the {@code abstract} modifier. 53 * Type is {@code boolean}. 54 * Default value is {@code false}. 55 * </li> 56 * </ul> 57 * <p> 58 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 59 * </p> 60 * <p> 61 * Violation Message Keys: 62 * </p> 63 * <ul> 64 * <li> 65 * {@code illegal.abstract.class.name} 66 * </li> 67 * <li> 68 * {@code no.abstract.class.modifier} 69 * </li> 70 * </ul> 71 * 72 * @since 3.2 73 */ 74 @StatelessCheck 75 public final class AbstractClassNameCheck extends AbstractCheck { 76 77 /** 78 * A key is pointing to the warning message text in "messages.properties" 79 * file. 80 */ 81 public static final String MSG_ILLEGAL_ABSTRACT_CLASS_NAME = "illegal.abstract.class.name"; 82 83 /** 84 * A key is pointing to the warning message text in "messages.properties" 85 * file. 86 */ 87 public static final String MSG_NO_ABSTRACT_CLASS_MODIFIER = "no.abstract.class.modifier"; 88 89 /** 90 * Control whether to ignore checking for the {@code abstract} modifier on 91 * classes that match the name. 92 */ 93 private boolean ignoreModifier; 94 95 /** 96 * Control whether to ignore checking the name. Realistically only useful 97 * if using the check to identify that match name and do not have the 98 * {@code abstract} modifier. 99 */ 100 private boolean ignoreName; 101 102 /** Specify valid identifiers. */ 103 private Pattern format = Pattern.compile("^Abstract.+$"); 104 105 /** 106 * Setter to control whether to ignore checking for the {@code abstract} modifier on 107 * classes that match the name. 108 * 109 * @param value new value 110 * @since 5.3 111 */ 112 public void setIgnoreModifier(boolean value) { 113 ignoreModifier = value; 114 } 115 116 /** 117 * Setter to control whether to ignore checking the name. Realistically only useful if 118 * using the check to identify that match name and do not have the {@code abstract} modifier. 119 * 120 * @param value new value. 121 * @since 5.3 122 */ 123 public void setIgnoreName(boolean value) { 124 ignoreName = value; 125 } 126 127 /** 128 * Setter to specify valid identifiers. 129 * 130 * @param pattern the new pattern 131 * @since 3.2 132 */ 133 public void setFormat(Pattern pattern) { 134 format = pattern; 135 } 136 137 @Override 138 public int[] getDefaultTokens() { 139 return getRequiredTokens(); 140 } 141 142 @Override 143 public int[] getRequiredTokens() { 144 return new int[] {TokenTypes.CLASS_DEF}; 145 } 146 147 @Override 148 public int[] getAcceptableTokens() { 149 return getRequiredTokens(); 150 } 151 152 @Override 153 public void visitToken(DetailAST ast) { 154 visitClassDef(ast); 155 } 156 157 /** 158 * Checks class definition. 159 * 160 * @param ast class definition for check. 161 */ 162 private void visitClassDef(DetailAST ast) { 163 final String className = 164 ast.findFirstToken(TokenTypes.IDENT).getText(); 165 if (isAbstract(ast)) { 166 // if class has abstract modifier 167 if (!ignoreName && !isMatchingClassName(className)) { 168 log(ast, MSG_ILLEGAL_ABSTRACT_CLASS_NAME, className, format.pattern()); 169 } 170 } 171 else if (!ignoreModifier && isMatchingClassName(className)) { 172 log(ast, MSG_NO_ABSTRACT_CLASS_MODIFIER, className); 173 } 174 } 175 176 /** 177 * Checks if declared class is abstract or not. 178 * 179 * @param ast class definition for check. 180 * @return true if a given class declared as abstract. 181 */ 182 private static boolean isAbstract(DetailAST ast) { 183 final DetailAST abstractAST = ast.findFirstToken(TokenTypes.MODIFIERS) 184 .findFirstToken(TokenTypes.ABSTRACT); 185 186 return abstractAST != null; 187 } 188 189 /** 190 * Returns true if class name matches format of abstract class names. 191 * 192 * @param className class name for check. 193 * @return true if class name matches format of abstract class names. 194 */ 195 private boolean isMatchingClassName(String className) { 196 return format.matcher(className).find(); 197 } 198 199 }