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.ArrayDeque;
11 import java.util.Deque;
12 import java.util.HashSet;
13 import java.util.Set;
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 public final class StaticAccessViaInstanceCheck extends AbstractCheck {
31
32
33
34
35 private final Deque<Set<String>> scopes = new ArrayDeque<>();
36
37 @Override
38 public int[] getDefaultTokens() {
39 return new int[] {
40 TokenTypes.CLASS_DEF,
41 TokenTypes.ENUM_DEF,
42 TokenTypes.INTERFACE_DEF,
43 TokenTypes.DOT,
44 };
45 }
46
47 @Override
48 public int[] getAcceptableTokens() {
49 return this.getDefaultTokens();
50 }
51
52 @Override
53 public int[] getRequiredTokens() {
54 return this.getDefaultTokens();
55 }
56
57 @Override
58 public void beginTree(final DetailAST root) {
59 this.scopes.clear();
60 }
61
62 @Override
63 public void visitToken(final DetailAST ast) {
64 final int type = ast.getType();
65 if (type == TokenTypes.DOT) {
66 this.checkDot(ast);
67 } else {
68 this.scopes.push(collectStatic(ast));
69 }
70 }
71
72 @Override
73 public void leaveToken(final DetailAST ast) {
74 if (ast.getType() != TokenTypes.DOT) {
75 this.scopes.pop();
76 }
77 }
78
79
80
81
82
83
84 private void checkDot(final DetailAST dot) {
85 final DetailAST left = dot.getFirstChild();
86 if (!this.scopes.isEmpty()
87 && left != null
88 && left.getType() == TokenTypes.LITERAL_THIS
89 && isStaticIdent(left.getNextSibling(), this.scopes.peek())) {
90 this.log(
91 dot,
92 "Static member must be accessed via class name, not via instance"
93 );
94 }
95 }
96
97
98
99
100
101
102
103
104 private static boolean isStaticIdent(
105 final DetailAST node, final Set<String> names) {
106 return node != null
107 && node.getType() == TokenTypes.IDENT
108 && names.contains(node.getText());
109 }
110
111
112
113
114
115
116
117 private static Set<String> collectStatic(final DetailAST clazz) {
118 final Set<String> names = new HashSet<>(0);
119 final DetailAST body = clazz.findFirstToken(TokenTypes.OBJBLOCK);
120 for (DetailAST child = body.getFirstChild();
121 child != null; child = child.getNextSibling()) {
122 if (isStaticMember(child)) {
123 names.add(child.findFirstToken(TokenTypes.IDENT).getText());
124 }
125 }
126 return names;
127 }
128
129
130
131
132
133
134
135 private static boolean isStaticMember(final DetailAST node) {
136 final int type = node.getType();
137 final boolean member = type == TokenTypes.METHOD_DEF
138 || type == TokenTypes.VARIABLE_DEF;
139 boolean result = false;
140 if (member) {
141 final DetailAST modifiers =
142 node.findFirstToken(TokenTypes.MODIFIERS);
143 result = modifiers != null
144 && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
145 }
146 return result;
147 }
148 }