001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.naming;
021
022import java.util.regex.Pattern;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028
029/**
030 * <p>
031 * Ensures that the names of abstract classes conforming to some pattern
032 * and check that {@code abstract} modifier exists.
033 * </p>
034 * <p>
035 * Rationale: Abstract classes are convenience base class implementations of
036 * interfaces. For this reason, it should be made obvious that a given class
037 * is abstract by prefacing the class name with 'Abstract'.
038 * </p>
039 * <ul>
040 * <li>
041 * Property {@code format} - Specify valid identifiers.
042 * Type is {@code java.util.regex.Pattern}.
043 * Default value is {@code "^Abstract.+$"}.</li>
044 * <li>
045 * Property {@code ignoreModifier} - Control whether to ignore checking for the
046 * {@code abstract} modifier on classes that match the name.
047 * Type is {@code boolean}.
048 * Default value is {@code false}.</li>
049 * <li>
050 * Property {@code ignoreName} - Control whether to ignore checking the name.
051 * Realistically only useful if using the check to identify that match name and
052 * do not have the {@code abstract} modifier.
053 * Type is {@code boolean}.
054 * Default value is {@code false}.
055 * </li>
056 * </ul>
057 * <p>
058 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
059 * </p>
060 * <p>
061 * Violation Message Keys:
062 * </p>
063 * <ul>
064 * <li>
065 * {@code illegal.abstract.class.name}
066 * </li>
067 * <li>
068 * {@code no.abstract.class.modifier}
069 * </li>
070 * </ul>
071 *
072 * @since 3.2
073 */
074@StatelessCheck
075public final class AbstractClassNameCheck extends AbstractCheck {
076
077    /**
078     * A key is pointing to the warning message text in "messages.properties"
079     * file.
080     */
081    public static final String MSG_ILLEGAL_ABSTRACT_CLASS_NAME = "illegal.abstract.class.name";
082
083    /**
084     * A key is pointing to the warning message text in "messages.properties"
085     * file.
086     */
087    public static final String MSG_NO_ABSTRACT_CLASS_MODIFIER = "no.abstract.class.modifier";
088
089    /**
090     * Control whether to ignore checking for the {@code abstract} modifier on
091     * classes that match the name.
092     */
093    private boolean ignoreModifier;
094
095    /**
096     * Control whether to ignore checking the name. Realistically only useful
097     * if using the check to identify that match name and do not have the
098     * {@code abstract} modifier.
099     */
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}