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