View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8   import com.puppycrawl.tools.checkstyle.api.DetailAST;
9   import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10  import java.util.LinkedList;
11  import java.util.List;
12  
13  /**
14   * Checks the order of constructor declarations.
15   *
16   * <p>A primary constructor is the one that does the real initialization
17   * work and does not delegate to another constructor via {@code this(...)}.
18   * A secondary constructor delegates, as its first statement, to another
19   * constructor in the same class. The rule requires the primary constructor
20   * to be declared after all secondary ones, so that the delegation chain
21   * reads top-down towards the primary.
22   *
23   * @since 0.24
24   */
25  public final class ConstructorsOrderCheck extends AbstractCheck {
26  
27      @Override
28      public int[] getDefaultTokens() {
29          return new int[] {
30              TokenTypes.CLASS_DEF,
31              TokenTypes.ENUM_DEF,
32              TokenTypes.RECORD_DEF,
33          };
34      }
35  
36      @Override
37      public int[] getAcceptableTokens() {
38          return this.getDefaultTokens();
39      }
40  
41      @Override
42      public int[] getRequiredTokens() {
43          return this.getDefaultTokens();
44      }
45  
46      @Override
47      public void visitToken(final DetailAST ast) {
48          final DetailAST obj = ast.findFirstToken(TokenTypes.OBJBLOCK);
49          if (obj != null) {
50              this.checkOrder(obj);
51          }
52      }
53  
54      /**
55       * Checks that every secondary constructor is declared before
56       * the primary one.
57       * @param obj Object block node
58       */
59      private void checkOrder(final DetailAST obj) {
60          boolean primary = false;
61          for (final DetailAST ctor : ConstructorsOrderCheck.constructors(obj)) {
62              if (primary) {
63                  this.log(
64                      ctor.getLineNo(),
65                      "Primary constructor must be the last one declared"
66                  );
67              } else if (!ConstructorsOrderCheck.delegates(ctor)) {
68                  primary = true;
69              }
70          }
71      }
72  
73      /**
74       * Collects all constructors declared directly in the given
75       * object block, in declaration order.
76       * @param obj Object block node
77       * @return Constructors
78       */
79      private static List<DetailAST> constructors(final DetailAST obj) {
80          final List<DetailAST> ctors = new LinkedList<>();
81          for (DetailAST child = obj.getFirstChild();
82              child != null; child = child.getNextSibling()) {
83              if (child.getType() == TokenTypes.CTOR_DEF) {
84                  ctors.add(child);
85              }
86          }
87          return ctors;
88      }
89  
90      /**
91       * Tells whether the given constructor delegates to another
92       * constructor via {@code this(...)}.
93       * @param ctor Constructor node
94       * @return True if delegating
95       */
96      private static boolean delegates(final DetailAST ctor) {
97          boolean delegates = false;
98          final DetailAST body = ctor.findFirstToken(TokenTypes.SLIST);
99          if (body != null) {
100             for (DetailAST stmt = body.getFirstChild();
101                 stmt != null; stmt = stmt.getNextSibling()) {
102                 if (stmt.getType() == TokenTypes.CTOR_CALL) {
103                     delegates = true;
104                     break;
105                 }
106             }
107         }
108         return delegates;
109     }
110 }