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      /**
95       * Checks if a private constructor is used in methods of the object block.
96       *
97       * @param privatector Node representing the private constructor
98       * @param objblock Node containing methods
99       * @return True if the private constructor is used, False otherwise
100      */
101     private static boolean isPrivateCtorUsedInMethods(
102         final DetailAST privatector, final DetailAST objblock) {
103         boolean result = false;
104         final DetailAST firstchld = objblock.getFirstChild();
105         for (DetailAST child = firstchld; child != null; child = child.getNextSibling()) {
106             if (child.getType() == TokenTypes.METHOD_DEF
107                 &&
108                 isCallingConstructor(child, privatector)) {
109                 result = true;
110                 break;
111             }
112         }
113         return result;
114     }
115 
116     /**
117      * Collects all constructors in a given object block.
118      *
119      * @param objblock Node which contains constructors
120      * @return List of DetailAST nodes representing all the constructors
121      */
122     private static List<DetailAST> collectAllConstructors(final DetailAST objblock) {
123         final List<DetailAST> allctors = new LinkedList<>();
124         final DetailAST firstchld = objblock.getFirstChild();
125         for (DetailAST child = firstchld; child != null; child = child.getNextSibling()) {
126             if (child.getType() == TokenTypes.CTOR_DEF) {
127                 allctors.add(child);
128             }
129         }
130         return allctors;
131     }
132 
133     /**
134      * Returns true if specified node has modifiers of type
135      * <code>PRIVATE</code>.
136      *
137      * @param node Node to check.
138      * @return True if specified node contains modifiers of type
139      *  <code>PRIVATE</code>, else returns <code>false</code>.
140      */
141     private static boolean isPrivate(final DetailAST node) {
142         final DetailAST modifiers = node.findFirstToken(TokenTypes.MODIFIERS);
143         return modifiers.getChildCount(TokenTypes.LITERAL_PRIVATE) > 0;
144     }
145 
146     private static boolean isCallingConstructor(
147         final DetailAST methodorctor, final DetailAST targetctor) {
148         boolean result = false;
149         final DetailAST body = methodorctor.findFirstToken(TokenTypes.SLIST);
150         if (body != null) {
151             DetailAST stmt = body.getFirstChild();
152             while (stmt != null && !result) {
153                 result = isMatchingConstructorCall(stmt, targetctor);
154                 stmt = stmt.getNextSibling();
155             }
156         }
157         return result;
158     }
159 
160     private static boolean isMatchingConstructorCall(
161         final DetailAST stmt, final DetailAST targetctor) {
162         return
163             stmt.getType() == TokenTypes.CTOR_CALL
164             &&
165             matchesConstructorSignature(stmt, targetctor);
166     }
167 
168     private static boolean matchesConstructorSignature(
169         final DetailAST callexpr, final DetailAST ctor) {
170         final DetailAST callparams = callexpr.findFirstToken(TokenTypes.ELIST);
171         final DetailAST ctorparams = ctor.findFirstToken(TokenTypes.PARAMETERS);
172         return parametersCountMatch(callparams, ctorparams);
173     }
174 
175     private static boolean parametersCountMatch(
176         final DetailAST callparams, final DetailAST ctorparams) {
177         final int ncallparams = callparams.getChildCount(TokenTypes.EXPR);
178         final int nctorparams = ctorparams.getChildCount(TokenTypes.PARAMETER_DEF);
179         return ncallparams == nctorparams;
180     }
181 
182     /**
183      * Checks if private constructors are used.
184      * Logs a message if a private constructor is not used.
185      *
186      * @param objblock Node which contains constructors
187      */
188     private void checkConstructors(final DetailAST objblock) {
189         final List<DetailAST> prvctors = collectPrivateConstructors(objblock);
190         for (final DetailAST ctor : prvctors) {
191             if (!isPrivateConstructorUsed(ctor, objblock)) {
192                 this.log(ctor.getLineNo(), "Unused private constructor.");
193             }
194         }
195     }
196 
197 }