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 }