001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.sizes; 021 022import java.util.Arrays; 023import java.util.concurrent.atomic.AtomicInteger; 024 025import com.puppycrawl.tools.checkstyle.StatelessCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption; 030import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 031import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 032 033/** 034 * <p> 035 * Checks the number of record components in the 036 * <a href="https://docs.oracle.com/javase/specs/jls/se14/preview/specs/records-jls.html#jls-8.10.1"> 037 * header</a> of a record definition. 038 * </p> 039 * <ul> 040 * <li> 041 * Property {@code max} - Specify the maximum number of components allowed in the header of a 042 * record definition. 043 * Type is {@code int}. 044 * Default value is {@code 8}. 045 * </li> 046 * <li> 047 * Property {@code accessModifiers} - Access modifiers of record definitions where 048 * the number of record components should be checked. 049 * Type is {@code com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption[]}. 050 * Default value is {@code public, protected, package, private}. 051 * </li> 052 * </ul> 053 * <p> 054 * To configure the check: 055 * </p> 056 * <pre> 057 * <module name="RecordComponentNumber"/> 058 * </pre> 059 * <p> 060 * Java code example: 061 * </p> 062 * <pre> 063 * public record MyRecord1(int x, int y) { // ok, 2 components 064 * ... 065 * } 066 * 067 * record MyRecord2(int x, int y, String str, 068 * Node node, Order order, Data data 069 * String location, Date date, Image image) { // violation, 9 components 070 * ... 071 * } 072 * </pre> 073 * <p> 074 * To configure the check to allow 5 record components at all access modifier levels 075 * for record definitions: 076 * </p> 077 * <pre> 078 * <module name="RecordComponentNumber"> 079 * <property name="max" value="5"/> 080 * </module> 081 * </pre> 082 * <p> 083 * Java code example: 084 * </p> 085 * <pre> 086 * public record MyRecord1(int x, int y, String str) { // ok, 3 components 087 * ... 088 * } 089 * 090 * public record MyRecord2(int x, int y, String str, 091 * Node node, Order order, Data data) { // violation, 6 components 092 * ... 093 * } 094 * </pre> 095 * <p> 096 * To configure the check to allow 10 record components for a public record definition, 097 * but 3 for private record definitions: 098 * </p> 099 * <pre> 100 * <module name="RecordComponentNumber"> 101 * <property name="max" value="3"/> 102 * <property name="accessModifiers" value="private"/> 103 * </module> 104 * <module name="RecordComponentNumber"> 105 * <property name="max" value="10"/> 106 * <property name="accessModifiers" value="public"/> 107 * </module> 108 * </pre> 109 * <p> 110 * Java code example: 111 * </p> 112 * <pre> 113 * public record MyRecord1(int x, int y, String str) { // ok, public record definition allowed 10 114 * ... 115 * } 116 * 117 * private record MyRecord2(int x, int y, String str, Node node) { // violation 118 * ... // private record definition allowed 3 components 119 * } 120 * </pre> 121 * <p> 122 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 123 * </p> 124 * <p> 125 * Violation Message Keys: 126 * </p> 127 * <ul> 128 * <li> 129 * {@code too.many.components} 130 * </li> 131 * </ul> 132 * 133 * @since 8.36 134 */ 135@StatelessCheck 136public class RecordComponentNumberCheck extends AbstractCheck { 137 138 /** 139 * A key is pointing to the warning message text in "messages.properties" 140 * file. 141 */ 142 public static final String MSG_KEY = "too.many.components"; 143 144 /** Default maximum number of allowed components. */ 145 private static final int DEFAULT_MAX_COMPONENTS = 8; 146 147 /** Specify the maximum number of components allowed in the header of a record definition. */ 148 private int max = DEFAULT_MAX_COMPONENTS; 149 150 /** 151 * Access modifiers of record definitions where the number 152 * of record components should be checked. 153 */ 154 private AccessModifierOption[] accessModifiers = { 155 AccessModifierOption.PUBLIC, 156 AccessModifierOption.PROTECTED, 157 AccessModifierOption.PACKAGE, 158 AccessModifierOption.PRIVATE, 159 }; 160 161 /** 162 * Setter to specify the maximum number of components allowed in the header 163 * of a record definition. 164 * 165 * @param value the maximum allowed. 166 */ 167 public void setMax(int value) { 168 max = value; 169 } 170 171 /** 172 * Setter to access modifiers of record definitions where the number of record 173 * components should be checked. 174 * 175 * @param accessModifiers access modifiers of record definitions which should be checked. 176 */ 177 public void setAccessModifiers(AccessModifierOption... accessModifiers) { 178 this.accessModifiers = 179 Arrays.copyOf(accessModifiers, accessModifiers.length); 180 } 181 182 @Override 183 public int[] getDefaultTokens() { 184 return getAcceptableTokens(); 185 } 186 187 @Override 188 public int[] getAcceptableTokens() { 189 return new int[] { 190 TokenTypes.RECORD_DEF, 191 }; 192 } 193 194 @Override 195 public int[] getRequiredTokens() { 196 return getAcceptableTokens(); 197 } 198 199 @Override 200 public void visitToken(DetailAST ast) { 201 final AccessModifierOption accessModifier = 202 CheckUtil.getAccessModifierFromModifiersToken(ast); 203 204 if (matchAccessModifiers(accessModifier)) { 205 final DetailAST recordComponents = 206 ast.findFirstToken(TokenTypes.RECORD_COMPONENTS); 207 final int componentCount = countComponents(recordComponents); 208 209 if (componentCount > max) { 210 log(ast, MSG_KEY, componentCount, max); 211 } 212 } 213 } 214 215 /** 216 * Method to count the number of record components in this record definition. 217 * 218 * @param recordComponents the ast to check 219 * @return the number of record components in this record definition 220 */ 221 private static int countComponents(DetailAST recordComponents) { 222 final AtomicInteger count = new AtomicInteger(0); 223 TokenUtil.forEachChild(recordComponents, 224 TokenTypes.RECORD_COMPONENT_DEF, 225 node -> count.getAndIncrement()); 226 return count.get(); 227 } 228 229 /** 230 * Checks whether a record definition has the correct access modifier to be checked. 231 * 232 * @param accessModifier the access modifier of the record definition. 233 * @return whether the record definition matches the expected access modifier. 234 */ 235 private boolean matchAccessModifiers(final AccessModifierOption accessModifier) { 236 return Arrays.stream(accessModifiers) 237 .anyMatch(modifier -> modifier == accessModifier); 238 } 239}