Skip to content

Commit a096eb9

Browse files
committed
1 parent 8891fb7 commit a096eb9

35 files changed

Lines changed: 978 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: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
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.HashSet;
22+
import java.util.Objects;
23+
import java.util.Set;
24+
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
25+
import com.puppycrawl.tools.checkstyle.api.DetailAST;
26+
27+
import static com.puppycrawl.tools.checkstyle.api.FullIdent.createFullIdent;
28+
import static com.puppycrawl.tools.checkstyle.api.TokenTypes.EXTENDS_CLAUSE;
29+
import static com.puppycrawl.tools.checkstyle.api.TokenTypes.IMPORT;
30+
import static com.puppycrawl.tools.checkstyle.api.TokenTypes.LITERAL_NEW;
31+
import static com.puppycrawl.tools.checkstyle.api.TokenTypes.METHOD_CALL;
32+
import static com.puppycrawl.tools.checkstyle.api.TokenTypes.METHOD_REF;
33+
import static com.puppycrawl.tools.checkstyle.api.TokenTypes.STATIC_IMPORT;
34+
35+
/** */
36+
public class ClassUsageRestrictionRule extends AbstractCheck {
37+
/** */
38+
private static final int[] TOKENS = new int[] {
39+
IMPORT,
40+
STATIC_IMPORT,
41+
EXTENDS_CLAUSE,
42+
LITERAL_NEW,
43+
METHOD_REF,
44+
METHOD_CALL
45+
};
46+
47+
/** */
48+
private static final Set<String> DEFAULT_RESTRICTED_FACTORY_METHODS = Set.of("new");
49+
50+
/** */
51+
private ClassDescriptor restrictedCls;
52+
53+
/** */
54+
private Set<String> restrictedFactoryMethods = DEFAULT_RESTRICTED_FACTORY_METHODS;
55+
56+
/** */
57+
private String substitutionClsName;
58+
59+
/** */
60+
private boolean isRestrictedClsImportDetected;
61+
62+
/** */
63+
private final Set<String> detectedMethodImports = new HashSet<>();
64+
65+
/** {@inheritDoc} */
66+
@Override public void init() {
67+
if (restrictedCls == null)
68+
throw new IllegalStateException("Restricted class is not set");
69+
70+
if (isEmpty(substitutionClsName))
71+
throw new IllegalStateException("Substitution class is not set");
72+
73+
if (isEmpty(restrictedCls.packageName) || isEmpty(restrictedCls.simpleName))
74+
throw new IllegalStateException("Invalid restricted class name. Class FQN is required");
75+
}
76+
77+
/** {@inheritDoc} */
78+
@Override public void beginTree(DetailAST rootAST) {
79+
isRestrictedClsImportDetected = "java.lang".equals(restrictedCls.packageName);
80+
81+
detectedMethodImports.clear();
82+
}
83+
84+
/** {@inheritDoc} */
85+
@Override public void visitToken(DetailAST token) {
86+
if (token.getType() == IMPORT) {
87+
ClassDescriptor clsDesc = ClassDescriptor.forToken(token.getFirstChild());
88+
89+
if (isRestrictedClass(clsDesc))
90+
isRestrictedClsImportDetected = true;
91+
}
92+
else if (token.getType() == STATIC_IMPORT) {
93+
ClassMemberDescriptor memberDesc = ClassMemberDescriptor.fromToken(token.getFirstChild().getNextSibling());
94+
95+
if (isRestrictedMethod(memberDesc))
96+
detectedMethodImports.add(memberDesc.memberName);
97+
}
98+
else if (token.getType() == EXTENDS_CLAUSE || token.getType() == LITERAL_NEW) {
99+
ClassDescriptor clsDesc = ClassDescriptor.forToken(token.getFirstChild());
100+
101+
if (isRestrictedClass(clsDesc))
102+
logFailure(token);
103+
}
104+
else if (token.getType() == METHOD_REF) {
105+
ClassMemberDescriptor methodDesc = ClassMemberDescriptor.fromMethodReferenceToken(token);
106+
107+
if (isRestrictedMethod(methodDesc))
108+
logFailure(token);
109+
}
110+
else if (token.getType() == METHOD_CALL) {
111+
ClassMemberDescriptor methodDesc = ClassMemberDescriptor.fromToken(token.getFirstChild());
112+
113+
if (isRestrictedMethod(methodDesc))
114+
logFailure(token);
115+
}
116+
else
117+
throw new IllegalStateException();
118+
}
119+
120+
/** {@inheritDoc} */
121+
@Override public int[] getDefaultTokens() {
122+
return TOKENS.clone();
123+
}
124+
125+
/** {@inheritDoc} */
126+
@Override public int[] getAcceptableTokens() {
127+
return TOKENS.clone();
128+
}
129+
130+
/** {@inheritDoc} */
131+
@Override public int[] getRequiredTokens() {
132+
return TOKENS.clone();
133+
}
134+
135+
/** */
136+
public void setClassName(String clsName) {
137+
restrictedCls = ClassDescriptor.forName(clsName);
138+
}
139+
140+
/** */
141+
public void setFactoryMethods(String factoryMethods) {
142+
Set<String> restrictedFactoryMethods = new HashSet<>(DEFAULT_RESTRICTED_FACTORY_METHODS);
143+
144+
Arrays.stream(factoryMethods.split(",")).map(String::trim).forEach(restrictedFactoryMethods::add);
145+
146+
this.restrictedFactoryMethods = restrictedFactoryMethods;
147+
}
148+
149+
/** */
150+
public void setSubstitutionClassName(String substitutionClsName) {
151+
this.substitutionClsName = substitutionClsName;
152+
}
153+
154+
/** */
155+
private void logFailure(DetailAST token) {
156+
log(
157+
token,
158+
"Usage of {0} class and its factory methods is restricted. Use {1} class as a substitution",
159+
restrictedCls.fullName,
160+
substitutionClsName
161+
);
162+
}
163+
164+
/** */
165+
private static boolean isEmpty(String str) {
166+
return str == null || str.isEmpty();
167+
}
168+
169+
/** */
170+
private boolean isRestrictedMethod(ClassMemberDescriptor desc) {
171+
if (desc.clsDesc == null)
172+
return detectedMethodImports.contains(desc.memberName);
173+
174+
return restrictedFactoryMethods.contains(desc.memberName) && isRestrictedClass(desc.clsDesc);
175+
}
176+
177+
/** */
178+
private boolean isRestrictedClass(ClassDescriptor desc) {
179+
if (Objects.equals(restrictedCls.fullName, desc.fullName))
180+
return true;
181+
182+
return Objects.equals(restrictedCls.simpleName, desc.simpleName) && isRestrictedClsImportDetected;
183+
}
184+
185+
/** */
186+
private static class ClassDescriptor {
187+
/** */
188+
private final String fullName;
189+
190+
/** */
191+
private final String packageName;
192+
193+
/** */
194+
private final String simpleName;
195+
196+
/** */
197+
public ClassDescriptor(String fullName, String packageName, String simpleName) {
198+
this.fullName = fullName;
199+
this.packageName = packageName;
200+
this.simpleName = simpleName;
201+
}
202+
203+
/** */
204+
public static ClassDescriptor forToken(DetailAST token) {
205+
return forName(createFullIdent(token).getText());
206+
}
207+
208+
/** */
209+
public static ClassDescriptor forName(String name) {
210+
int sepPos = name.lastIndexOf('.');
211+
212+
if (sepPos == -1)
213+
return new ClassDescriptor(name, "", name);
214+
215+
return new ClassDescriptor(name, name.substring(0, sepPos), name.substring(sepPos + 1));
216+
}
217+
}
218+
219+
/** */
220+
private static class ClassMemberDescriptor {
221+
/** */
222+
private final ClassDescriptor clsDesc;
223+
224+
/** */
225+
private final String memberName;
226+
227+
/** */
228+
private ClassMemberDescriptor(ClassDescriptor clsDesc, String memberName) {
229+
this.clsDesc = clsDesc;
230+
this.memberName = memberName;
231+
}
232+
233+
/** */
234+
public static ClassMemberDescriptor fromMethodReferenceToken(DetailAST token) {
235+
DetailAST clsToken = token.getFirstChild();
236+
237+
DetailAST methodToken = clsToken.getNextSibling();
238+
239+
return new ClassMemberDescriptor(ClassDescriptor.forToken(clsToken), methodToken.getText());
240+
}
241+
242+
/** */
243+
public static ClassMemberDescriptor fromToken(DetailAST token) {
244+
final String tokenText = createFullIdent(token).getText();
245+
246+
final int memberSepPos = tokenText.lastIndexOf('.');
247+
248+
if (memberSepPos == -1)
249+
return new ClassMemberDescriptor(null, tokenText);
250+
251+
return new ClassMemberDescriptor(
252+
ClassDescriptor.forName(tokenText.substring(0, memberSepPos)),
253+
tokenText.substring(memberSepPos + 1)
254+
);
255+
}
256+
}
257+
}

0 commit comments

Comments
 (0)