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