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