View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8   import com.puppycrawl.tools.checkstyle.api.DetailAST;
9   import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10  
11  /**
12   * Checks that constant, declared as private field of class is used more than
13   * once.
14   * @since 0.3
15   */
16  public final class ConstantUsageCheck extends AbstractCheck {
17  
18      @Override
19      public int[] getDefaultTokens() {
20          return new int[]{
21              TokenTypes.VARIABLE_DEF,
22          };
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          if (ConstantUsageCheck.isField(ast)
38              && ConstantUsageCheck.isFinal(ast)) {
39              final DetailAST namenode = ast.findFirstToken(TokenTypes.IDENT);
40              if (!"serialVersionUID".equals(this.getText(namenode))) {
41                  this.checkField(ast, namenode);
42              }
43          }
44      }
45  
46      /**
47       * Check that constant, declared as private field of class
48       * is used more than ones.
49       * @param ast Node which contains VARIABLE_DEF
50       * @param namenode Node which contains variable name
51       */
52      private void checkField(final DetailAST ast, final DetailAST namenode) {
53          final String name = namenode.getText();
54          final DetailAST objblock = ast.getParent();
55          int counter = 0;
56          final DetailAST classdef = objblock.getParent();
57          if (classdef != null) {
58              final DetailAST mods =
59                  classdef.findFirstToken(TokenTypes.MODIFIERS);
60              if (mods != null) {
61                  counter += this.parseAnnotation(mods, name);
62              }
63          }
64          DetailAST variable = objblock.getFirstChild();
65          while (null != variable) {
66              if (!variable.equals(ast)) {
67                  switch (variable.getType()) {
68                      case TokenTypes.VARIABLE_DEF:
69                          counter += this.parseVarDef(variable, name);
70                          break;
71                      case TokenTypes.CLASS_DEF:
72                          counter += this.parseDef(
73                              variable, name, TokenTypes.OBJBLOCK
74                          );
75                          break;
76                      default:
77                          counter += this.parseDef(variable, name, TokenTypes.SLIST);
78                          break;
79                  }
80              }
81              variable = variable.getNextSibling();
82          }
83          if (counter == 0 && ConstantUsageCheck.isPrivate(ast)) {
84              this.log(
85                  namenode.getLineNo(),
86                  String.format("Private constant \"%s\" is not used", name)
87              );
88          }
89      }
90  
91      /**
92       * Parses the variable definition and increments the counter
93       * if name is found.
94       * @param variable DetailAST of variable definition
95       * @param name Name of constant we search for
96       * @return Zero if not found, 1 otherwise
97       */
98      private int parseVarDef(final DetailAST variable, final String name) {
99          int counter = 0;
100         final DetailAST modifiers =
101             variable.findFirstToken(TokenTypes.MODIFIERS);
102         if (modifiers != null) {
103             counter += this.parseAnnotation(modifiers, name);
104         }
105         final DetailAST assign =
106             variable.findFirstToken(TokenTypes.ASSIGN);
107         if (assign != null) {
108             DetailAST expression =
109                 assign.findFirstToken(TokenTypes.EXPR);
110             if (expression == null) {
111                 expression = assign.findFirstToken(
112                     TokenTypes.ARRAY_INIT
113                 );
114             }
115             final String text = this.getText(expression);
116             if (text.contains(name)) {
117                 ++counter;
118             }
119         }
120         return counter;
121     }
122 
123     /**
124      * Returns text representation of the specified node, including it's
125      * children.
126      * @param node Node, containing text
127      * @return Text representation of the node
128      */
129     private String getText(final DetailAST node) {
130         final String ret;
131         if (node == null) {
132             ret = "";
133         } else if (0 == node.getChildCount()) {
134             ret = node.getText();
135         } else {
136             final StringBuilder result = new StringBuilder();
137             DetailAST child = node.getFirstChild();
138             while (null != child) {
139                 final String text = this.getText(child);
140                 result.append(text);
141                 if (".".equals(node.getText())
142                     && child.getNextSibling() != null) {
143                     result.append(node.getText());
144                 }
145                 child = child.getNextSibling();
146             }
147             ret = result.toString();
148         }
149         return ret;
150     }
151 
152     /**
153      * Returns <code>true</code> if specified node has parent node of type
154      * <code>OBJBLOCK</code>.
155      * @param node Node to check
156      * @return True if parent node is <code>OBJBLOCK</code>, else
157      *  returns <code>false</code>
158      */
159     private static boolean isField(final DetailAST node) {
160         return TokenTypes.OBJBLOCK == node.getParent().getType();
161     }
162 
163     /**
164      * Returns true if specified node has modifiers of type <code>FINAL</code>.
165      * @param node Node to check
166      * @return True if specified node contains modifiers of type
167      *  <code>FINAL</code>, else returns <code>false</code>
168      */
169     private static boolean isFinal(final DetailAST node) {
170         return node.findFirstToken(TokenTypes.MODIFIERS)
171             .getChildCount(TokenTypes.FINAL) > 0;
172     }
173 
174     /**
175      * Returns true if specified node has modifiers of type
176      * <code>PRIVATE</code>.
177      * @param node Node to check
178      * @return True if specified node contains modifiers of type
179      *  <code>PRIVATE</code>, else returns <code>false</code>
180      */
181     private static boolean isPrivate(final DetailAST node) {
182         return node.findFirstToken(TokenTypes.MODIFIERS)
183             .getChildCount(TokenTypes.LITERAL_PRIVATE) > 0;
184     }
185 
186     /**
187      * Parses the body of the definition (either method or inner class) and
188      * increments counter each time when it founds constant name.
189      * @param definition Tree node, containing definition
190      * @param name Constant name to search
191      * @param type Type of definition start
192      * @return Number of found constant usages
193      */
194     private int parseDef(final DetailAST definition, final String name,
195         final int type) {
196         int counter = 0;
197         final DetailAST modifiers =
198             definition.findFirstToken(TokenTypes.MODIFIERS);
199         if (modifiers != null) {
200             counter += this.parseAnnotation(modifiers, name);
201         }
202         final DetailAST opening = definition.findFirstToken(type);
203         if (null != opening) {
204             final DetailAST closing = opening.findFirstToken(TokenTypes.RCURLY);
205             final int start = opening.getLineNo();
206             final int end = closing.getLineNo() - 1;
207             final String[] lines = this.getLines();
208             for (int pos = start; pos < end; pos += 1) {
209                 if (lines[pos].contains(name)) {
210                     counter += 1;
211                 }
212             }
213         }
214         return counter;
215     }
216 
217     /**
218      * Parses the annotation value pair and increments the counter
219      * if name is found.
220      * @param modifiers DetailAST of variable definition
221      * @param name Name of constant we search for
222      * @return Zero if not found, 1 otherwise
223      */
224     private int parseAnnotation(final DetailAST modifiers, final String name) {
225         int counter = 0;
226         final DetailAST variable =
227             modifiers.findFirstToken(TokenTypes.ANNOTATION);
228         if (variable != null) {
229             final String txt = this.getText(variable);
230             if (txt.contains(name)) {
231                 ++counter;
232             }
233         }
234         return counter;
235     }
236 }