1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 import java.util.HashSet;
37 import java.util.Set;
38
39
40
41
42
43
44
45
46
47
48 public final class QualifyInnerClassCheck extends AbstractCheck {
49
50
51
52
53 private final Set<String> nested = new HashSet<>();
54
55
56
57
58 private boolean root;
59
60 @Override
61 public int[] getDefaultTokens() {
62 return new int[]{
63 TokenTypes.CLASS_DEF,
64 TokenTypes.ENUM_DEF,
65 TokenTypes.INTERFACE_DEF,
66 TokenTypes.LITERAL_NEW,
67 };
68 }
69
70 @Override
71 public int[] getAcceptableTokens() {
72 return this.getDefaultTokens();
73 }
74
75 @Override
76 public int[] getRequiredTokens() {
77 return this.getDefaultTokens();
78 }
79
80 @Override
81 public void visitToken(final DetailAST ast) {
82 if (ast.getType() == TokenTypes.CLASS_DEF
83 || ast.getType() == TokenTypes.ENUM_DEF
84 || ast.getType() == TokenTypes.INTERFACE_DEF) {
85 this.scanForNestedClassesIfNecessary(ast);
86 }
87 if (ast.getType() == TokenTypes.LITERAL_NEW) {
88 this.visitNewExpression(ast);
89 }
90 }
91
92
93
94
95
96
97
98
99 private void visitNewExpression(final DetailAST expr) {
100 final DetailAST child = expr.getFirstChild();
101 if (child.getType() == TokenTypes.IDENT) {
102 if (this.nested.contains(child.getText())) {
103 this.log(child, "Static inner class should be qualified with outer class");
104 }
105 } else if (child.getType() != TokenTypes.DOT) {
106 final String message = String.format("unsupported input %d", child.getType());
107 throw new IllegalStateException(message);
108 }
109 }
110
111
112
113
114
115
116
117 private void scanForNestedClassesIfNecessary(final DetailAST node) {
118 if (!this.root) {
119 this.root = true;
120 this.scanClass(node);
121 }
122 }
123
124
125
126
127
128
129
130
131 private void scanClass(final DetailAST node) {
132 this.nested.add(getClassName(node));
133 final DetailAST content = node.findFirstToken(TokenTypes.OBJBLOCK);
134 if (content == null) {
135 return;
136 }
137 for (
138 DetailAST child = content.getFirstChild();
139 child != null;
140 child = child.getNextSibling()
141 ) {
142 if (child.getType() == TokenTypes.CLASS_DEF
143 || child.getType() == TokenTypes.ENUM_DEF
144 || child.getType() == TokenTypes.INTERFACE_DEF) {
145 this.scanClass(child);
146 }
147 }
148 }
149
150
151
152
153
154
155 private static String getClassName(final DetailAST clazz) {
156 for (
157 DetailAST child = clazz.getFirstChild();
158 child != null;
159 child = child.getNextSibling()
160 ) {
161 if (child.getType() == TokenTypes.IDENT) {
162 return child.getText();
163 }
164 }
165 throw new IllegalStateException("unexpected input: can not find class name");
166 }
167 }