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. Default value is 043 * {@code "^Abstract.+$"}.</li> 044 * <li> 045 * Property {@code ignoreModifier} - Control whether to ignore checking for the 046 * {@code abstract} modifier on classes that match the name. Default value is 047 * {@code false}.</li> 048 * <li> 049 * Property {@code ignoreName} - Control whether to ignore checking the name. 050 * Realistically only useful if using the check to identify that match name and 051 * do not have the {@code abstract} modifier. Default value is 052 * {@code false}.</li> 053 * </ul> 054 * <p> 055 * The following example shows how to configure the {@code AbstractClassName} to 056 * checks names, but ignore missing {@code abstract} modifiers: 057 * </p> 058 * <p>Configuration:</p> 059 * <pre> 060 * <module name="AbstractClassName"> 061 * <property name="ignoreModifier" value="true"/> 062 * </module> 063 * </pre> 064 * <p>Example:</p> 065 * <pre> 066 * abstract class AbstractFirstClass {} // OK 067 * abstract class SecondClass {} // violation, it should match the pattern "^Abstract.+$" 068 * class AbstractThirdClass {} // OK, no "abstract" modifier 069 * </pre> 070 * 071 * @since 3.2 072 */ 073@StatelessCheck 074public final class AbstractClassNameCheck extends AbstractCheck { 075 076 /** 077 * A key is pointing to the warning message text in "messages.properties" 078 * file. 079 */ 080 public static final String MSG_ILLEGAL_ABSTRACT_CLASS_NAME = "illegal.abstract.class.name"; 081 082 /** 083 * A key is pointing to the warning message text in "messages.properties" 084 * file. 085 */ 086 public static final String MSG_NO_ABSTRACT_CLASS_MODIFIER = "no.abstract.class.modifier"; 087 088 /** 089 * Control whether to ignore checking for the {@code abstract} modifier on 090 * classes that match the name. 091 */ 092 private boolean ignoreModifier; 093 094 /** 095 * Control whether to ignore checking the name. Realistically only useful 096 * if using the check to identify that match name and do not have the 097 * {@code abstract} modifier. 098 */ 099 private boolean ignoreName; 100 101 /** Specify valid identifiers. */ 102 private Pattern format = Pattern.compile("^Abstract.+$"); 103 104 /** 105 * Setter to control whether to ignore checking for the {@code abstract} modifier on 106 * classes that match the name. 107 * 108 * @param value new value 109 */ 110 public void setIgnoreModifier(boolean value) { 111 ignoreModifier = value; 112 } 113 114 /** 115 * Setter to control whether to ignore checking the name. Realistically only useful if 116 * using the check to identify that match name and do not have the {@code abstract} modifier. 117 * 118 * @param value new value. 119 */ 120 public void setIgnoreName(boolean value) { 121 ignoreName = value; 122 } 123 124 /** 125 * Setter to specify valid identifiers. 126 * 127 * @param pattern the new pattern 128 */ 129 public void setFormat(Pattern pattern) { 130 format = pattern; 131 } 132 133 @Override 134 public int[] getDefaultTokens() { 135 return getRequiredTokens(); 136 } 137 138 @Override 139 public int[] getRequiredTokens() { 140 return new int[] {TokenTypes.CLASS_DEF}; 141 } 142 143 @Override 144 public int[] getAcceptableTokens() { 145 return getRequiredTokens(); 146 } 147 148 @Override 149 public void visitToken(DetailAST ast) { 150 visitClassDef(ast); 151 } 152 153 /** 154 * Checks class definition. 155 * 156 * @param ast class definition for check. 157 */ 158 private void visitClassDef(DetailAST ast) { 159 final String className = 160 ast.findFirstToken(TokenTypes.IDENT).getText(); 161 if (isAbstract(ast)) { 162 // if class has abstract modifier 163 if (!ignoreName && !isMatchingClassName(className)) { 164 log(ast, MSG_ILLEGAL_ABSTRACT_CLASS_NAME, className, format.pattern()); 165 } 166 } 167 else if (!ignoreModifier && isMatchingClassName(className)) { 168 log(ast, MSG_NO_ABSTRACT_CLASS_MODIFIER, className); 169 } 170 } 171 172 /** 173 * Checks if declared class is abstract or not. 174 * 175 * @param ast class definition for check. 176 * @return true if a given class declared as abstract. 177 */ 178 private static boolean isAbstract(DetailAST ast) { 179 final DetailAST abstractAST = ast.findFirstToken(TokenTypes.MODIFIERS) 180 .findFirstToken(TokenTypes.ABSTRACT); 181 182 return abstractAST != null; 183 } 184 185 /** 186 * Returns true if class name matches format of abstract class names. 187 * 188 * @param className class name for check. 189 * @return true if class name matches format of abstract class names. 190 */ 191 private boolean isMatchingClassName(String className) { 192 return format.matcher(className).find(); 193 } 194 195}