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