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 qName,
177 Attributes attributes)
178 throws SAXException {
179 if ("import-control".equals(qName)) {
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(qName)) {
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(qName)) {
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(qName, attributes);
206 stack.peek().addImportRule(rule);
207 }
208 }
209
210
211
212
213
214
215
216
217
218
219 private static AbstractImportRule createImportRule(String qName, Attributes attributes)
220 throws SAXException {
221
222 final boolean isAllow = ALLOW_ELEMENT_NAME.equals(qName);
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 qName) {
256 if (SUBPACKAGE_ELEMENT_NAME.equals(qName) || FILE_ELEMENT_NAME.equals(qName)) {
257 stack.pop();
258 }
259 }
260
261
262
263
264
265
266
267
268 public static PkgImportControl load(URI uri) throws CheckstyleException {
269 return loadUri(uri);
270 }
271
272
273
274
275
276
277
278
279
280 private static PkgImportControl load(InputSource source,
281 URI uri) throws CheckstyleException {
282 try {
283 final ImportControlLoader loader = new ImportControlLoader();
284 loader.parseInputSource(source);
285 return loader.getRoot();
286 }
287 catch (ParserConfigurationException | SAXException exc) {
288 throw new CheckstyleException("unable to parse " + uri, exc);
289 }
290 catch (IOException exc) {
291 throw new CheckstyleException("unable to read " + uri, exc);
292 }
293 }
294
295
296
297
298
299
300
301
302 private static PkgImportControl loadUri(URI uri) throws CheckstyleException {
303 try (InputStream inputStream = uri.toURL().openStream()) {
304 final InputSource source = new InputSource(inputStream);
305 return load(source, uri);
306 }
307 catch (MalformedURLException exc) {
308 throw new CheckstyleException("syntax error in url " + uri, exc);
309 }
310 catch (IOException exc) {
311 throw new CheckstyleException("unable to find " + uri, exc);
312 }
313 }
314
315
316
317
318
319
320 private PkgImportControl getRoot() {
321 return (PkgImportControl) stack.peek();
322 }
323
324
325
326
327
328
329
330 private static MismatchStrategy getStrategyForImportControl(Attributes attributes) {
331 final String returnValue = attributes.getValue(STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME);
332 MismatchStrategy strategyOnMismatch = MismatchStrategy.DISALLOWED;
333 if (STRATEGY_ON_MISMATCH_ALLOWED_VALUE.equals(returnValue)) {
334 strategyOnMismatch = MismatchStrategy.ALLOWED;
335 }
336 return strategyOnMismatch;
337 }
338
339
340
341
342
343
344
345 private static MismatchStrategy getStrategyForSubpackage(Attributes attributes) {
346 final String returnValue = attributes.getValue(STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME);
347 MismatchStrategy strategyOnMismatch = MismatchStrategy.DELEGATE_TO_PARENT;
348 if (STRATEGY_ON_MISMATCH_ALLOWED_VALUE.equals(returnValue)) {
349 strategyOnMismatch = MismatchStrategy.ALLOWED;
350 }
351 else if (STRATEGY_ON_MISMATCH_DISALLOWED_VALUE.equals(returnValue)) {
352 strategyOnMismatch = MismatchStrategy.DISALLOWED;
353 }
354 return strategyOnMismatch;
355 }
356
357
358
359
360
361
362
363
364
365
366 private static String safeGet(Attributes attributes, String name)
367 throws SAXException {
368 final String returnValue = attributes.getValue(name);
369 if (returnValue == null) {
370
371
372 throw new SAXException("missing attribute " + name);
373 }
374 return returnValue;
375 }
376
377 }