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