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