1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.imports;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.net.MalformedURLException;
25 import java.net.URI;
26 import java.util.ArrayDeque;
27 import java.util.Deque;
28 import java.util.HashMap;
29 import java.util.Map;
30
31 import javax.xml.parsers.ParserConfigurationException;
32
33 import org.xml.sax.Attributes;
34 import org.xml.sax.InputSource;
35 import org.xml.sax.SAXException;
36
37 import com.puppycrawl.tools.checkstyle.XmlLoader;
38 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
39
40
41
42
43 public final class ImportControlLoader extends XmlLoader {
44
45
46 private static final String DTD_PUBLIC_ID_1_0 =
47 "-//Puppy Crawl//DTD Import Control 1.0//EN";
48
49
50 private static final String DTD_PUBLIC_CS_ID_1_0 =
51 "-//Checkstyle//DTD ImportControl Configuration 1.0//EN";
52
53
54 private static final String DTD_PUBLIC_ID_1_1 =
55 "-//Puppy Crawl//DTD Import Control 1.1//EN";
56
57
58 private static final String DTD_PUBLIC_CS_ID_1_1 =
59 "-//Checkstyle//DTD ImportControl Configuration 1.1//EN";
60
61
62 private static final String DTD_PUBLIC_ID_1_2 =
63 "-//Puppy Crawl//DTD Import Control 1.2//EN";
64
65
66 private static final String DTD_PUBLIC_CS_ID_1_2 =
67 "-//Checkstyle//DTD ImportControl Configuration 1.2//EN";
68
69
70 private static final String DTD_PUBLIC_ID_1_3 =
71 "-//Puppy Crawl//DTD Import Control 1.3//EN";
72
73
74 private static final String DTD_PUBLIC_CS_ID_1_3 =
75 "-//Checkstyle//DTD ImportControl Configuration 1.3//EN";
76
77
78 private static final String DTD_PUBLIC_ID_1_4 =
79 "-//Puppy Crawl//DTD Import Control 1.4//EN";
80
81
82 private static final String DTD_PUBLIC_CS_ID_1_4 =
83 "-//Checkstyle//DTD ImportControl Configuration 1.4//EN";
84
85
86 private static final String DTD_PUBLIC_ID_1_5 =
87 "-//Puppy Crawl//DTD Import Control 1.5//EN";
88
89
90 private static final String DTD_PUBLIC_CS_ID_1_5 =
91 "-//Checkstyle//DTD ImportControl Configuration 1.5//EN";
92
93
94 private static final String DTD_RESOURCE_NAME_1_0 =
95 "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_0.dtd";
96
97
98 private static final String DTD_RESOURCE_NAME_1_1 =
99 "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_1.dtd";
100
101
102 private static final String DTD_RESOURCE_NAME_1_2 =
103 "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_2.dtd";
104
105
106 private static final String DTD_RESOURCE_NAME_1_3 =
107 "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_3.dtd";
108
109
110 private static final String DTD_RESOURCE_NAME_1_4 =
111 "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_4.dtd";
112
113
114 private static final String DTD_RESOURCE_NAME_1_5 =
115 "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_5.dtd";
116
117
118 private static final Map<String, String> DTD_RESOURCE_BY_ID = new HashMap<>();
119
120
121 private static final String PKG_ATTRIBUTE_NAME = "pkg";
122
123
124 private static final String NAME_ATTRIBUTE_NAME = "name";
125
126
127 private static final String STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME = "strategyOnMismatch";
128
129
130 private static final String STRATEGY_ON_MISMATCH_ALLOWED_VALUE = "allowed";
131
132
133 private static final String STRATEGY_ON_MISMATCH_DISALLOWED_VALUE = "disallowed";
134
135
136 private static final String SUBPACKAGE_ELEMENT_NAME = "subpackage";
137
138
139 private static final String FILE_ELEMENT_NAME = "file";
140
141
142 private static final String ALLOW_ELEMENT_NAME = "allow";
143
144
145 private final Deque<AbstractImportControl> stack = new ArrayDeque<>();
146
147 static {
148 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_0, DTD_RESOURCE_NAME_1_0);
149 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_1, DTD_RESOURCE_NAME_1_1);
150 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_2, DTD_RESOURCE_NAME_1_2);
151 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_3, DTD_RESOURCE_NAME_1_3);
152 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_4, DTD_RESOURCE_NAME_1_4);
153 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_5, DTD_RESOURCE_NAME_1_5);
154 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_CS_ID_1_0, DTD_RESOURCE_NAME_1_0);
155 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_CS_ID_1_1, DTD_RESOURCE_NAME_1_1);
156 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_CS_ID_1_2, DTD_RESOURCE_NAME_1_2);
157 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_CS_ID_1_3, DTD_RESOURCE_NAME_1_3);
158 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_CS_ID_1_4, DTD_RESOURCE_NAME_1_4);
159 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_CS_ID_1_5, DTD_RESOURCE_NAME_1_5);
160 }
161
162
163
164
165
166
167
168 private ImportControlLoader() throws ParserConfigurationException,
169 SAXException {
170 super(DTD_RESOURCE_BY_ID);
171 }
172
173 @Override
174 public void startElement(String namespaceUri,
175 String localName,
176 String qualifiedName,
177 Attributes attributes)
178 throws SAXException {
179 if ("import-control".equals(qualifiedName)) {
180 final String pkg = safeGet(attributes, PKG_ATTRIBUTE_NAME);
181 final MismatchStrategy strategyOnMismatch = getStrategyForImportControl(attributes);
182 final boolean regex = containsRegexAttribute(attributes);
183 stack.push(new PkgImportControl(pkg, regex, strategyOnMismatch));
184 }
185 else if (SUBPACKAGE_ELEMENT_NAME.equals(qualifiedName)) {
186 final String name = safeGet(attributes, NAME_ATTRIBUTE_NAME);
187 final MismatchStrategy strategyOnMismatch = getStrategyForSubpackage(attributes);
188 final boolean regex = containsRegexAttribute(attributes);
189 final PkgImportControl parentImportControl = (PkgImportControl) stack.peek();
190 final AbstractImportControl importControl = new PkgImportControl(parentImportControl,
191 name, regex, strategyOnMismatch);
192 parentImportControl.addChild(importControl);
193 stack.push(importControl);
194 }
195 else if (FILE_ELEMENT_NAME.equals(qualifiedName)) {
196 final String name = safeGet(attributes, NAME_ATTRIBUTE_NAME);
197 final boolean regex = containsRegexAttribute(attributes);
198 final PkgImportControl parentImportControl = (PkgImportControl) stack.peek();
199 final AbstractImportControl importControl = new FileImportControl(parentImportControl,
200 name, regex);
201 parentImportControl.addChild(importControl);
202 stack.push(importControl);
203 }
204 else {
205 final AbstractImportRule rule = createImportRule(qualifiedName, attributes);
206 stack.peek().addImportRule(rule);
207 }
208 }
209
210
211
212
213
214
215
216
217
218
219 private static AbstractImportRule createImportRule(String qualifiedName, Attributes attributes)
220 throws SAXException {
221
222 final boolean isAllow = ALLOW_ELEMENT_NAME.equals(qualifiedName);
223 final boolean isLocalOnly = attributes.getValue("local-only") != null;
224 final String pkg = attributes.getValue(PKG_ATTRIBUTE_NAME);
225 final String module = attributes.getValue("module");
226 final boolean regex = containsRegexAttribute(attributes);
227 final AbstractImportRule rule;
228
229 if (module != null) {
230 rule = new ModuleImportRule(isAllow, isLocalOnly, module, regex);
231 }
232 else if (pkg != null) {
233 final boolean exactMatch = attributes.getValue("exact-match") != null;
234 rule = new PkgImportRule(isAllow, isLocalOnly, pkg, exactMatch, regex);
235 }
236 else {
237 final String clazz = safeGet(attributes, "class");
238 rule = new ClassImportRule(isAllow, isLocalOnly, clazz, regex);
239 }
240 return rule;
241 }
242
243
244
245
246
247
248
249 private static boolean containsRegexAttribute(Attributes attributes) {
250 return attributes.getValue("regex") != null;
251 }
252
253 @Override
254 public void endElement(String namespaceUri, String localName,
255 String qualifiedName) {
256 if (SUBPACKAGE_ELEMENT_NAME.equals(qualifiedName)
257 || FILE_ELEMENT_NAME.equals(qualifiedName)) {
258 stack.pop();
259 }
260 }
261
262
263
264
265
266
267
268
269 public static PkgImportControl load(URI uri) throws CheckstyleException {
270 return loadUri(uri);
271 }
272
273
274
275
276
277
278
279
280
281 private static PkgImportControl load(InputSource source,
282 URI uri) throws CheckstyleException {
283 try {
284 final ImportControlLoader loader = new ImportControlLoader();
285 loader.parseInputSource(source);
286 return loader.getRoot();
287 }
288 catch (ParserConfigurationException | SAXException exc) {
289 throw new CheckstyleException("unable to parse " + uri, exc);
290 }
291 catch (IOException exc) {
292 throw new CheckstyleException("unable to read " + uri, exc);
293 }
294 }
295
296
297
298
299
300
301
302
303 private static PkgImportControl loadUri(URI uri) throws CheckstyleException {
304 try (InputStream inputStream = uri.toURL().openStream()) {
305 final InputSource source = new InputSource(inputStream);
306 return load(source, uri);
307 }
308 catch (MalformedURLException exc) {
309 throw new CheckstyleException("syntax error in url " + uri, exc);
310 }
311 catch (IOException exc) {
312 throw new CheckstyleException("unable to find " + uri, exc);
313 }
314 }
315
316
317
318
319
320
321 private PkgImportControl getRoot() {
322 return (PkgImportControl) stack.peek();
323 }
324
325
326
327
328
329
330
331 private static MismatchStrategy getStrategyForImportControl(Attributes attributes) {
332 final String returnValue = attributes.getValue(STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME);
333 MismatchStrategy strategyOnMismatch = MismatchStrategy.DISALLOWED;
334 if (STRATEGY_ON_MISMATCH_ALLOWED_VALUE.equals(returnValue)) {
335 strategyOnMismatch = MismatchStrategy.ALLOWED;
336 }
337 return strategyOnMismatch;
338 }
339
340
341
342
343
344
345
346 private static MismatchStrategy getStrategyForSubpackage(Attributes attributes) {
347 final String returnValue = attributes.getValue(STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME);
348 MismatchStrategy strategyOnMismatch = MismatchStrategy.DELEGATE_TO_PARENT;
349 if (STRATEGY_ON_MISMATCH_ALLOWED_VALUE.equals(returnValue)) {
350 strategyOnMismatch = MismatchStrategy.ALLOWED;
351 }
352 else if (STRATEGY_ON_MISMATCH_DISALLOWED_VALUE.equals(returnValue)) {
353 strategyOnMismatch = MismatchStrategy.DISALLOWED;
354 }
355 return strategyOnMismatch;
356 }
357
358
359
360
361
362
363
364
365
366
367 private static String safeGet(Attributes attributes, String name)
368 throws SAXException {
369 final String returnValue = attributes.getValue(name);
370 if (returnValue == null) {
371
372
373 throw new SAXException("missing attribute " + name);
374 }
375 return returnValue;
376 }
377
378 }