001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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 * @since 3.2 071 */ 072@StatelessCheck 073public final class AbstractClassNameCheck extends AbstractCheck { 074 075 /** 076 * A key is pointing to the warning message text in "messages.properties" 077 * file. 078 */ 079 public static final String MSG_ILLEGAL_ABSTRACT_CLASS_NAME = "illegal.abstract.class.name"; 080 081 /** 082 * A key is pointing to the warning message text in "messages.properties" 083 * file. 084 */ 085 public static final String MSG_NO_ABSTRACT_CLASS_MODIFIER = "no.abstract.class.modifier"; 086 087 /** 088 * Control whether to ignore checking for the {@code abstract} modifier on 089 * classes that match the name. 090 */ 091 private boolean ignoreModifier; 092 093 /** 094 * Control whether to ignore checking the name. Realistically only useful 095 * if using the check to identify that match name and do not have the 096 * {@code abstract} modifier. 097 */ 098 private boolean ignoreName; 099 100 /** Specify valid identifiers. */ 101 private Pattern format = Pattern.compile("^Abstract.+$"); 102 103 /** 104 * Setter to control whether to ignore checking for the {@code abstract} modifier on 105 * classes that match the name. 106 * @param value new value 107 */ 108 public void setIgnoreModifier(boolean value) { 109 ignoreModifier = value; 110 } 111 112 /** 113 * Setter to control whether to ignore checking the name. Realistically only useful if 114 * using the check to identify that match name and do not have the {@code abstract} modifier. 115 * @param value new value. 116 */ 117 public void setIgnoreName(boolean value) { 118 ignoreName = value; 119 } 120 121 /** 122 * Setter to specify valid identifiers. 123 * @param pattern the new pattern 124 */ 125 public void setFormat(Pattern pattern) { 126 format = pattern; 127 } 128 129 @Override 130 public int[] getDefaultTokens() { 131 return getRequiredTokens(); 132 } 133 134 @Override 135 public int[] getRequiredTokens() { 136 return new int[] {TokenTypes.CLASS_DEF}; 137 } 138 139 @Override 140 public int[] getAcceptableTokens() { 141 return getRequiredTokens(); 142 } 143 144 @Override 145 public void visitToken(DetailAST ast) { 146 visitClassDef(ast); 147 } 148 149 /** 150 * Checks class definition. 151 * @param ast class definition for check. 152 */ 153 private void visitClassDef(DetailAST ast) { 154 final String className = 155 ast.findFirstToken(TokenTypes.IDENT).getText(); 156 if (isAbstract(ast)) { 157 // if class has abstract modifier 158 if (!ignoreName && !isMatchingClassName(className)) { 159 log(ast, MSG_ILLEGAL_ABSTRACT_CLASS_NAME, className, format.pattern()); 160 } 161 } 162 else if (!ignoreModifier && isMatchingClassName(className)) { 163 log(ast, MSG_NO_ABSTRACT_CLASS_MODIFIER, className); 164 } 165 } 166 167 /** 168 * Checks if declared class is abstract or not. 169 * @param ast class definition for check. 170 * @return true if a given class declared as abstract. 171 */ 172 private static boolean isAbstract(DetailAST ast) { 173 final DetailAST abstractAST = ast.findFirstToken(TokenTypes.MODIFIERS) 174 .findFirstToken(TokenTypes.ABSTRACT); 175 176 return abstractAST != null; 177 } 178 179 /** 180 * Returns true if class name matches format of abstract class names. 181 * @param className class name for check. 182 * @return true if class name matches format of abstract class names. 183 */ 184 private boolean isMatchingClassName(String className) { 185 return format.matcher(className).find(); 186 } 187 188}