Skip to content

Commit 518c1f2

Browse files
committed
1 parent 8891fb7 commit 518c1f2

35 files changed

Lines changed: 973 additions & 422 deletions

checkstyle/checkstyle.xml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,39 @@
194194

195195
<!-- Usage of Ignite abbrevations check. -->
196196
<module name="org.apache.ignite.tools.checkstyle.IgniteAbbrevationsRule"/>
197+
198+
<module name="org.apache.ignite.tools.checkstyle.ClassUsageRestrictionRule">
199+
<property name="className" value="java.util.concurrent.ForkJoinPool"/>
200+
<property name="factoryMethods" value="commonPool"/>
201+
<property name="substitutionClassName" value="org.apache.ignite.internal.thread.pool.IgniteForkJoinPool"/>
202+
</module>
203+
204+
<module name="org.apache.ignite.tools.checkstyle.ClassUsageRestrictionRule">
205+
<property name="className" value="java.util.concurrent.Executors"/>
206+
<property name="factoryMethods" value="newFixedThreadPool, newWorkStealingPool, newSingleThreadExecutor, newCachedThreadPool, unconfigurableExecutorService"/>
207+
<property name="substitutionClassName" value="org.apache.ignite.internal.thread.IgniteThreadPoolExecutor"/>
208+
</module>
209+
210+
<module name="org.apache.ignite.tools.checkstyle.ClassUsageRestrictionRule">
211+
<property name="className" value="java.util.concurrent.Executors"/>
212+
<property name="factoryMethods" value="newSingleThreadScheduledExecutor, newScheduledThreadPool, , unconfigurableScheduledExecutorService"/>
213+
<property name="substitutionClassName" value="org.apache.ignite.internal.thread.pool.IgniteScheduledThreadPoolExecutor"/>
214+
</module>
215+
216+
<module name="org.apache.ignite.tools.checkstyle.ClassUsageRestrictionRule">
217+
<property name="className" value="java.util.concurrent.ThreadPoolExecutor"/>
218+
<property name="substitutionClassName" value="org.apache.ignite.internal.thread.IgniteThreadPoolExecutor"/>
219+
</module>
220+
221+
<module name="org.apache.ignite.tools.checkstyle.ClassUsageRestrictionRule">
222+
<property name="className" value="java.util.concurrent.ScheduledThreadPoolExecutor"/>
223+
<property name="substitutionClassName" value="org.apache.ignite.internal.thread.pool.IgniteScheduledThreadPoolExecutor"/>
224+
</module>
225+
226+
<module name="SuppressionXpathSingleFilter">
227+
<property name="checks" value="org.apache.ignite.tools.checkstyle.ClassUsageRestrictionRule"/>
228+
<property name="files" value="DumpReader\.java|CacheLoadOnlyStoreAdapter\.java|[\\/]io[\\/]opencensus[\\/]|[\\/]jdbc[\\/]thin[\\/]|[\\/]test[\\/]|[\\/]tests[\\/]|[\\/]internal[\\/]client[\\/]|[\\/]org[\\/]apache[\\/]ignite[\\/]internal[\\/]thread[\\/]"/>
229+
</module>
197230
</module>
198231

