• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.buildtools.checkstyle;
17 
18 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
19 import com.puppycrawl.tools.checkstyle.api.DetailAST;
20 import com.puppycrawl.tools.checkstyle.api.FullIdent;
21 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
22 import java.util.Arrays;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Set;
27 import java.util.regex.Pattern;
28 
29 /**
30  * Verify that we're not using classes in rt.jar that aren't exported via the java.base module.
31  *
32  * If anything fails this check, it increases our module dependencies. If you absolutely must use one of these
33  * (e.g. java.beans) because it's fundamental to your functionality, you can suppress this checkstyle rule via
34  * {@link #setLegalPackages(String...)}, but know that it is not free - you're essentially adding a dependency
35  * for customers that use the module path.
36  */
37 public class NonJavaBaseModuleCheck extends AbstractCheck {
38     private static final List<String> ILLEGAL_PACKAGES = Arrays.asList(
39         "java",
40         "javax",
41         "sun",
42         "apple",
43         "com.apple",
44         "com.oracle");
45 
46     private static final Set<String> LEGAL_PACKAGES = new HashSet<>(Arrays.asList(
47         "java.io",
48         "java.lang",
49         "java.lang.annotation",
50         "java.lang.invoke",
51         "java.lang.module",
52         "java.lang.ref",
53         "java.lang.reflect",
54         "java.math",
55         "java.net",
56         "java.net.spi",
57         "java.nio",
58         "java.nio.channels",
59         "java.nio.channels.spi",
60         "java.nio.charset",
61         "java.nio.charset.spi",
62         "java.nio.file",
63         "java.nio.file.attribute",
64         "java.nio.file.spi",
65         "java.security",
66         "java.security.acl",
67         "java.security.cert",
68         "java.security.interfaces",
69         "java.security.spec",
70         "java.text",
71         "java.text.spi",
72         "java.time",
73         "java.time.chrono",
74         "java.time.format",
75         "java.time.temporal",
76         "java.time.zone",
77         "java.util",
78         "java.util.concurrent",
79         "java.util.concurrent.atomic",
80         "java.util.concurrent.locks",
81         "java.util.function",
82         "java.util.jar",
83         "java.util.regex",
84         "java.util.spi",
85         "java.util.stream",
86         "java.util.jar",
87         "java.util.zip",
88         "javax.crypto",
89         "javax.crypto.interfaces",
90         "javax.crypto.spec",
91         "javax.net",
92         "javax.net.ssl",
93         "javax.security.auth",
94         "javax.security.auth.callback",
95         "javax.security.auth.login",
96         "javax.security.auth.spi",
97         "javax.security.auth.x500",
98         "javax.security.cert"));
99 
100     private static final Pattern CLASSNAME_START_PATTERN = Pattern.compile("[A-Z]");
101 
102     private String currentSdkPackage;
103 
104     private HashMap<String, Set<String>> additionalLegalPackagesBySdkPackage = new HashMap<>();
105 
106     /**
107      * Additional legal packages are formatted as "sdk.package.name:jdk.package.name,sdk.package.name2:jdk.package.name2".
108      * Multiple SDK packages can be repeated.
109      */
setLegalPackages(String... legalPackages)110     public void setLegalPackages(String... legalPackages) {
111         for (String additionalLegalPackage : legalPackages) {
112             String[] splitPackage = additionalLegalPackage.split(":", 2);
113             if (splitPackage.length != 2) {
114                 throw new IllegalArgumentException("Invalid legal package definition '" + additionalLegalPackage + "'. Expected"
115                                                    + " format is sdk.package.name:jdk.package.name");
116             }
117 
118             this.additionalLegalPackagesBySdkPackage.computeIfAbsent(splitPackage[0], k -> new HashSet<>())
119                                                     .add(splitPackage[1]);
120         }
121     }
122 
123     @Override
getDefaultTokens()124     public int[] getDefaultTokens() {
125         return getRequiredTokens();
126     }
127 
128     @Override
getAcceptableTokens()129     public int[] getAcceptableTokens() {
130         return getRequiredTokens();
131     }
132 
133     @Override
getRequiredTokens()134     public int[] getRequiredTokens() {
135         return new int[] { TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT, TokenTypes.PACKAGE_DEF };
136     }
137 
138     @Override
visitToken(DetailAST ast)139     public void visitToken(DetailAST ast) {
140         if (ast.getType() == TokenTypes.PACKAGE_DEF) {
141             handlePackageDefToken(ast);
142             return;
143         }
144 
145         handleImportToken(ast);
146     }
147 
handlePackageDefToken(DetailAST ast)148     private void handlePackageDefToken(DetailAST ast) {
149         this.currentSdkPackage = FullIdent.createFullIdent(ast.getLastChild().getPreviousSibling()).getText();
150     }
151 
handleImportToken(DetailAST ast)152     private void handleImportToken(DetailAST ast) {
153         FullIdent importIdentifier;
154         if (ast.getType() == TokenTypes.IMPORT) {
155             importIdentifier = FullIdent.createFullIdentBelow(ast);
156         } else {
157             importIdentifier = FullIdent.createFullIdent(ast.getFirstChild().getNextSibling());
158         }
159 
160         String importText = importIdentifier.getText();
161         if (isIllegalImport(importText) && !isLegalImport(importText)) {
162             log(ast, "Import '" + importText + "' uses a JDK class that is not in java.base. This essentially adds an "
163                      + "additional module dependency. Don't suppress this rule unless it's absolutely required, and only "
164                      + "suppress the specific package you need via checkstyle.xml instead of suppressing the entire rule.");
165         }
166     }
167 
isIllegalImport(String importText)168     private boolean isIllegalImport(String importText) {
169         for (String illegalPackage : ILLEGAL_PACKAGES) {
170             if (importText.startsWith(illegalPackage + ".")) {
171                 return true;
172             }
173         }
174 
175         return false;
176     }
177 
isLegalImport(String importText)178     private boolean isLegalImport(String importText) {
179         String importPackageWithTrailingDot = CLASSNAME_START_PATTERN.split(importText, 2)[0];
180         String importPackage = importText.substring(0, importPackageWithTrailingDot.length() - 1);
181 
182         if (LEGAL_PACKAGES.contains(importPackage)) {
183             return true;
184         }
185 
186         if (additionalLegalPackagesBySdkPackage.entrySet()
187                                                .stream()
188                                                .anyMatch(e -> currentSdkPackage.startsWith(e.getKey()) &&
189                                                               e.getValue().contains(importPackage))) {
190             return true;
191         }
192 
193         return false;
194     }
195 }
196