001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 * <div> 031 * Ensures that the names of abstract classes conforming to some pattern 032 * and check that {@code abstract} modifier exists. 033 * </div> 034 * 035 * <p> 036 * Rationale: Abstract classes are convenience base class implementations of 037 * interfaces. For this reason, it should be made obvious that a given class 038 * is abstract by prefacing the class name with 'Abstract'. 039 * </p> 040 * 041 * @since 3.2 042 */ 043@StatelessCheck 044public final class AbstractClassNameCheck extends AbstractCheck { 045 046 /** 047 * A key is pointing to the warning message text in "messages.properties" 048 * file. 049 */ 050 public static final String MSG_ILLEGAL_ABSTRACT_CLASS_NAME = "illegal.abstract.class.name"; 051 052 /** 053 * A key is pointing to the warning message text in "messages.properties" 054 * file. 055 */ 056 public static final String MSG_NO_ABSTRACT_CLASS_MODIFIER = "no.abstract.class.modifier"; 057 058 /** 059 * Control whether to ignore checking for the {@code abstract} modifier on 060 * classes that match the name. 061 */ 062 private boolean ignoreModifier; 063 064 /** 065 * Control whether to ignore checking the name. Realistically only useful 066 * if using the check to identify that match name and do not have the 067 * {@code abstract} modifier. 068 */ 069 private boolean ignoreName; 070 071 /** Specify valid identifiers. */ 072 private Pattern format = Pattern.compile("^Abstract.+$"); 073 074 /** 075 * Creates a new {@code AbstractClassNameCheck} instance. 076 */ 077 public AbstractClassNameCheck() { 078 // no code by default 079 } 080 081 /** 082 * Setter to control whether to ignore checking for the {@code abstract} modifier on 083 * classes that match the name. 084 * 085 * @param value new value 086 * @since 5.3 087 */ 088 public void setIgnoreModifier(boolean value) { 089 ignoreModifier = value; 090 } 091 092 /** 093 * Setter to control whether to ignore checking the name. Realistically only useful if 094 * using the check to identify that match name and do not have the {@code abstract} modifier. 095 * 096 * @param value new value. 097 * @since 5.3 098 */ 099 public void setIgnoreName(boolean value) { 100 ignoreName = value; 101 } 102 103 /** 104 * Setter to specify valid identifiers. 105 * 106 * @param pattern the new pattern 107 * @since 3.2 108 */ 109 public void setFormat(Pattern pattern) { 110 format = pattern; 111 } 112 113 @Override 114 public int[] getDefaultTokens() { 115 return getRequiredTokens(); 116 } 117 118 @Override 119 public int[] getRequiredTokens() { 120 return new int[] {TokenTypes.CLASS_DEF}; 121 } 122 123 @Override 124 public int[] getAcceptableTokens() { 125 return getRequiredTokens(); 126 } 127 128 @Override 129 public void visitToken(DetailAST ast) { 130 visitClassDef(ast); 131 } 132 133 /** 134 * Checks class definition. 135 * 136 * @param ast class definition for check. 137 */ 138 private void visitClassDef(DetailAST ast) { 139 final String className = 140 ast.findFirstToken(TokenTypes.IDENT).getText(); 141 if (isAbstract(ast)) { 142 // if class has abstract modifier 143 if (!ignoreName && !isMatchingClassName(className)) { 144 log(ast, MSG_ILLEGAL_ABSTRACT_CLASS_NAME, className, format.pattern()); 145 } 146 } 147 else if (!ignoreModifier && isMatchingClassName(className)) { 148 log(ast, MSG_NO_ABSTRACT_CLASS_MODIFIER, className); 149 } 150 } 151 152 /** 153 * Checks if declared class is abstract or not. 154 * 155 * @param ast class definition for check. 156 * @return true if a given class declared as abstract. 157 */ 158 private static boolean isAbstract(DetailAST ast) { 159 final DetailAST abstractAST = ast.findFirstToken(TokenTypes.MODIFIERS) 160 .findFirstToken(TokenTypes.ABSTRACT); 161 162 return abstractAST != null; 163 } 164 165 /** 166 * Returns true if class name matches format of abstract class names. 167 * 168 * @param className class name for check. 169 * @return true if class name matches format of abstract class names. 170 */ 171 private boolean isMatchingClassName(String className) { 172 return format.matcher(className).find(); 173 } 174 175}