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