001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.annotation; 021 022import java.util.HashSet; 023import java.util.Set; 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.utils.AnnotationUtil; 030import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 031 032/** 033 * <div> 034 * Verifies that explicitly declared record accessor methods include 035 * the {@code @Override} annotation. 036 * </div> 037 * 038 * <p> 039 * Per <a href="https://openjdk.org/jeps/395">JEP 395</a>, the meaning of the 040 * {@code @Override} annotation was extended to include explicitly declared 041 * accessor methods for record components. 042 * </p> 043 * 044 * <p> 045 * This check focuses only on record component accessor methods. It does not 046 * attempt to detect general method overrides from interfaces or superclasses, 047 * due to Checkstyle's single-file analysis limitations. 048 * </p> 049 * 050 * @since 13.1.0 051 */ 052@StatelessCheck 053public class MissingOverrideOnRecordAccessorCheck extends AbstractCheck { 054 055 /** 056 * A key is pointing to the warning message text in "messages.properties" file. 057 */ 058 public static final String MSG_KEY = "annotation.missing.override.record.accessor"; 059 060 @Override 061 public int[] getDefaultTokens() { 062 return getRequiredTokens(); 063 } 064 065 @Override 066 public int[] getAcceptableTokens() { 067 return getRequiredTokens(); 068 } 069 070 @Override 071 public int[] getRequiredTokens() { 072 return new int[] {TokenTypes.METHOD_DEF}; 073 } 074 075 @Override 076 public void visitToken(DetailAST ast) { 077 if (isRecordAccessorMethod(ast) && !AnnotationUtil.hasOverrideAnnotation(ast)) { 078 log(ast, MSG_KEY); 079 } 080 } 081 082 /** 083 * Checks if the given method is an explicitly declared accessor for a record component. 084 * 085 * @param ast the METHOD_DEF AST node 086 * @return true if this method is a record accessor 087 */ 088 private static boolean isRecordAccessorMethod(DetailAST ast) { 089 boolean result = false; 090 final DetailAST grandParent = ast.getParent().getParent(); 091 if (grandParent.getType() == TokenTypes.RECORD_DEF) { 092 final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS); 093 if (parameters.getChildCount() == 0) { 094 final String methodName = getMethodName(ast); 095 result = getRecordComponentNames(grandParent).contains(methodName); 096 } 097 } 098 return result; 099 } 100 101 /** 102 * Gets the method name from a method definition AST. 103 * 104 * @param methodDef the METHOD_DEF AST node 105 * @return the method name 106 */ 107 private static String getMethodName(DetailAST methodDef) { 108 return TokenUtil.getIdent(methodDef).getText(); 109 } 110 111 /** 112 * Gets the set of record component names from a record definition. 113 * 114 * @param recordDef the RECORD_DEF AST node 115 * @return set of component names 116 */ 117 private static Set<String> getRecordComponentNames(DetailAST recordDef) { 118 final Set<String> names = new HashSet<>(); 119 final DetailAST recordComponents = recordDef.findFirstToken(TokenTypes.RECORD_COMPONENTS); 120 DetailAST child = recordComponents.getFirstChild(); 121 while (child != null) { 122 if (child.getType() == TokenTypes.RECORD_COMPONENT_DEF) { 123 names.add(TokenUtil.getIdent(child).getText()); 124 } 125 child = child.getNextSibling(); 126 } 127 return names; 128 } 129}