199232
<!--
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.ignite.tools.checkstyle;
19+
20+
import java.util.Arrays;
21+
import java.util.Collections;
22+
import java.util.HashSet;
23+
import java.util.Objects;
24+
import java.util.Set;
25+
import java.util.stream.Collectors;
26+
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27+
import com.puppycrawl.tools.checkstyle.api.DetailAST;
28+
29+
import static com.puppycrawl.tools.checkstyle.api.FullIdent.createFullIdent;
30+
import static com.puppycrawl.tools.checkstyle.api.TokenTypes.EXTENDS_CLAUSE;
31+
import static com.puppycrawl.tools.checkstyle.api.TokenTypes.IMPORT;
32+
import static com.puppycrawl.tools.checkstyle.api.TokenTypes.LITERAL_NEW;
33+
import static com.puppycrawl.tools.checkstyle.api.TokenTypes.METHOD_CALL;
34+
import static com.puppycrawl.tools.checkstyle.api.TokenTypes.METHOD_REF;
35+
import static com.puppycrawl.tools.checkstyle.api.TokenTypes.STATIC_IMPORT;
36+
37+
/** */
38+
public class ClassUsageRestrictionRule extends AbstractCheck {
39+
/** */
40+
private ClassDescriptor restrictedCls;
41+
42+
/** */
43+
private Set<String> restrictedFactoryMethods = Collections.emptySet();
44+
45+
/** */
46+
private String substitutionClsName;
47+
48+
/** */
49+
private boolean isRestrictedClsImportDetected;
50+
51+
/** */
52+
private final Set<String> detectedMethodImports = new HashSet<>();
53+
54+
/** */
55+
private static final int[] TOKENS = new int[] {
56+
IMPORT,
57+
STATIC_IMPORT,
58+
EXTENDS_CLAUSE,
59+
LITERAL_NEW,
60+
METHOD_REF,
61+
METHOD_CALL
62+
};
63+
64+
/** {@inheritDoc} */
65+
@Override public void init() {
66+
if (restrictedCls == null)
67+
throw new IllegalStateException("Restricted class is not set");
68+
69+
if (isEmpty(substitutionClsName))
70+
throw new IllegalStateException("Substitution class is not set");
71+
72+
if (isEmpty(restrictedCls.packageName) || isEmpty(restrictedCls.simpleName))
73+
throw new IllegalStateException("Invalid restricted class name. Class FQN is required");
74+
}
75+
76+
/** {@inheritDoc} */
77+
@Override public void beginTree(DetailAST rootAST) {
78+
isRestrictedClsImportDetected = "java.lang".equals(restrictedCls.packageName);
79+
80+
detectedMethodImports.clear();
81+
}
82+
83+
/** {@inheritDoc} */
84+
@Override public void visitToken(DetailAST token) {
85+
if (token.getType() == IMPORT) {
86+
ClassDescriptor clsDesc = ClassDescriptor.forToken(token.getFirstChild());
87+
88+
if (isRestrictedClass(clsDesc))
89+
isRestrictedClsImportDetected = true;
90+
}
91+
else if (token.getType() == STATIC_IMPORT) {
92+
ClassMemberDescriptor memberDesc = ClassMemberDescriptor.fromToken(token.getFirstChild().getNextSibling());
93+
94+
if (isRestrictedMethod(memberDesc))
95+
detectedMethodImports.add(memberDesc.memberName);
96+
}
97+
else if (token.getType() == EXTENDS_CLAUSE || token.getType() == LITERAL_NEW) {
98+
ClassDescriptor clsDesc = ClassDescriptor.forToken(token.getFirstChild());
99+
100+
if (isRestrictedClass(clsDesc))
101+
logFailure(token);
102+
}
103+
else if (token.getType() == METHOD_REF) {
104+
ClassMemberDescriptor methodDesc = ClassMemberDescriptor.fromMethodReferenceToken(token);
105+
106+
if (isRestrictedMethod(methodDesc))
107+
logFailure(token);
108+
}
109+
else if (token.getType() == METHOD_CALL) {
110+
ClassMemberDescriptor methodDesc = ClassMemberDescriptor.fromToken(token.getFirstChild());
111+
112+
if (isRestrictedMethod(methodDesc))
113+
logFailure(token);
114+
}
115+
else
116+
throw new IllegalStateException();
117+
}
118+
119+
/** {@inheritDoc} */
120+
@Override public int[] getDefaultTokens() {
121+
return TOKENS.clone();
122+
}
123+
124+
/** {@inheritDoc} */
125+
@Override public int[] getAcceptableTokens() {
126+
return TOKENS.clone();
127+
}
128+
129+
/** {@inheritDoc} */
130+
@Override public int[] getRequiredTokens() {
131+
return TOKENS.clone();
132+
}
133+
134+
/** */
135+
public void setClassName(String clsName) {
136+
restrictedCls = ClassDescriptor.forName(clsName);
137+
}
138+
139+
/** */
140+
public void setFactoryMethods(String factoryMethods) {
141+
restrictedFactoryMethods = Arrays.stream(factoryMethods.split(",")).map(String::trim).collect(Collectors.toSet());
142+
}
143+
144+
/** */
145+
public void setSubstitutionClassName(String substitutionClsName) {
146+
this.substitutionClsName = substitutionClsName;
147+
}
148+
149+
/** */
150+
private void logFailure(DetailAST token) {
151+
log(
152+
token,
153+
"Usage of {0} class and its factory methods is restricted. Use {1} class as a substitution",
154+
restrictedCls.fullName,
155+
substitutionClsName
156+
);
157+
}
158+
159+
/** */
160+
private static boolean isEmpty(String str) {
161+
return str == null || str.isEmpty();
162+
}
163+
164+
/** */
165+
private boolean isRestrictedMethod(ClassMemberDescriptor desc) {
166+
if (desc.clsDesc == null)
167+
return detectedMethodImports.contains(desc.memberName);
168+
169+
return restrictedFactoryMethods.contains(desc.memberName) && isRestrictedClass(desc.clsDesc);
170+
}
171+
172+
/** */
173+
private boolean isRestrictedClass(ClassDescriptor desc) {
174+
if (Objects.equals(restrictedCls.fullName, desc.fullName))
175+
return true;
176+
177+
return Objects.equals(restrictedCls.simpleName, desc.simpleName) && isRestrictedClsImportDetected;
178+
}
179+
180+
/** */
181+
private static class ClassDescriptor {
182+
/** */
183+
private final String fullName;
184+
185+
/** */
186+
private final String packageName;
187+
188+
/** */
189+
private final String simpleName;
190+
191+
/** */
192+
public ClassDescriptor(String fullName, String packageName, String simpleName) {
193+
this.fullName = fullName;
194+
this.packageName = packageName;
195+
this.simpleName = simpleName;
196+
}
197+
198+
/** */
199+
public static ClassDescriptor forToken(DetailAST token) {
200+
return forName(createFullIdent(token).getText());
201+
}
202+
203+
/** */
204+
public static ClassDescriptor forName(String name) {
205+
int sepPos = name.lastIndexOf('.');
206+
207+
if (sepPos == -1)
208+
return new ClassDescriptor(name, "", name);
209+
210+
return new ClassDescriptor(name, name.substring(0, sepPos), name.substring(sepPos + 1));
211+
}
212+
}
213+
214+
/** */
215+
private static class ClassMemberDescriptor {
216+
/** */
217+
private final ClassDescriptor clsDesc;
218+
219+
/** */
220+
private final String memberName;
221+
222+
/** */
223+
private ClassMemberDescriptor(ClassDescriptor clsDesc, String memberName) {
224+
this.clsDesc = clsDesc;
225+
this.memberName = memberName;
226+
}
227+
228+
/** */
229+
public static ClassMemberDescriptor fromMethodReferenceToken(DetailAST token) {
230+
DetailAST clsToken = token.getFirstChild();
231+
232+
DetailAST methodToken = clsToken.getNextSibling();
233+
234+
return new ClassMemberDescriptor(ClassDescriptor.forToken(clsToken), methodToken.getText());
235+
}
236+
237+
/** */
238+
public static ClassMemberDescriptor fromToken(DetailAST token) {
239+
final String tokenText = createFullIdent(token).getText();
240+
241+
final int memberSepPos = tokenText.lastIndexOf('.');
242+
243+
if (memberSepPos == -1)
244+
return new ClassMemberDescriptor(null, tokenText);
245+
246+
return new ClassMemberDescriptor(
247+
ClassDescriptor.forName(tokenText.substring(0, memberSepPos)),
248+
tokenText.substring(memberSepPos + 1)
249+
);
250+
}
251+
}
252+
}

0 commit comments

Comments
 (0)