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 * <p>
048 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
049 * </p>
050 *
051 * <p>
052 * Violation Message Keys:
053 * </p>
054 * <ul>
055 * <li>
056 * {@code constructors.declaration.grouping}
057 * </li>
058 * </ul>
059 *
060 * @since 10.17.0
061 */
062
063@StatelessCheck
064public class ConstructorsDeclarationGroupingCheck extends AbstractCheck {
065
066    /**
067     * A key is pointing to the warning message text in "messages.properties"
068     * file.
069     */
070    public static final String MSG_KEY = "constructors.declaration.grouping";
071
072    @Override
073    public int[] getDefaultTokens() {
074        return getRequiredTokens();
075    }
076
077    @Override
078    public int[] getAcceptableTokens() {
079        return getRequiredTokens();
080    }
081
082    @Override
083    public int[] getRequiredTokens() {
084        return new int[] {
085            TokenTypes.CLASS_DEF,
086            TokenTypes.ENUM_DEF,
087            TokenTypes.RECORD_DEF,
088        };
089    }
090
091    @Override
092    public void visitToken(DetailAST ast) {
093        // list of all child ASTs
094        final List<DetailAST> children = getChildList(ast);
095
096        // find first constructor
097        final DetailAST firstConstructor = children.stream()
098                .filter(ConstructorsDeclarationGroupingCheck::isConstructor)
099                .findFirst()
100                .orElse(null);
101
102        if (firstConstructor != null) {
103
104            // get all children AST after the first constructor
105            final List<DetailAST> childrenAfterFirstConstructor =
106                    children.subList(children.indexOf(firstConstructor), children.size());
107
108            // find the first index of non-constructor AST after the first constructor, if present
109            final Optional<Integer> indexOfFirstNonConstructor = childrenAfterFirstConstructor
110                    .stream()
111                    .filter(currAst -> !isConstructor(currAst))
112                    .findFirst()
113                    .map(children::indexOf);
114
115            // list of all children after first non-constructor AST
116            final List<DetailAST> childrenAfterFirstNonConstructor = indexOfFirstNonConstructor
117                    .map(index -> children.subList(index, children.size()))
118                    .orElseGet(ArrayList::new);
119
120            // create a list of all constructors that are not grouped to log
121            final List<DetailAST> constructorsToLog = childrenAfterFirstNonConstructor.stream()
122                    .filter(ConstructorsDeclarationGroupingCheck::isConstructor)
123                    .toList();
124
125            // find the last grouped constructor
126            final DetailAST lastGroupedConstructor = childrenAfterFirstConstructor.stream()
127                    .takeWhile(ConstructorsDeclarationGroupingCheck::isConstructor)
128                    .reduce((first, second) -> second)
129                    .orElse(firstConstructor);
130
131            // log all constructors that are not grouped
132            constructorsToLog
133                    .forEach(ctor -> log(ctor, MSG_KEY, lastGroupedConstructor.getLineNo()));
134        }
135    }
136
137    /**
138     * Get a list of all children of the given AST.
139     *
140     * @param ast the AST to get children of
141     * @return a list of all children of the given AST
142     */
143    private static List<DetailAST> getChildList(DetailAST ast) {
144        final List<DetailAST> children = new ArrayList<>();
145        DetailAST child = ast.findFirstToken(TokenTypes.OBJBLOCK).getFirstChild();
146        while (child != null) {
147            children.add(child);
148            child = child.getNextSibling();
149        }
150        return children;
151    }
152
153    /**
154     * Check if the given AST is a constructor.
155     *
156     * @param ast the AST to check
157     * @return true if the given AST is a constructor, false otherwise
158     */
159    private static boolean isConstructor(DetailAST ast) {
160        return ast.getType() == TokenTypes.CTOR_DEF
161                || ast.getType() == TokenTypes.COMPACT_CTOR_DEF;
162    }
163}