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
20
21
22 public final class QualifyInnerClassCheck extends AbstractCheck {
23
24
25
26
27 private final Set<String> nested = new HashSet<>();
28
29
30
31
32 private boolean root;
33
34 @Override
35 public int[] getDefaultTokens() {
36 return new int[]{
37 TokenTypes.CLASS_DEF,
38 TokenTypes.ENUM_DEF,
39 TokenTypes.INTERFACE_DEF,
40 TokenTypes.LITERAL_NEW,
41 };
42 }
43
44 @Override
45 public int[] getAcceptableTokens() {
46 return this.getDefaultTokens();
47 }
48
49 @Override
50 public int[] getRequiredTokens() {
51 return this.getDefaultTokens();
52 }
53
54 @Override
55 public void visitToken(final DetailAST ast) {
56 if (ast.getType() == TokenTypes.CLASS_DEF
57 || ast.getType() == TokenTypes.ENUM_DEF
58 || ast.getType() == TokenTypes.INTERFACE_DEF) {
59 this.scanForNestedClassesIfNecessary(ast);
60 }
61 if (ast.getType() == TokenTypes.LITERAL_NEW) {
62 this.visitNewExpression(ast);
63 }
64 }
65
66
67
68
69
70
71
72
73 private void visitNewExpression(final DetailAST expr) {
74 final DetailAST child = expr.getFirstChild();
75 if (child.getType() == TokenTypes.IDENT) {
76 if (this.nested.contains(child.getText())) {
77 this.log(child, "Static inner class should be qualified with outer class");
78 }
79 } else if (child.getType() != TokenTypes.DOT) {
80 final String message = String.format("unsupported input %d", child.getType());
81 throw new IllegalStateException(message);
82 }
83 }
84
85
86
87
88
89
90
91 private void scanForNestedClassesIfNecessary(final DetailAST node) {
92 if (!this.root) {
93 this.root = true;
94 this.scanClass(node);
95 }
96 }
97
98
99
100
101
102
103
104
105 private void scanClass(final DetailAST node) {
106 this.nested.add(getClassName(node));
107 final DetailAST content = node.findFirstToken(TokenTypes.OBJBLOCK);
108 if (content == null) {
109 return;
110 }
111 for (
112 DetailAST child = content.getFirstChild();
113 child != null;
114 child = child.getNextSibling()
115 ) {
116 if (child.getType() == TokenTypes.CLASS_DEF
117 || child.getType() == TokenTypes.ENUM_DEF
118 || child.getType() == TokenTypes.INTERFACE_DEF) {
119 this.scanClass(child);
120 }
121 }
122 }
123
124
125
126
127
128
129 private static String getClassName(final DetailAST clazz) {
130 for (
131 DetailAST child = clazz.getFirstChild();
132 child != null;
133 child = child.getNextSibling()
134 ) {
135 if (child.getType() == TokenTypes.IDENT) {
136 return child.getText();
137 }
138 }
139 throw new IllegalStateException("unexpected input: can not find class name");
140 }
141 }