001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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.coding;
021
022import java.util.ArrayList;
023import java.util.List;
024import java.util.Optional;
025
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030
031/**
032 * <div>
033 * Checks that all constructors are grouped together.
034 * If there is any non-constructor code separating constructors,
035 * this check identifies and logs a violation for those ungrouped constructors.
036 * The violation message will specify the line number of the last grouped constructor.
037 * Comments between constructors are allowed.
038 * </div>
039 *
040 * <p>
041 * Rationale: Grouping constructors together in a class improves code readability
042 * and maintainability. It allows developers to easily understand
043 * the different ways an object can be instantiated
044 * and the tasks performed by each constructor.
045 * </p>
046 *
047 * @since 10.17.0
048 */
049
050@StatelessCheck
051public class ConstructorsDeclarationGroupingCheck extends AbstractCheck {
052
053    /**
054     * A key is pointing to the warning message text in "messages.properties"
055     * file.
056     */
057    public static final String MSG_KEY = "constructors.declaration.grouping";
058
059    @Override
060    public int[] getDefaultTokens() {
061        return getRequiredTokens();
062    }
063
064    @Override
065    public int[] getAcceptableTokens() {
066        return getRequiredTokens();
067    }
068
069    @Override
070    public int[] getRequiredTokens() {
071        return new int[] {
072            TokenTypes.CLASS_DEF,
073            TokenTypes.ENUM_DEF,
074            TokenTypes.RECORD_DEF,
075        };
076    }
077
078    @Override
079    public void visitToken(DetailAST ast) {
080        // list of all child ASTs
081        final List<DetailAST> children = getChildList(ast);
082
083        // find first constructor
084        final DetailAST firstConstructor = children.stream()
085                .filter(ConstructorsDeclarationGroupingCheck::isConstructor)
086                .findFirst()
087                .orElse(null);
088
089        if (firstConstructor != null) {
090
091            // get all children AST after the first constructor
092            final List<DetailAST> childrenAfterFirstConstructor =
093                    children.subList(children.indexOf(firstConstructor), children.size());
094
095            // find the first index of non-constructor AST after the first constructor, if present
096            final Optional<Integer> indexOfFirstNonConstructor = childrenAfterFirstConstructor
097                    .stream()
098                    .filter(currAst -> !isConstructor(currAst))
099                    .findFirst()
100                    .map(children::indexOf);
101
102            // list of all children after first non-constructor AST
103            final List<DetailAST> childrenAfterFirstNonConstructor = indexOfFirstNonConstructor
104                    .map(index -> children.subList(index, children.size()))
105                    .orElseGet(ArrayList::new);
106
107            // create a list of all constructors that are not grouped to log
108            final List<DetailAST> constructorsToLog = childrenAfterFirstNonConstructor.stream()
109                    .filter(ConstructorsDeclarationGroupingCheck::isConstructor)
110                    .toList();
111
112            // find the last grouped constructor
113            final DetailAST lastGroupedConstructor = childrenAfterFirstConstructor.stream()
114                    .takeWhile(ConstructorsDeclarationGroupingCheck::isConstructor)
115                    .reduce((first, second) -> second)
116                    .orElse(firstConstructor);
117
118            // log all constructors that are not grouped
119            constructorsToLog
120                    .forEach(ctor -> log(ctor, MSG_KEY, lastGroupedConstructor.getLineNo()));
121        }
122    }
123
124    /**
125     * Get a list of all children of the given AST.
126     *
127     * @param ast the AST to get children of
128     * @return a list of all children of the given AST
129     */
130    private static List<DetailAST> getChildList(DetailAST ast) {
131        final List<DetailAST> children = new ArrayList<>();
132        DetailAST child = ast.findFirstToken(TokenTypes.OBJBLOCK).getFirstChild();
133        while (child != null) {
134            children.add(child);
135            child = child.getNextSibling();
136        }
137        return children;
138    }
139
140    /**
141     * Check if the given AST is a constructor.
142     *
143     * @param ast the AST to check
144     * @return true if the given AST is a constructor, false otherwise
145     */
146    private static boolean isConstructor(DetailAST ast) {
147        return ast.getType() == TokenTypes.CTOR_DEF
148                || ast.getType() == TokenTypes.COMPACT_CTOR_DEF;
149    }
150}