1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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 * Creates a new {@code AbstractClassNameCheck} instance.
76 */
77 public AbstractClassNameCheck() {
78 // no code by default
79 }
80
81 /**
82 * Setter to control whether to ignore checking for the {@code abstract} modifier on
83 * classes that match the name.
84 *
85 * @param value new value
86 * @since 5.3
87 */
88 public void setIgnoreModifier(boolean value) {
89 ignoreModifier = value;
90 }
91
92 /**
93 * Setter to control whether to ignore checking the name. Realistically only useful if
94 * using the check to identify that match name and do not have the {@code abstract} modifier.
95 *
96 * @param value new value.
97 * @since 5.3
98 */
99 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 }