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}