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.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 and the tasks performed by each constructor.
044 * </p>
045 *
046 * @since 10.17.0
047 */
048
049@StatelessCheck
050public class ConstructorsDeclarationGroupingCheck extends AbstractCheck {
051
052    /**
053     * A key is pointing to the warning message text in "messages.properties" file.
054     */
055    public static final String MSG_KEY = "constructors.declaration.grouping";
056
057    /**
058     * A key is pointing to the warning message text in "messages.properties" file.
059     */
060    public static final String MSG_ORDER = "constructors.declaration.order";
061
062    /**
063     * Control whether to order constructors by increasing parameter count or not.
064     */
065    private boolean orderByIncreasingParameterCount;
066
067    /**
068     * Creates a new {@code ConstructorsDeclarationGroupingCheck} instance.
069     */
070    public ConstructorsDeclarationGroupingCheck() {
071        // no code by default
072    }
073
074    /**
075     * Setter to control whether to enforce order by increasing parameter count (arity) or not.
076     *
077     * @param orderByIncreasingParameterCount true if order by increasing parameter
078     *        count is required.
079     * @since 13.6.0
080     */
081    public void setOrderByIncreasingParameterCount(boolean orderByIncreasingParameterCount) {
082        this.orderByIncreasingParameterCount = orderByIncreasingParameterCount;
083    }
084
085    @Override
086    public int[] getDefaultTokens() {
087        return getRequiredTokens();
088    }
089
090    @Override
091    public int[] getAcceptableTokens() {
092        return getRequiredTokens();
093    }
094
095    @Override
096    public int[] getRequiredTokens() {
097        return new int[] {
098            TokenTypes.CLASS_DEF,
099            TokenTypes.ENUM_DEF,
100            TokenTypes.RECORD_DEF,
101        };
102    }
103
104    @Override
105    public void visitToken(DetailAST ast) {
106        // list of all child ASTs
107        final List<DetailAST> children = getChildList(ast);
108
109        // find first constructor
110        final DetailAST firstConstructor = children.stream()
111                .filter(ConstructorsDeclarationGroupingCheck::isConstructor)
112                .findFirst()
113                .orElse(null);
114
115        if (firstConstructor != null) {
116
117            // get all children AST after the first constructor
118            final List<DetailAST> childrenAfterFirstConstructor =
119                    children.subList(children.indexOf(firstConstructor), children.size());
120
121            // find the first index of non-constructor AST after the first constructor, if present
122            final Optional<Integer> indexOfFirstNonConstructor = childrenAfterFirstConstructor
123                    .stream()
124                    .filter(currAst -> !isConstructor(currAst))
125                    .findFirst()
126                    .map(children::indexOf);
127
128            // list of all children after first non-constructor AST
129            final List<DetailAST> childrenAfterFirstNonConstructor = indexOfFirstNonConstructor
130                    .map(index -> children.subList(index, children.size()))
131                    .orElseGet(ArrayList::new);
132
133            // create a list of all constructors that are not grouped to log
134            final List<DetailAST> constructorsToLog = childrenAfterFirstNonConstructor.stream()
135                    .filter(ConstructorsDeclarationGroupingCheck::isConstructor)
136                    .toList();
137
138            // find the last grouped constructor
139            final DetailAST lastGroupedConstructor = childrenAfterFirstConstructor.stream()
140                    .takeWhile(ConstructorsDeclarationGroupingCheck::isConstructor)
141                    .reduce((first, second) -> second)
142                    .orElse(firstConstructor);
143
144            // log all constructors that are not grouped
145            constructorsToLog
146                    .forEach(ctor -> log(ctor, MSG_KEY, lastGroupedConstructor.getLineNo()));
147
148            if (orderByIncreasingParameterCount) {
149
150                // list of all constructor ASTs
151                final List<DetailAST> allConstructors = children.stream()
152                        .filter(ConstructorsDeclarationGroupingCheck::isConstructor)
153                        .toList();
154
155                int previousParamCount = 0;
156                boolean isOrdered = true;
157                for (DetailAST constructor : allConstructors) {
158                    final int currentParamCount = getParameterCount(constructor);
159                    isOrdered = isOrdered && currentParamCount >= previousParamCount;
160                    previousParamCount = currentParamCount;
161                    if (!isOrdered) {
162                        log(constructor, MSG_ORDER);
163                    }
164                }
165            }
166        }
167    }
168
169    /**
170     * Get a list of all children of the given AST.
171     *
172     * @param ast the AST to get children of
173     * @return a list of all children of the given AST
174     */
175    private static List<DetailAST> getChildList(DetailAST ast) {
176        final List<DetailAST> children = new ArrayList<>();
177        DetailAST child = ast.findFirstToken(TokenTypes.OBJBLOCK).getFirstChild();
178        while (child != null) {
179            children.add(child);
180            child = child.getNextSibling();
181        }
182        return children;
183    }
184
185    /**
186     * Check if the given AST is a constructor.
187     *
188     * @param ast the AST to check
189     * @return true if the given AST is a constructor, false otherwise
190     */
191    private static boolean isConstructor(DetailAST ast) {
192        return ast.getType() == TokenTypes.CTOR_DEF
193                || ast.getType() == TokenTypes.COMPACT_CTOR_DEF;
194    }
195
196    /**
197     * Get the parameter count of a constructor.
198     *
199     * @param constructor the constructor AST
200     * @return the parameter count of the constructor
201     */
202    private static int getParameterCount(DetailAST constructor) {
203        final DetailAST params = constructor.findFirstToken(TokenTypes.PARAMETERS);
204        int parameterCount = 0;
205        if (params != null) {
206            parameterCount = params.getChildCount(TokenTypes.PARAMETER_DEF);
207        }
208        return parameterCount;
209    }
210
211}