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
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 public final class ConstructorsCodeFreeCheck extends AbstractCheck {
41
42
43
44
45 public ConstructorsCodeFreeCheck() {
46
47 }
48
49 @Override
50 public int[] getDefaultTokens() {
51 return new int[] {TokenTypes.CTOR_DEF};
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 visitToken(final DetailAST ast) {
66 final DetailAST body = ast.findFirstToken(TokenTypes.SLIST);
67 if (body != null && !ConstructorsCodeFreeCheck.isOnlyDelegate(body)) {
68 this.reportCalls(body);
69 }
70 }
71
72 private static boolean isOnlyDelegate(final DetailAST body) {
73 final DetailAST first = body.getFirstChild();
74 final boolean delegate;
75 if (ConstructorsCodeFreeCheck.isDelegate(first)) {
76 final DetailAST next = first.getNextSibling();
77 delegate = next == null || next.getType() == TokenTypes.RCURLY;
78 } else {
79 delegate = false;
80 }
81 return delegate;
82 }
83
84 private static boolean isDelegate(final DetailAST node) {
85 final boolean delegate;
86 if (node == null) {
87 delegate = false;
88 } else {
89 delegate = node.getType() == TokenTypes.CTOR_CALL
90 || node.getType() == TokenTypes.SUPER_CTOR_CALL;
91 }
92 return delegate;
93 }
94
95 private void reportCalls(final DetailAST node) {
96 for (DetailAST child = node.getFirstChild();
97 child != null; child = child.getNextSibling()) {
98 final int type = child.getType();
99 if (type == TokenTypes.LAMBDA || type == TokenTypes.OBJBLOCK) {
100 continue;
101 }
102 if (type == TokenTypes.METHOD_CALL
103 && !ConstructorsCodeFreeCheck.isDefensiveCopy(child)) {
104 this.log(
105 child.getLineNo(),
106 "Constructor must not contain method calls"
107 );
108 }
109 this.reportCalls(child);
110 }
111 }
112
113 private static boolean isDefensiveCopy(final DetailAST call) {
114 final DetailAST dot = call.getFirstChild();
115 final boolean defensive;
116 if (dot == null || dot.getType() != TokenTypes.DOT) {
117 defensive = false;
118 } else {
119 final DetailAST method = dot.getLastChild();
120 defensive = method != null
121 && method.getType() == TokenTypes.IDENT
122 && (
123 ConstructorsCodeFreeCheck.isArraysCopyOf(dot, method)
124 || ConstructorsCodeFreeCheck.isArrayClone(call, method)
125 );
126 }
127 return defensive;
128 }
129
130 private static boolean isArraysCopyOf(
131 final DetailAST dot, final DetailAST method
132 ) {
133 final DetailAST qualifier = dot.getFirstChild();
134 return "copyOf".equals(method.getText())
135 && qualifier != null
136 && ConstructorsCodeFreeCheck.endsWith(qualifier, "Arrays");
137 }
138
139 private static boolean isArrayClone(
140 final DetailAST call, final DetailAST method
141 ) {
142 final DetailAST elist = call.findFirstToken(TokenTypes.ELIST);
143 return "clone".equals(method.getText())
144 && elist != null
145 && elist.getFirstChild() == null;
146 }
147
148 private static boolean endsWith(final DetailAST node, final String name) {
149 final boolean match;
150 if (node.getType() == TokenTypes.IDENT) {
151 match = name.equals(node.getText());
152 } else if (node.getType() == TokenTypes.DOT) {
153 final DetailAST last = node.getLastChild();
154 match = last != null
155 && last.getType() == TokenTypes.IDENT
156 && name.equals(last.getText());
157 } else {
158 match = false;
159 }
160 return match;
161 }
162 }