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