View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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  
20  package com.puppycrawl.tools.checkstyle.checks.sizes;
21  
22  import java.util.Arrays;
23  
24  import com.puppycrawl.tools.checkstyle.StatelessCheck;
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  import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
29  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
30  
31  /**
32   * <div>
33   * Checks the number of record components in the
34   * <a href="https://docs.oracle.com/javase/specs/jls/se14/preview/specs/records-jls.html#jls-8.10.1">
35   * header</a> of a record definition.
36   * </div>
37   *
38   * <ul>
39   * <li>
40   * Property {@code accessModifiers} - Access modifiers of record definitions where
41   * the number of record components should be checked.
42   * Type is {@code com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption[]}.
43   * Default value is {@code public, protected, package, private}.
44   * </li>
45   * <li>
46   * Property {@code max} - Specify the maximum number of components allowed in the header of a
47   * record definition.
48   * Type is {@code int}.
49   * Default value is {@code 8}.
50   * </li>
51   * </ul>
52   *
53   * <p>
54   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
55   * </p>
56   *
57   * <p>
58   * Violation Message Keys:
59   * </p>
60   * <ul>
61   * <li>
62   * {@code too.many.components}
63   * </li>
64   * </ul>
65   *
66   * @since 8.36
67   */
68  @StatelessCheck
69  public class RecordComponentNumberCheck extends AbstractCheck {
70  
71      /**
72       * A key is pointing to the warning message text in "messages.properties"
73       * file.
74       */
75      public static final String MSG_KEY = "too.many.components";
76  
77      /** Default maximum number of allowed components. */
78      private static final int DEFAULT_MAX_COMPONENTS = 8;
79  
80      /** Specify the maximum number of components allowed in the header of a record definition. */
81      private int max = DEFAULT_MAX_COMPONENTS;
82  
83      /**
84       * Access modifiers of record definitions where the number
85       * of record components should be checked.
86       */
87      private AccessModifierOption[] accessModifiers = {
88          AccessModifierOption.PUBLIC,
89          AccessModifierOption.PROTECTED,
90          AccessModifierOption.PACKAGE,
91          AccessModifierOption.PRIVATE,
92      };
93  
94      /**
95       * Setter to specify the maximum number of components allowed in the header
96       * of a record definition.
97       *
98       * @param value the maximum allowed.
99       * @since 8.36
100      */
101     public void setMax(int value) {
102         max = value;
103     }
104 
105     /**
106      * Setter to access modifiers of record definitions where the number of record
107      * components should be checked.
108      *
109      * @param accessModifiers access modifiers of record definitions which should be checked.
110      * @since 8.36
111      */
112     public void setAccessModifiers(AccessModifierOption... accessModifiers) {
113         this.accessModifiers =
114                 Arrays.copyOf(accessModifiers, accessModifiers.length);
115     }
116 
117     @Override
118     public int[] getDefaultTokens() {
119         return getAcceptableTokens();
120     }
121 
122     @Override
123     public int[] getAcceptableTokens() {
124         return new int[] {
125             TokenTypes.RECORD_DEF,
126         };
127     }
128 
129     @Override
130     public int[] getRequiredTokens() {
131         return getAcceptableTokens();
132     }
133 
134     @Override
135     public void visitToken(DetailAST ast) {
136         final AccessModifierOption accessModifier =
137             CheckUtil.getAccessModifierFromModifiersToken(ast);
138 
139         if (matchAccessModifiers(accessModifier)) {
140             final DetailAST recordComponents =
141                 ast.findFirstToken(TokenTypes.RECORD_COMPONENTS);
142             final int componentCount = countComponents(recordComponents);
143 
144             if (componentCount > max) {
145                 log(ast, MSG_KEY, componentCount, max);
146             }
147         }
148     }
149 
150     /**
151      * Method to count the number of record components in this record definition.
152      *
153      * @param recordComponents the ast to check
154      * @return the number of record components in this record definition
155      */
156     private static int countComponents(DetailAST recordComponents) {
157         return recordComponents.getChildCount(TokenTypes.RECORD_COMPONENT_DEF);
158     }
159 
160     /**
161      * Checks whether a record definition has the correct access modifier to be checked.
162      *
163      * @param accessModifier the access modifier of the record definition.
164      * @return whether the record definition matches the expected access modifier.
165      */
166     private boolean matchAccessModifiers(final AccessModifierOption accessModifier) {
167         return Arrays.stream(accessModifiers)
168                 .anyMatch(modifier -> modifier == accessModifier);
169     }
170 }