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