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.ArrayList;
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      /**
22       * Default constructor.
23       */
24      public ProhibitUnusedPrivateConstructorCheck() {
25          // nothing to initialize
26      }
27  
28      @Override
29      public int[] getDefaultTokens() {
30          return new int[] {TokenTypes.CLASS_DEF};
31      }
32  
33      @Override
34      public int[] getAcceptableTokens() {
35          return this.getDefaultTokens();
36      }
37  
38      @Override
39      public int[] getRequiredTokens() {
40          return this.getDefaultTokens();
41      }
42  
43      @Override
44      public void visitToken(final DetailAST ast) {
45          final DetailAST objblock = ast.findFirstToken(TokenTypes.OBJBLOCK);
46          if (objblock != null) {
47              this.checkConstructors(objblock);
48          }
49      }
50  
51      private static List<DetailAST> collectPrivateConstructors(final DetailAST objblock) {
52          final List<DetailAST> prvctors = new ArrayList<>(0);
53          final DetailAST firstchld = objblock.getFirstChild();
54          for (DetailAST child = firstchld; child != null; child = child.getNextSibling()) {
55              if (child.getType() == TokenTypes.CTOR_DEF && isPrivate(child)) {
56                  prvctors.add(child);
57              }
58          }
59          return prvctors;
60      }
61  
62      private static boolean isPrivateConstructorUsed(
63          final DetailAST privatector, final DetailAST objblock) {
64          return
65              isPrivateCtorUsedInOtherCtors(privatector, objblock)
66              ||
67              isPrivateCtorUsedInMethods(privatector, objblock);
68      }
69  
70      private static boolean isPrivateCtorUsedInOtherCtors(
71          final DetailAST privatector, final DetailAST objblock) {
72          return collectAllConstructors(objblock).stream().anyMatch(
73              otherCtor -> !otherCtor.equals(privatector)
74              &&
75              isCallingConstructor(otherCtor, privatector)
76          );
77      }
78  
79      private static boolean isPrivateCtorUsedInMethods(
80          final DetailAST privatector, final DetailAST objblock) {
81          boolean result = false;
82          final DetailAST firstchld = objblock.getFirstChild();
83          for (DetailAST child = firstchld; child != null; child = child.getNextSibling()) {
84              if (child.getType() == TokenTypes.METHOD_DEF
85                  &&
86                  isCallingConstructor(child, privatector)) {
87                  result = true;
88                  break;
89              }
90          }
91          return result;
92      }
93  
94      private static List<DetailAST> collectAllConstructors(final DetailAST objblock) {
95          final List<DetailAST> allctors = new ArrayList<>(0);
96          final DetailAST firstchld = objblock.getFirstChild();
97          for (DetailAST child = firstchld; child != null; child = child.getNextSibling()) {
98              if (child.getType() == TokenTypes.CTOR_DEF) {
99                  allctors.add(child);
100             }
101         }
102         return allctors;
103     }
104 
105     private static boolean isPrivate(final DetailAST node) {
106         return node.findFirstToken(TokenTypes.MODIFIERS)
107             .getChildCount(TokenTypes.LITERAL_PRIVATE) > 0;
108     }
109 
110     private static boolean isCallingConstructor(
111         final DetailAST methodorctor, final DetailAST targetctor) {
112         boolean result = false;
113         final DetailAST body = methodorctor.findFirstToken(TokenTypes.SLIST);
114         if (body != null) {
115             DetailAST stmt = body.getFirstChild();
116             while (stmt != null && !result) {
117                 result = isMatchingConstructorCall(stmt, targetctor);
118                 stmt = stmt.getNextSibling();
119             }
120         }
121         return result;
122     }
123 
124     private static boolean isMatchingConstructorCall(
125         final DetailAST stmt, final DetailAST targetctor) {
126         return
127             stmt.getType() == TokenTypes.CTOR_CALL
128             &&
129             matchesConstructorSignature(stmt, targetctor);
130     }
131 
132     private static boolean matchesConstructorSignature(
133         final DetailAST callexpr, final DetailAST ctor) {
134         return parametersCountMatch(
135             callexpr.findFirstToken(TokenTypes.ELIST),
136             ctor.findFirstToken(TokenTypes.PARAMETERS)
137         );
138     }
139 
140     private static boolean parametersCountMatch(
141         final DetailAST callparams, final DetailAST ctorparams) {
142         return callparams.getChildCount(TokenTypes.EXPR) == ctorparams
143             .getChildCount(TokenTypes.PARAMETER_DEF);
144     }
145 
146     private void checkConstructors(final DetailAST objblock) {
147         final List<DetailAST> prvctors = collectPrivateConstructors(objblock);
148         for (final DetailAST ctor : prvctors) {
149             if (!isPrivateConstructorUsed(ctor, objblock)) {
150                 this.log(ctor.getLineNo(), "Unused private constructor.");
151             }
152         }
153     }
154 }