View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   
6   package com.qulice.checkstyle;
7   
8   import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
9   import com.puppycrawl.tools.checkstyle.api.DetailAST;
10  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
11  import java.util.LinkedList;
12  import java.util.List;
13  
14  /**
15   * Checks that constructor, declared as private class is used more than once.
16   *
17   * @since 0.3
18   */
19  public final class ProhibitUnusedPrivateConstructorCheck extends AbstractCheck {
20  
21      @Override
22      public int[] getDefaultTokens() {
23          return new int[] {TokenTypes.CLASS_DEF};
24      }
25  
26      @Override
27      public int[] getAcceptableTokens() {
28          return this.getDefaultTokens();
29      }
30  
31      @Override
32      public int[] getRequiredTokens() {
33          return this.getDefaultTokens();
34      }
35  
36      @Override
37      public void visitToken(final DetailAST ast) {
38          final DetailAST objblock = ast.findFirstToken(TokenTypes.OBJBLOCK);
39          if (objblock != null) {
40              this.checkConstructors(objblock);
41          }
42      }
43  
44      /**
45       * Collects all private constructors in a given object block.
46       *
47       * @param objblock Node which contains constructors
48       * @return List of DetailAST nodes representing the private constructors
49       */
50      private static List<DetailAST> collectPrivateConstructors(final DetailAST objblock) {
51          final List<DetailAST> prvctors = new LinkedList<>();
52          final DetailAST firstchld = objblock.getFirstChild();
53          for (DetailAST child = firstchld; child != null; child = child.getNextSibling()) {
54              if (child.getType() == TokenTypes.CTOR_DEF && isPrivate(child)) {
55                  prvctors.add(child);
56              }
57          }
58          return prvctors;
59      }
60  
61      /**
62       * Checks if a private constructor is used in the object block.
63       *
64       * @param privatector Node representing the private constructor
65       * @param objblock Node which contains constructors
66       * @return True if the private constructor is used, False otherwise
67       */
68      private static boolean isPrivateConstructorUsed(
69          final DetailAST privatector, final DetailAST objblock) {
70          return
71              isPrivateCtorUsedInOtherCtors(privatector, objblock)
72              ||
73              isPrivateCtorUsedInMethods(privatector, objblock);
74      }
75  
76      /**
77       * Checks if a private constructor is used in other constructors.
78       *
79       * @param privatector Node representing the private constructor
80       * @param objblock Node containing constructors
81       * @return True if the private constructor is used, False otherwise
82       */
83      private static boolean isPrivateCtorUsedInOtherCtors(
84          final DetailAST privatector, final DetailAST objblock) {
85          final List<DetailAST> allctors = collectAllConstructors(objblock);
86          return allctors.stream()
87              .anyMatch(
88                  otherCtor -> otherCtor != privatector
89                  &&
90                  isCallingConstructor(otherCtor, privatector));
91      }
92  
93      /**
94       * Checks if a private constructor is used in methods of the object block.
95       *
96       * @param privatector Node representing the private constructor
97       * @param objblock Node containing methods
98       * @return True if the private constructor is used, False otherwise
99       */
100     private static boolean isPrivateCtorUsedInMethods(
101         final DetailAST privatector, final DetailAST objblock) {
102         boolean result = false;
103         final DetailAST firstchld = objblock.getFirstChild();
104         for (DetailAST child = firstchld; child != null; child = child.getNextSibling()) {
105             if (child.getType() == TokenTypes.METHOD_DEF
106                 &&
107                 isCallingConstructor(child, privatector)) {
108                 result = true;
109                 break;
110             }
111         }
112         return result;
113     }
114 
115     /**
116      * Collects all constructors in a given object block.
117      *
118      * @param objblock Node which contains constructors
119      * @return List of DetailAST nodes representing all the constructors
120      */
121     private static List<DetailAST> collectAllConstructors(final DetailAST objblock) {
122         final List<DetailAST> allctors = new LinkedList<>();
123         final DetailAST firstchld = objblock.getFirstChild();
124         for (DetailAST child = firstchld; child != null; child = child.getNextSibling()) {
125             if (child.getType() == TokenTypes.CTOR_DEF) {
126                 allctors.add(child);
127             }
128         }
129         return allctors;
130     }
131 
132     /**
133      * Returns true if specified node has modifiers of type
134      * <code>PRIVATE</code>.
135      *
136      * @param node Node to check.
137      * @return True if specified node contains modifiers of type
138      *  <code>PRIVATE</code>, else returns <code>false</code>.
139      */
140     private static boolean isPrivate(final DetailAST node) {
141         final DetailAST modifiers = node.findFirstToken(TokenTypes.MODIFIERS);
142         return modifiers.getChildCount(TokenTypes.LITERAL_PRIVATE) > 0;
143     }
144 
145     private static boolean isCallingConstructor(
146         final DetailAST methodorctor, final DetailAST targetctor) {
147         boolean result = false;
148         final DetailAST body = methodorctor.findFirstToken(TokenTypes.SLIST);
149         if (body != null) {
150             DetailAST stmt = body.getFirstChild();
151             while (stmt != null && !result) {
152                 result = isMatchingConstructorCall(stmt, targetctor);
153                 stmt = stmt.getNextSibling();
154             }
155         }
156         return result;
157     }
158 
159     private static boolean isMatchingConstructorCall(
160         final DetailAST stmt, final DetailAST targetctor) {
161         return
162             stmt.getType() == TokenTypes.CTOR_CALL
163             &&
164             matchesConstructorSignature(stmt, targetctor);
165     }
166 
167     private static boolean matchesConstructorSignature(
168         final DetailAST callexpr, final DetailAST ctor) {
169         final DetailAST callparams = callexpr.findFirstToken(TokenTypes.ELIST);
170         final DetailAST ctorparams = ctor.findFirstToken(TokenTypes.PARAMETERS);
171         return parametersCountMatch(callparams, ctorparams);
172     }
173 
174     private static boolean parametersCountMatch(
175         final DetailAST callparams, final DetailAST ctorparams) {
176         final int ncallparams = callparams.getChildCount(TokenTypes.EXPR);
177         final int nctorparams = ctorparams.getChildCount(TokenTypes.PARAMETER_DEF);
178         return ncallparams == nctorparams;
179     }
180 
181     /**
182      * Checks if private constructors are used.
183      * Logs a message if a private constructor is not used.
184      *
185      * @param objblock Node which contains constructors
186      */
187     private void checkConstructors(final DetailAST objblock) {
188         final List<DetailAST> prvctors = collectPrivateConstructors(objblock);
189         for (final DetailAST ctor : prvctors) {
190             if (!isPrivateConstructorUsed(ctor, objblock)) {
191                 this.log(ctor.getLineNo(), "Unused private constructor.");
192             }
193         }
194     }
195 
196 }