001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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 com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.TokenTypes;
024import com.puppycrawl.tools.checkstyle.checks.AbstractFormatCheck;
025
026/**
027 * <p>
028 * Ensures that the names of abstract classes conforming to some
029 * regular expression and check that {@code abstract} modifier exists.
030 * </p>
031 * <p>
032 * Rationale: Abstract classes are convenience base class
033 * implementations of interfaces, not types as such. As such
034 * they should be named to indicate this. Also if names of classes
035 * starts with 'Abstract' it's very convenient that they will
036 * have abstract modifier.
037 * </p>
038 *
039 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
040 * @author <a href="mailto:solid.danil@gmail.com">Danil Lopatin</a>
041 */
042public final class AbstractClassNameCheck extends AbstractFormatCheck {
043
044    /**
045     * A key is pointing to the warning message text in "messages.properties"
046     * file.
047     */
048    public static final String ILLEGAL_ABSTRACT_CLASS_NAME = "illegal.abstract.class.name";
049
050    /**
051     * A key is pointing to the warning message text in "messages.properties"
052     * file.
053     */
054    public static final String NO_ABSTRACT_CLASS_MODIFIER = "no.abstract.class.modifier";
055
056    /** Default format for abstract class names. */
057    private static final String DEFAULT_FORMAT = "^Abstract.+$";
058
059    /** Whether to ignore checking the modifier. */
060    private boolean ignoreModifier;
061
062    /** Whether to ignore checking the name. */
063    private boolean ignoreName;
064
065    /** Creates new instance of the check. */
066    public AbstractClassNameCheck() {
067        super(DEFAULT_FORMAT);
068    }
069
070    /**
071     * Whether to ignore checking for the {@code abstract} modifier.
072     * @param value new value
073     */
074    public void setIgnoreModifier(boolean value) {
075        ignoreModifier = value;
076    }
077
078    /**
079     * Whether to ignore checking the name.
080     * @param value new value.
081     */
082    public void setIgnoreName(boolean value) {
083        ignoreName = value;
084    }
085
086    @Override
087    public int[] getDefaultTokens() {
088        return new int[]{TokenTypes.CLASS_DEF};
089    }
090
091    @Override
092    public int[] getRequiredTokens() {
093        return new int[]{TokenTypes.CLASS_DEF};
094    }
095
096    @Override
097    public int[] getAcceptableTokens() {
098        return new int[]{TokenTypes.CLASS_DEF};
099    }
100
101    @Override
102    public void visitToken(DetailAST ast) {
103        visitClassDef(ast);
104    }
105
106    /**
107     * Checks class definition.
108     * @param ast class definition for check.
109     */
110    private void visitClassDef(DetailAST ast) {
111        final String className =
112            ast.findFirstToken(TokenTypes.IDENT).getText();
113        if (isAbstract(ast)) {
114            // if class has abstract modifier
115            if (!ignoreName && !isMatchingClassName(className)) {
116                log(ast.getLineNo(), ast.getColumnNo(),
117                    ILLEGAL_ABSTRACT_CLASS_NAME, className, getFormat());
118            }
119        }
120        else if (!ignoreModifier && isMatchingClassName(className)) {
121            log(ast.getLineNo(), ast.getColumnNo(),
122                NO_ABSTRACT_CLASS_MODIFIER, className);
123        }
124    }
125
126    /**
127     * @param ast class definition for check.
128     * @return true if a given class declared as abstract.
129     */
130    private static boolean isAbstract(DetailAST ast) {
131        final DetailAST abstractAST = ast.findFirstToken(TokenTypes.MODIFIERS)
132            .findFirstToken(TokenTypes.ABSTRACT);
133
134        return abstractAST != null;
135    }
136
137    /**
138     * @param className class name for check.
139     * @return true if class name matches format of abstract class names.
140     */
141    private boolean isMatchingClassName(String className) {
142        return getRegexp().matcher(className).find();
143    }
144}