UniformEnumConstantNameCheck.java

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // checkstyle: Checks Java source code for adherence to a set of rules.
  3. // Copyright (C) 2001-2019 the original author or authors.
  4. //
  5. // This library is free software; you can redistribute it and/or
  6. // modify it under the terms of the GNU Lesser General Public
  7. // License as published by the Free Software Foundation; either
  8. // version 2.1 of the License, or (at your option) any later version.
  9. //
  10. // This library is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13. // Lesser General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Lesser General Public
  16. // License along with this library; if not, write to the Free Software
  17. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  18. ////////////////////////////////////////////////////////////////////////////////

  19. package com.github.sevntu.checkstyle.checks.naming;

  20. import java.util.ArrayList;
  21. import java.util.BitSet;
  22. import java.util.Collection;
  23. import java.util.List;
  24. import java.util.regex.Pattern;

  25. import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
  26. import com.puppycrawl.tools.checkstyle.api.DetailAST;
  27. import com.puppycrawl.tools.checkstyle.api.TokenTypes;

  28. /**
  29.  * Check forces enum constants to match one of the specified patterns and forces
  30.  * all the values to follow only one of the specified patterns.
  31.  *
  32.  * <p>By default both CamelCase and UPPER_CASE are allowed, so check validates,
  33.  * whether all the values conform the either of them.
  34.  *
  35.  * <p>For example, both enums are allowed by the check:<pre>
  36.  * public enum EnumOne {
  37.  *    FirstElement, SecondElement, ThirdElement;
  38.  * }
  39.  * public enum EnumTwo {
  40.  *    FIRST_ELEMENT, SECOND_ELEMENT, THIRD_ELEMENT;
  41.  * }</pre> But the following enum, is violated, because values conform
  42.  * different notations: <pre>
  43.  * public enum EnumThree {
  44.  *    FirstElement, SECOND_ELEMENT, ThirdElement;
  45.  * }
  46.  * </pre>
  47.  *
  48.  * <p>To use only CamelCase, use the following configuration:
  49.  *
  50.  * <pre>
  51.  * &lt;module name="UniformEnumConstantNameCheck"&gt;
  52.  *    &lt;property name="format" value="^[A-Z][a-zA-Z0-9]*$"/&gt;
  53.  * &lt;/module&gt;
  54.  * </pre>
  55.  *
  56.  * <p>If both CamelCase and UPPER_CASE are allowed, use the following configuration
  57.  * (this is the default):
  58.  *
  59.  * <pre>
  60.  * &lt;module name="UniformEnumConstantNameCheck"&gt;
  61.  *    &lt;property name="format" value="^[A-Z][a-zA-Z0-9]*$,^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"/&gt;
  62.  * &lt;/module&gt;
  63.  * </pre>
  64.  *
  65.  * <p>Only first violation is reported for each enumeration because of the nature
  66.  * of the check: it's impossible to determine which specific pattern user should
  67.  * follow for this certain enumeration, as multiple patterns have been
  68.  * specified. The only thing that this check reports is whether there is at
  69.  * least one pattern (among specified in the configuration), which all the enum
  70.  * constant conform or there is no.
  71.  *
  72.  * @author Pavel Baranchikov
  73.  * @since 1.21.0
  74.  */
  75. public class UniformEnumConstantNameCheck extends AbstractCheck {

  76.     /**
  77.      * Message code for format violations. Used, when more than one format
  78.      * violated.
  79.      */
  80.     public static final String MSG_NOT_VALID_MULTI = "enum.name.formats.violated";
  81.     /**
  82.      * Message code for format violations. Used, when exactly one format
  83.      * violated.
  84.      */
  85.     public static final String MSG_NOT_VALID_SINGLE = "enum.name.format.violated";
  86.     /**
  87.      * Camel notation regular expression.
  88.      */
  89.     public static final String CAMEL_PATTERN = "^[A-Z][a-zA-Z0-9]*$";
  90.     /**
  91.      * Upper case notation regular expression.
  92.      */
  93.     public static final String UPPERCASE_PATTERN = "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$";
  94.     /**
  95.      * Default pattern for enumeration values.
  96.      */
  97.     public static final String[] DEFAULT_PATTERN = {
  98.         CAMEL_PATTERN,
  99.         UPPERCASE_PATTERN,
  100.     };

  101.     /**
  102.      * Regular expression list to test Enumeration names against.
  103.      */
  104.     private List<Pattern> patterns;
  105.     /**
  106.      * Number of patterns specified for {@code patterns} field. This field is
  107.      * always the size of {@link #patterns}.
  108.      */
  109.     private int patternCount;

  110.     /**
  111.      * Constructs check with the default pattern.
  112.      */
  113.     public UniformEnumConstantNameCheck() {
  114.         setFormats(DEFAULT_PATTERN);
  115.     }

  116.     /**
  117.      * Method sets format to match Class Enumeration names.
  118.      * @param regexps format to check against
  119.      */
  120.     public final void setFormats(String... regexps) {
  121.         this.patterns = new ArrayList<>(regexps.length);
  122.         for (final String regexp: regexps) {
  123.             final Pattern pattern = Pattern.compile(regexp, 0);
  124.             patterns.add(pattern);
  125.         }
  126.         patternCount = regexps.length;
  127.     }

  128.     @Override
  129.     public int[] getDefaultTokens() {
  130.         return getAcceptableTokens();
  131.     }

  132.     @Override
  133.     public int[] getAcceptableTokens() {
  134.         return new int[] {
  135.             TokenTypes.ENUM_DEF,
  136.             };
  137.     }

  138.     @Override
  139.     public int[] getRequiredTokens() {
  140.         return getAcceptableTokens();
  141.     }

  142.     @Override
  143.     public void visitToken(DetailAST ast) {
  144.         final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
  145.         final BitSet conformedPatterns = new BitSet(patternCount);
  146.         conformedPatterns.set(0, patternCount);
  147.         for (DetailAST member = objBlock.getFirstChild(); member != null
  148.                 && !conformedPatterns.isEmpty();
  149.                 member = member.getNextSibling()) {
  150.             if (member.getType() != TokenTypes.ENUM_CONSTANT_DEF) {
  151.                 continue;
  152.             }
  153.             final String enumName = member.findFirstToken(TokenTypes.IDENT).getText();
  154.             final BitSet matched = match(enumName, conformedPatterns);
  155.             if (matched.isEmpty()) {
  156.                 logViolation(member, enumName, conformedPatterns);
  157.             }
  158.             conformedPatterns.and(matched);
  159.         }
  160.     }

  161.     /**
  162.      * Logs violation for the specified token, representing the specified enum
  163.      * value wich violates the specified patterns.
  164.      *
  165.      * @param member
  166.      *        token, which violates the check
  167.      * @param enumName
  168.      *        enum value name for this token
  169.      * @param violated
  170.      *        bit set of violated patterns
  171.      */
  172.     private void logViolation(DetailAST member, String enumName, BitSet violated) {
  173.         final String patternsString;
  174.         final String msgKey;
  175.         if (violated.cardinality() == 1) {
  176.             msgKey = MSG_NOT_VALID_SINGLE;
  177.             patternsString = patterns.get(violated.nextSetBit(0))
  178.                     .toString();
  179.         }
  180.         else {
  181.             msgKey = MSG_NOT_VALID_MULTI;
  182.             final Collection<Pattern> violatedPatterns = new ArrayList<>(
  183.                     violated.cardinality());
  184.             int index = violated.nextSetBit(0);
  185.             while (index >= 0) {
  186.                 violatedPatterns.add(patterns.get(index));
  187.                 index = violated.nextSetBit(index + 1);
  188.             }
  189.             patternsString = violatedPatterns.toString();
  190.         }
  191.         log(member, msgKey, enumName,
  192.                 patternsString);
  193.     }

  194.     /**
  195.      * Matches the specified enum name against the patterns, specified by
  196.      * {@code conformedPatterns}.
  197.      *
  198.      * @param name
  199.      *        name to validate
  200.      * @param conformedPatterns
  201.      *        bit set of patterns, which the method should match against.
  202.      * @return bit set of matched patterns. Returned value is always a subset of
  203.      *         {@code conformedPatterns}
  204.      */
  205.     private BitSet match(String name, BitSet conformedPatterns) {
  206.         final BitSet result = new BitSet(patternCount);
  207.         for (int i = 0; i < patterns.size(); i++) {
  208.             if (conformedPatterns.get(i)) {
  209.                 final Pattern pattern = patterns.get(i);
  210.                 if (pattern.matcher(name).find()) {
  211.                     result.set(i);
  212.                 }
  213.             }
  214.         }
  215.         return result;
  216.     }

  217. }