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