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   *
15   * @since 0.3
16   */
17  public final class ConstantUsageCheck extends AbstractCheck {
18  
19      /**
20       * Default constructor.
21       */
22      public ConstantUsageCheck() {
23          // nothing to initialize
24      }
25  
26      @Override
27      public int[] getDefaultTokens() {
28          return new int[]{
29              TokenTypes.VARIABLE_DEF,
30          };
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          if (ConstantUsageCheck.isField(ast)
46              && ConstantUsageCheck.isFinal(ast)) {
47              final DetailAST namenode = ast.findFirstToken(TokenTypes.IDENT);
48              if (!"serialVersionUID".equals(this.getText(namenode))) {
49                  this.checkField(ast, namenode);
50              }
51          }
52      }
53  
54      private void checkField(final DetailAST ast, final DetailAST namenode) {
55          final String name = namenode.getText();
56          final DetailAST objblock = ast.getParent();
57          int counter = 0;
58          final DetailAST classdef = objblock.getParent();
59          if (classdef != null) {
60              final DetailAST mods =
61                  classdef.findFirstToken(TokenTypes.MODIFIERS);
62              if (mods != null) {
63                  counter += this.parseAnnotation(mods, name);
64              }
65          }
66          DetailAST variable = objblock.getFirstChild();
67          while (null != variable) {
68              if (!variable.equals(ast)) {
69                  counter += switch (variable.getType()) {
70                      case TokenTypes.VARIABLE_DEF -> this.parseVarDef(variable, name);
71                      case TokenTypes.CLASS_DEF -> this.parseDef(
72                          variable, name, TokenTypes.OBJBLOCK
73                      );
74                      default -> this.parseDef(variable, name, TokenTypes.SLIST);
75                  };
76              }
77              variable = variable.getNextSibling();
78          }
79          if (counter == 0 && ConstantUsageCheck.isPrivate(ast)) {
80              this.log(
81                  namenode.getLineNo(),
82                  String.format("Private constant \"%s\" is not used", name)
83              );
84          }
85      }
86  
87      private int parseVarDef(final DetailAST variable, final String name) {
88          int counter = 0;
89          final DetailAST modifiers =
90              variable.findFirstToken(TokenTypes.MODIFIERS);
91          if (modifiers != null) {
92              counter += this.parseAnnotation(modifiers, name);
93          }
94          final DetailAST assign =
95              variable.findFirstToken(TokenTypes.ASSIGN);
96          if (assign != null) {
97              DetailAST expression =
98                  assign.findFirstToken(TokenTypes.EXPR);
99              if (expression == null) {
100                 expression = assign.findFirstToken(
101                     TokenTypes.ARRAY_INIT
102                 );
103             }
104             final String text = this.getText(expression);
105             if (text.contains(name)) {
106                 ++counter;
107             }
108         }
109         return counter;
110     }
111 
112     private String getText(final DetailAST node) {
113         final String ret;
114         if (node == null) {
115             ret = "";
116         } else if (0 == node.getChildCount()) {
117             ret = node.getText();
118         } else {
119             final StringBuilder result = new StringBuilder();
120             DetailAST child = node.getFirstChild();
121             while (null != child) {
122                 final String text = this.getText(child);
123                 result.append(text);
124                 if (".".equals(node.getText())
125                     && child.getNextSibling() != null) {
126                     result.append(node.getText());
127                 }
128                 child = child.getNextSibling();
129             }
130             ret = result.toString();
131         }
132         return ret;
133     }
134 
135     private static boolean isField(final DetailAST node) {
136         return TokenTypes.OBJBLOCK == node.getParent().getType();
137     }
138 
139     private static boolean isFinal(final DetailAST node) {
140         return node.findFirstToken(TokenTypes.MODIFIERS)
141             .getChildCount(TokenTypes.FINAL) > 0;
142     }
143 
144     private static boolean isPrivate(final DetailAST node) {
145         return node.findFirstToken(TokenTypes.MODIFIERS)
146             .getChildCount(TokenTypes.LITERAL_PRIVATE) > 0;
147     }
148 
149     private int parseDef(final DetailAST definition, final String name,
150         final int type) {
151         int counter = 0;
152         final DetailAST modifiers =
153             definition.findFirstToken(TokenTypes.MODIFIERS);
154         if (modifiers != null) {
155             counter += this.parseAnnotation(modifiers, name);
156         }
157         final DetailAST opening = definition.findFirstToken(type);
158         if (null != opening) {
159             final DetailAST closing = opening.findFirstToken(TokenTypes.RCURLY);
160             final int start = opening.getLineNo();
161             final int end = closing.getLineNo() - 1;
162             final String[] lines = this.getLines();
163             for (int pos = start; pos < end; pos += 1) {
164                 if (lines[pos].contains(name)) {
165                     counter += 1;
166                 }
167             }
168         }
169         return counter;
170     }
171 
172     private int parseAnnotation(final DetailAST modifiers, final String name) {
173         int counter = 0;
174         final DetailAST variable =
175             modifiers.findFirstToken(TokenTypes.ANNOTATION);
176         if (variable != null) {
177             final String txt = this.getText(variable);
178             if (txt.contains(name)) {
179                 ++counter;
180             }
181         }
182         return counter;
183     }
184 }