//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2017 the original author or authors. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package com.github.sevntu.checkstyle.checks.naming; import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; import java.util.List; import java.util.regex.Pattern; import com.puppycrawl.tools.checkstyle.api.AbstractCheck; import com.puppycrawl.tools.checkstyle.api.DetailAST; import com.puppycrawl.tools.checkstyle.api.TokenTypes; /** * Check forces enum constants to match one of the specified patterns and forces * all the values to follow only one of the specified patterns. * * <p>By default both CamelCase and UPPER_CASE are allowed, so check validates, * whether all the values conform the either of them. * * <p>For example, both enums are allowed by the check:<pre> * public enum EnumOne { * FirstElement, SecondElement, ThirdElement; * } * public enum EnumTwo { * FIRST_ELEMENT, SECOND_ELEMENT, THIRD_ELEMENT; * }</pre> But the following enum, is violated, because values conform * different notations: <pre> * public enum EnumThree { * FirstElement, SECOND_ELEMENT, ThirdElement; * } * </pre> * * <p>To use only CamelCase, use the following configuration: * * <pre> * <module name="UniformEnumConstantNameCheck"> * <property name="format" value="^[A-Z][a-zA-Z0-9]*$"/> * </module> * </pre> * * <p>If both CamelCase and UPPER_CASE are allowed, use the following configuration * (this is the default): * * <pre> * <module name="UniformEnumConstantNameCheck"> * <property name="format" value="^[A-Z][a-zA-Z0-9]*$,^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"/> * </module> * </pre> * * <p>Only first violation is reported for each enumeration because of the nature * of the check: it's impossible to determine which specific pattern user should * follow for this certain enumeration, as multiple patterns have been * specified. The only thing that this check reports is whether there is at * least one pattern (among specified in the configuration), which all the enum * constant conform or there is no. * * @author Pavel Baranchikov */ public class UniformEnumConstantNameCheck extends AbstractCheck { /** * Message code for format violations. Used, when more than one format * violated. */ public static final String MSG_NOT_VALID_MULTI = "enum.name.formats.violated"; /** * Message code for format violations. Used, when exactly one format * violated. */ public static final String MSG_NOT_VALID_SINGLE = "enum.name.format.violated"; /** * Camel notation regular expression. */ public static final String CAMEL_PATTERN = "^[A-Z][a-zA-Z0-9]*$"; /** * Upper case notation regular expression. */ public static final String UPPERCASE_PATTERN = "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"; /** * Default pattern for enumeration values. */ public static final String[] DEFAULT_PATTERN = { CAMEL_PATTERN, UPPERCASE_PATTERN, }; /** * Regular expression list to test Enumeration names against. */ private List<Pattern> patterns; /** * Number of patterns specified for {@code patterns} field. This field is * always the size of {@link #patterns}. */ private int patternCount; /** * Constructs check with the default pattern. */ public UniformEnumConstantNameCheck() { setFormats(DEFAULT_PATTERN); } /** * Method sets format to match Class Enumeration names. * @param regexps format to check against */ public final void setFormats(String[] regexps) { this.patterns = new ArrayList<Pattern>(regexps.length); for (final String regexp: regexps) { final Pattern pattern = Pattern.compile(regexp, 0); patterns.add(pattern); } patternCount = regexps.length; } @Override public int[] getDefaultTokens() { return getAcceptableTokens(); } @Override public int[] getAcceptableTokens() { return new int[] { TokenTypes.ENUM_DEF, }; } @Override public int[] getRequiredTokens() { return getAcceptableTokens(); } @Override public void visitToken(DetailAST ast) { final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); final BitSet conformedPatterns = new BitSet(patternCount); conformedPatterns.set(0, patternCount); for (DetailAST member = objBlock.getFirstChild(); member != null && !conformedPatterns.isEmpty(); member = member.getNextSibling()) { if (member.getType() != TokenTypes.ENUM_CONSTANT_DEF) { continue; } final String enumName = member.findFirstToken(TokenTypes.IDENT).getText(); final BitSet matched = match(enumName, conformedPatterns); if (matched.isEmpty()) { logViolation(member, enumName, conformedPatterns); } conformedPatterns.and(matched); } } /** * Logs violation for the specified token, representing the specified enum * value wich violates the specified patterns. * * @param member * token, which violates the check * @param enumName * enum value name for this token * @param violated * bit set of violated patterns */ private void logViolation(DetailAST member, String enumName, BitSet violated) { final String patternsString; final String msgKey; if (violated.cardinality() == 1) { msgKey = MSG_NOT_VALID_SINGLE; patternsString = patterns.get(violated.nextSetBit(0)) .toString(); } else { msgKey = MSG_NOT_VALID_MULTI; final Collection<Pattern> violatedPatterns = new ArrayList<>( violated.cardinality()); int index = violated.nextSetBit(0); while (index >= 0) { violatedPatterns.add(patterns.get(index)); index = violated.nextSetBit(index + 1); } patternsString = violatedPatterns.toString(); } log(member.getLineNo(), member.getColumnNo(), msgKey, enumName, patternsString); } /** * Matches the specified enum name against the patterns, specified by * {@code conformedPattenrs}. * * @param name * name to validate * @param conformedPatterns * bit set of patterns, which the method should match against. * @return bit set of matched patterns. Returned value is alwais a subset of * {@code conformedPatterns} */ private BitSet match(String name, BitSet conformedPatterns) { final BitSet result = new BitSet(patternCount); for (int i = 0; i < patterns.size(); i++) { if (conformedPatterns.get(i)) { final Pattern pattern = patterns.get(i); if (pattern.matcher(name).find()) { result.set(i); } } } return result; } }