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   * &lt;module name="AbstractClassName"&gt;
61   *   &lt;property name="ignoreModifier" value="true"/&gt;
62   * &lt;/module&gt;
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 }