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.HashSet;
11  import java.util.Set;
12  
13  /**
14   * Checks if inner classes are properly accessed using their qualified name
15   * with the outer class.
16   *
17   * @since 0.18
18   */
19  public final class QualifyInnerClassCheck extends AbstractCheck {
20  
21      /**
22       * Set of all nested classes.
23       */
24      private final Set<String> nested;
25  
26      /**
27       * Whether we already visited root class of the .java file.
28       */
29      private boolean root;
30  
31      /**
32       * Default constructor.
33       */
34      public QualifyInnerClassCheck() {
35          this.nested = new HashSet<>();
36      }
37  
38      @Override
39      public int[] getDefaultTokens() {
40          return new int[]{
41              TokenTypes.CLASS_DEF,
42              TokenTypes.ENUM_DEF,
43              TokenTypes.INTERFACE_DEF,
44              TokenTypes.LITERAL_NEW,
45          };
46      }
47  
48      @Override
49      public int[] getAcceptableTokens() {
50          return this.getDefaultTokens();
51      }
52  
53      @Override
54      public int[] getRequiredTokens() {
55          return this.getDefaultTokens();
56      }
57  
58      @Override
59      public void beginTree(final DetailAST ast) {
60          this.nested.clear();
61          this.root = false;
62      }
63  
64      @Override
65      public void visitToken(final DetailAST ast) {
66          if (ast.getType() == TokenTypes.CLASS_DEF
67              || ast.getType() == TokenTypes.ENUM_DEF
68              || ast.getType() == TokenTypes.INTERFACE_DEF) {
69              this.scanForNestedClassesIfNecessary(ast);
70          }
71          if (ast.getType() == TokenTypes.LITERAL_NEW) {
72              this.visitNewExpression(ast);
73          }
74      }
75  
76      private void visitNewExpression(final DetailAST expr) {
77          final DetailAST child = expr.getFirstChild();
78          if (child != null
79              && child.getType() == TokenTypes.IDENT
80              && this.nested.contains(child.getText())) {
81              this.log(child, "Static inner class should be qualified with outer class");
82          }
83      }
84  
85      private void scanForNestedClassesIfNecessary(final DetailAST node) {
86          if (!this.root) {
87              this.root = true;
88              this.scanClass(node);
89          }
90      }
91  
92      private void scanClass(final DetailAST node) {
93          final DetailAST content = node.findFirstToken(TokenTypes.OBJBLOCK);
94          if (content == null) {
95              return;
96          }
97          for (
98              DetailAST child = content.getFirstChild();
99              child != null;
100             child = child.getNextSibling()
101         ) {
102             if (child.getType() == TokenTypes.CLASS_DEF
103                 || child.getType() == TokenTypes.ENUM_DEF
104                 || child.getType() == TokenTypes.INTERFACE_DEF) {
105                 this.nested.add(getClassName(child));
106                 this.scanClass(child);
107             }
108         }
109     }
110 
111     private static String getClassName(final DetailAST clazz) {
112         for (
113             DetailAST child = clazz.getFirstChild();
114             child != null;
115             child = child.getNextSibling()
116         ) {
117             if (child.getType() == TokenTypes.IDENT) {
118                 return child.getText();
119             }
120         }
121         throw new IllegalStateException("Unable to find class name");
122     }
123 }