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  import java.util.regex.Matcher;
11  import java.util.regex.Pattern;
12  
13  /**
14   * Prohibits the redundant {@code java.lang.} prefix in front of a simple
15   * class name.
16   *
17   * <p>Classes from the {@code java.lang} package are imported implicitly,
18   * so qualifying them with their package name adds nothing. A field whose
19   * type is written with the {@code java.lang.} prefix should instead be
20   * declared as {@code private final String name}, in both code and Javadoc.</p>
21   *
22   * <p>Unlike a plain line-based regular expression, this check ignores the
23   * prefix when it appears inside a string literal or a text block. A string
24   * constant that happens to contain the {@code java.lang.} substring is data,
25   * not a type reference, and must not be reported. See
26   * <a href="https://github.com/yegor256/qulice/issues/1703">#1703</a>.</p>
27   *
28   * @since 0.24
29   */
30  public final class UnnecessaryJavaLangCheck extends AbstractCheck {
31  
32      /**
33       * Matches the redundant prefix in front of a class name (a capital
34       * letter). Sub-packages such as {@code java.lang.reflect} start with a
35       * lower-case letter and are intentionally left alone.
36       */
37      private static final Pattern PREFIX =
38          Pattern.compile("\\bjava\\.lang\\.[A-Z]");
39  
40      /**
41       * Mutable copy of the file lines, with the contents of string literals
42       * and text blocks blanked out so the pattern cannot match inside them.
43       */
44      private char[][] lines;
45  
46      /**
47       * Default constructor.
48       */
49      public UnnecessaryJavaLangCheck() {
50          // nothing to initialize
51      }
52  
53      @Override
54      public int[] getDefaultTokens() {
55          return new int[] {
56              TokenTypes.STRING_LITERAL,
57              TokenTypes.TEXT_BLOCK_CONTENT,
58          };
59      }
60  
61      @Override
62      public int[] getAcceptableTokens() {
63          return this.getDefaultTokens();
64      }
65  
66      @Override
67      public int[] getRequiredTokens() {
68          return new int[0];
69      }
70  
71      @Override
72      public void beginTree(final DetailAST root) {
73          final String[] source = this.getLines();
74          this.lines = new char[source.length][];
75          for (int pos = 0; pos < source.length; ++pos) {
76              this.lines[pos] = source[pos].toCharArray();
77          }
78      }
79  
80      @Override
81      public void visitToken(final DetailAST ast) {
82          if (ast.getType() == TokenTypes.STRING_LITERAL) {
83              this.blank(
84                  ast.getLineNo(), ast.getColumnNo(), ast.getText().length()
85              );
86          } else {
87              this.blankBlock(ast);
88          }
89      }
90  
91      @Override
92      public void finishTree(final DetailAST root) {
93          for (int pos = 0; pos < this.lines.length; ++pos) {
94              final Matcher matcher =
95                  UnnecessaryJavaLangCheck.PREFIX.matcher(
96                      String.valueOf(this.lines[pos])
97                  );
98              while (matcher.find()) {
99                  this.log(
100                     pos + 1,
101                     "Unnecessary java.lang. prefix, use the simple class name"
102                 );
103             }
104         }
105     }
106 
107     private void blankBlock(final DetailAST ast) {
108         final String text = ast.getText();
109         int span = 0;
110         for (int pos = 0; pos < text.length(); ++pos) {
111             if (text.charAt(pos) == '\n') {
112                 ++span;
113             }
114         }
115         final int first = ast.getLineNo();
116         for (int line = first; line <= first + span; ++line) {
117             final int index = line - 1;
118             if (index >= 0 && index < this.lines.length) {
119                 this.blank(line, 0, this.lines[index].length);
120             }
121         }
122     }
123 
124     private void blank(final int line, final int column, final int length) {
125         final int index = line - 1;
126         if (index >= 0 && index < this.lines.length) {
127             final char[] chars = this.lines[index];
128             final int from = Math.max(0, Math.min(column, chars.length));
129             final int upto = Math.min(column + length, chars.length);
130             for (int pos = from; pos < upto; ++pos) {
131                 chars[pos] = ' ';
132             }
133         }
134     }
135 }