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