1
2
3
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
15
16
17
18
19 public final class QualifyInnerClassCheck extends AbstractCheck {
20
21
22
23
24 private final Set<String> nested;
25
26
27
28
29 private boolean root;
30
31
32
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 }