View Javadoc
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   * <ul>
41   * <li>
42   * Property {@code format} - Specify valid identifiers.
43   * Type is {@code java.util.regex.Pattern}.
44   * Default value is {@code "^Abstract.+$"}.</li>
45   * <li>
46   * Property {@code ignoreModifier} - Control whether to ignore checking for the
47   * {@code abstract} modifier on classes that match the name.
48   * Type is {@code boolean}.
49   * Default value is {@code false}.</li>
50   * <li>
51   * Property {@code ignoreName} - Control whether to ignore checking the name.
52   * Realistically only useful if using the check to identify that match name and
53   * do not have the {@code abstract} modifier.
54   * Type is {@code boolean}.
55   * Default value is {@code false}.
56   * </li>
57   * </ul>
58   *
59   * <p>
60   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
61   * </p>
62   *
63   * <p>
64   * Violation Message Keys:
65   * </p>
66   * <ul>
67   * <li>
68   * {@code illegal.abstract.class.name}
69   * </li>
70   * <li>
71   * {@code no.abstract.class.modifier}
72   * </li>
73   * </ul>
74   *
75   * @since 3.2
76   */
77  @StatelessCheck
78  public final class AbstractClassNameCheck extends AbstractCheck {
79  
80      /**
81       * A key is pointing to the warning message text in "messages.properties"
82       * file.
83       */
84      public static final String MSG_ILLEGAL_ABSTRACT_CLASS_NAME = "illegal.abstract.class.name";
85  
86      /**
87       * A key is pointing to the warning message text in "messages.properties"
88       * file.
89       */
90      public static final String MSG_NO_ABSTRACT_CLASS_MODIFIER = "no.abstract.class.modifier";
91  
92      /**
93       * Control whether to ignore checking for the {@code abstract} modifier on
94       * classes that match the name.
95       */
96      private boolean ignoreModifier;
97  
98      /**
99       * Control whether to ignore checking the name. Realistically only useful
100      * if using the check to identify that match name and do not have the
101      * {@code abstract} modifier.
102      */
103     private boolean ignoreName;
104 
105     /** Specify valid identifiers. */
106     private Pattern format = Pattern.compile("^Abstract.+$");
107 
108     /**
109      * Setter to control whether to ignore checking for the {@code abstract} modifier on
110      * classes that match the name.
111      *
112      * @param value new value
113      * @since 5.3
114      */
115     public void setIgnoreModifier(boolean value) {
116         ignoreModifier = value;
117     }
118 
119     /**
120      * Setter to control whether to ignore checking the name. Realistically only useful if
121      * using the check to identify that match name and do not have the {@code abstract} modifier.
122      *
123      * @param value new value.
124      * @since 5.3
125      */
126     public void setIgnoreName(boolean value) {
127         ignoreName = value;
128     }
129 
130     /**
131      * Setter to specify valid identifiers.
132      *
133      * @param pattern the new pattern
134      * @since 3.2
135      */
136     public void setFormat(Pattern pattern) {
137         format = pattern;
138     }
139 
140     @Override
141     public int[] getDefaultTokens() {
142         return getRequiredTokens();
143     }
144 
145     @Override
146     public int[] getRequiredTokens() {
147         return new int[] {TokenTypes.CLASS_DEF};
148     }
149 
150     @Override
151     public int[] getAcceptableTokens() {
152         return getRequiredTokens();
153     }
154 
155     @Override
156     public void visitToken(DetailAST ast) {
157         visitClassDef(ast);
158     }
159 
160     /**
161      * Checks class definition.
162      *
163      * @param ast class definition for check.
164      */
165     private void visitClassDef(DetailAST ast) {
166         final String className =
167             ast.findFirstToken(TokenTypes.IDENT).getText();
168         if (isAbstract(ast)) {
169             // if class has abstract modifier
170             if (!ignoreName && !isMatchingClassName(className)) {
171                 log(ast, MSG_ILLEGAL_ABSTRACT_CLASS_NAME, className, format.pattern());
172             }
173         }
174         else if (!ignoreModifier && isMatchingClassName(className)) {
175             log(ast, MSG_NO_ABSTRACT_CLASS_MODIFIER, className);
176         }
177     }
178 
179     /**
180      * Checks if declared class is abstract or not.
181      *
182      * @param ast class definition for check.
183      * @return true if a given class declared as abstract.
184      */
185     private static boolean isAbstract(DetailAST ast) {
186         final DetailAST abstractAST = ast.findFirstToken(TokenTypes.MODIFIERS)
187             .findFirstToken(TokenTypes.ABSTRACT);
188 
189         return abstractAST != null;
190     }
191 
192     /**
193      * Returns true if class name matches format of abstract class names.
194      *
195      * @param className class name for check.
196      * @return true if class name matches format of abstract class names.
197      */
198     private boolean isMatchingClassName(String className) {
199         return format.matcher(className).find();
200     }
201 
202 }