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