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