View Javadoc
1   /*
2    * Copyright (c) 2011-2025 Yegor Bugayenko
3    *
4    * All rights reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions
8    * are met: 1) Redistributions of source code must retain the above
9    * copyright notice, this list of conditions and the following
10   * disclaimer. 2) Redistributions in binary form must reproduce the above
11   * copyright notice, this list of conditions and the following
12   * disclaimer in the documentation and/or other materials provided
13   * with the distribution. 3) Neither the name of the Qulice.com nor
14   * the names of its contributors may be used to endorse or promote
15   * products derived from this software without specific prior written
16   * permission.
17   *
18   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
20   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29   * OF THE POSSIBILITY OF SUCH DAMAGE.
30   */
31  
32  package com.qulice.checkstyle;
33  
34  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
35  import com.puppycrawl.tools.checkstyle.api.DetailAST;
36  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
37  import java.util.LinkedList;
38  import java.util.List;
39  
40  /**
41   * Checks that constructor, declared as private class is used more than once.
42   *
43   * @since 0.3
44   */
45  public final class ProhibitUnusedPrivateConstructorCheck extends AbstractCheck {
46  
47      @Override
48      public int[] getDefaultTokens() {
49          return new int[] {TokenTypes.CLASS_DEF};
50      }
51  
52      @Override
53      public int[] getAcceptableTokens() {
54          return this.getDefaultTokens();
55      }
56  
57      @Override
58      public int[] getRequiredTokens() {
59          return this.getDefaultTokens();
60      }
61  
62      @Override
63      public void visitToken(final DetailAST ast) {
64          final DetailAST objblock = ast.findFirstToken(TokenTypes.OBJBLOCK);
65          if (objblock != null) {
66              this.checkConstructors(objblock);
67          }
68      }
69  
70      /**
71       * Collects all private constructors in a given object block.
72       *
73       * @param objblock Node which contains constructors
74       * @return List of DetailAST nodes representing the private constructors
75       */
76      private static List<DetailAST> collectPrivateConstructors(final DetailAST objblock) {
77          final List<DetailAST> prvctors = new LinkedList<>();
78          final DetailAST firstchld = objblock.getFirstChild();
79          for (DetailAST child = firstchld; child != null; child = child.getNextSibling()) {
80              if (child.getType() == TokenTypes.CTOR_DEF && isPrivate(child)) {
81                  prvctors.add(child);
82              }
83          }
84          return prvctors;
85      }
86  
87      /**
88       * Checks if a private constructor is used in the object block.
89       *
90       * @param privatector Node representing the private constructor
91       * @param objblock Node which contains constructors
92       * @return True if the private constructor is used, False otherwise
93       */
94      private static boolean isPrivateConstructorUsed(
95          final DetailAST privatector, final DetailAST objblock) {
96          return
97              isPrivateCtorUsedInOtherCtors(privatector, objblock)
98              ||
99              isPrivateCtorUsedInMethods(privatector, objblock);
100     }
101 
102     /**
103      * Checks if a private constructor is used in other constructors.
104      *
105      * @param privatector Node representing the private constructor
106      * @param objblock Node containing constructors
107      * @return True if the private constructor is used, False otherwise
108      */
109     private static boolean isPrivateCtorUsedInOtherCtors(
110         final DetailAST privatector, final DetailAST objblock) {
111         final List<DetailAST> allctors = collectAllConstructors(objblock);
112         return allctors.stream()
113             .anyMatch(
114                 otherCtor -> otherCtor != privatector
115                 &&
116                 isCallingConstructor(otherCtor, privatector));
117     }
118 
119     /**
120      * Checks if a private constructor is used in methods of the object block.
121      *
122      * @param privatector Node representing the private constructor
123      * @param objblock Node containing methods
124      * @return True if the private constructor is used, False otherwise
125      */
126     private static boolean isPrivateCtorUsedInMethods(
127         final DetailAST privatector, final DetailAST objblock) {
128         boolean result = false;
129         final DetailAST firstchld = objblock.getFirstChild();
130         for (DetailAST child = firstchld; child != null; child = child.getNextSibling()) {
131             if (child.getType() == TokenTypes.METHOD_DEF
132                 &&
133                 isCallingConstructor(child, privatector)) {
134                 result = true;
135                 break;
136             }
137         }
138         return result;
139     }
140 
141     /**
142      * Collects all constructors in a given object block.
143      *
144      * @param objblock Node which contains constructors
145      * @return List of DetailAST nodes representing all the constructors
146      */
147     private static List<DetailAST> collectAllConstructors(final DetailAST objblock) {
148         final List<DetailAST> allctors = new LinkedList<>();
149         final DetailAST firstchld = objblock.getFirstChild();
150         for (DetailAST child = firstchld; child != null; child = child.getNextSibling()) {
151             if (child.getType() == TokenTypes.CTOR_DEF) {
152                 allctors.add(child);
153             }
154         }
155         return allctors;
156     }
157 
158     /**
159      * Returns true if specified node has modifiers of type
160      * <code>PRIVATE</code>.
161      *
162      * @param node Node to check.
163      * @return True if specified node contains modifiers of type
164      *  <code>PRIVATE</code>, else returns <code>false</code>.
165      */
166     private static boolean isPrivate(final DetailAST node) {
167         final DetailAST modifiers = node.findFirstToken(TokenTypes.MODIFIERS);
168         return modifiers.getChildCount(TokenTypes.LITERAL_PRIVATE) > 0;
169     }
170 
171     private static boolean isCallingConstructor(
172         final DetailAST methodorctor, final DetailAST targetctor) {
173         boolean result = false;
174         final DetailAST body = methodorctor.findFirstToken(TokenTypes.SLIST);
175         if (body != null) {
176             DetailAST stmt = body.getFirstChild();
177             while (stmt != null && !result) {
178                 result = isMatchingConstructorCall(stmt, targetctor);
179                 stmt = stmt.getNextSibling();
180             }
181         }
182         return result;
183     }
184 
185     private static boolean isMatchingConstructorCall(
186         final DetailAST stmt, final DetailAST targetctor) {
187         return
188             stmt.getType() == TokenTypes.CTOR_CALL
189             &&
190             matchesConstructorSignature(stmt, targetctor);
191     }
192 
193     private static boolean matchesConstructorSignature(
194         final DetailAST callexpr, final DetailAST ctor) {
195         final DetailAST callparams = callexpr.findFirstToken(TokenTypes.ELIST);
196         final DetailAST ctorparams = ctor.findFirstToken(TokenTypes.PARAMETERS);
197         return parametersCountMatch(callparams, ctorparams);
198     }
199 
200     private static boolean parametersCountMatch(
201         final DetailAST callparams, final DetailAST ctorparams) {
202         final int ncallparams = callparams.getChildCount(TokenTypes.EXPR);
203         final int nctorparams = ctorparams.getChildCount(TokenTypes.PARAMETER_DEF);
204         return ncallparams == nctorparams;
205     }
206 
207     /**
208      * Checks if private constructors are used.
209      * Logs a message if a private constructor is not used.
210      *
211      * @param objblock Node which contains constructors
212      */
213     private void checkConstructors(final DetailAST objblock) {
214         final List<DetailAST> prvctors = collectPrivateConstructors(objblock);
215         for (final DetailAST ctor : prvctors) {
216             if (!isPrivateConstructorUsed(ctor, objblock)) {
217                 this.log(ctor.getLineNo(), "Unused private constructor.");
218             }
219         }
220     }
221 
222 }