1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 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 regular 32 * expression and check that {@code abstract} modifier exists. 33 * </p> 34 * <p> 35 * Rationale: Abstract classes are convenience base class implementations of 36 * interfaces, not types as such. As such they should be named to indicate this. 37 * Also if names of classes starts with 'Abstract' it's very convenient that 38 * they will have abstract modifier. 39 * </p> 40 * <ul> 41 * <li> 42 * Property {@code format} - Specify valid identifiers. Default value is 43 * {@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. Default value is 47 * {@code false}.</li> 48 * <li> 49 * Property {@code ignoreName} - Control whether to ignore checking the name. 50 * Realistically only useful if using the check to identify that match name and 51 * do not have the {@code abstract} modifier. Default value is 52 * {@code false}.</li> 53 * </ul> 54 * <p> 55 * The following example shows how to configure the {@code AbstractClassName} to 56 * checks names, but ignore missing {@code abstract} modifiers: 57 * </p> 58 * <p>Configuration:</p> 59 * <pre> 60 * <module name="AbstractClassName"> 61 * <property name="ignoreModifier" value="true"/> 62 * </module> 63 * </pre> 64 * <p>Example:</p> 65 * <pre> 66 * abstract class AbstractFirstClass {} // OK 67 * abstract class SecondClass {} // violation, it should match the pattern "^Abstract.+$" 68 * class AbstractThirdClass {} // OK, no "abstract" modifier 69 * </pre> 70 * @since 3.2 71 */ 72 @StatelessCheck 73 public final class AbstractClassNameCheck extends AbstractCheck { 74 75 /** 76 * A key is pointing to the warning message text in "messages.properties" 77 * file. 78 */ 79 public static final String MSG_ILLEGAL_ABSTRACT_CLASS_NAME = "illegal.abstract.class.name"; 80 81 /** 82 * A key is pointing to the warning message text in "messages.properties" 83 * file. 84 */ 85 public static final String MSG_NO_ABSTRACT_CLASS_MODIFIER = "no.abstract.class.modifier"; 86 87 /** 88 * Control whether to ignore checking for the {@code abstract} modifier on 89 * classes that match the name. 90 */ 91 private boolean ignoreModifier; 92 93 /** 94 * Control whether to ignore checking the name. Realistically only useful 95 * if using the check to identify that match name and do not have the 96 * {@code abstract} modifier. 97 */ 98 private boolean ignoreName; 99 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 }