001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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 * <div> 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 * </div> 039 * 040 * <ul> 041 * <li> 042 * Property {@code accessModifiers} - Access modifiers of record definitions where 043 * the number of record components should be checked. 044 * Type is {@code com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption[]}. 045 * Default value is {@code public, protected, package, private}. 046 * </li> 047 * <li> 048 * Property {@code max} - Specify the maximum number of components allowed in the header of a 049 * record definition. 050 * Type is {@code int}. 051 * Default value is {@code 8}. 052 * </li> 053 * </ul> 054 * 055 * <p> 056 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 057 * </p> 058 * 059 * <p> 060 * Violation Message Keys: 061 * </p> 062 * <ul> 063 * <li> 064 * {@code too.many.components} 065 * </li> 066 * </ul> 067 * 068 * @since 8.36 069 */ 070@StatelessCheck 071public class RecordComponentNumberCheck extends AbstractCheck { 072 073 /** 074 * A key is pointing to the warning message text in "messages.properties" 075 * file. 076 */ 077 public static final String MSG_KEY = "too.many.components"; 078 079 /** Default maximum number of allowed components. */ 080 private static final int DEFAULT_MAX_COMPONENTS = 8; 081 082 /** Specify the maximum number of components allowed in the header of a record definition. */ 083 private int max = DEFAULT_MAX_COMPONENTS; 084 085 /** 086 * Access modifiers of record definitions where the number 087 * of record components should be checked. 088 */ 089 private AccessModifierOption[] accessModifiers = { 090 AccessModifierOption.PUBLIC, 091 AccessModifierOption.PROTECTED, 092 AccessModifierOption.PACKAGE, 093 AccessModifierOption.PRIVATE, 094 }; 095 096 /** 097 * Setter to specify the maximum number of components allowed in the header 098 * of a record definition. 099 * 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}