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_RESOURCE_NAME_1_0 =
87 "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_0.dtd";
88
89
90 private static final String DTD_RESOURCE_NAME_1_1 =
91 "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_1.dtd";
92
93
94 private static final String DTD_RESOURCE_NAME_1_2 =
95 "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_2.dtd";
96
97
98 private static final String DTD_RESOURCE_NAME_1_3 =
99 "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_3.dtd";
100
101
102 private static final String DTD_RESOURCE_NAME_1_4 =
103 "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_4.dtd";
104
105
106 private static final Map<String, String> DTD_RESOURCE_BY_ID = new HashMap<>();
107
108
109 private static final String PKG_ATTRIBUTE_NAME = "pkg";
110
111
112 private static final String NAME_ATTRIBUTE_NAME = "name";
113
114
115 private static final String STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME = "strategyOnMismatch";
116
117
118 private static final String STRATEGY_ON_MISMATCH_ALLOWED_VALUE = "allowed";
119
120
121 private static final String STRATEGY_ON_MISMATCH_DISALLOWED_VALUE = "disallowed";
122
123
124 private static final String SUBPACKAGE_ELEMENT_NAME = "subpackage";
125
126
127 private static final String FILE_ELEMENT_NAME = "file";
128
129
130 private static final String ALLOW_ELEMENT_NAME = "allow";
131
132
133 private final Deque<AbstractImportControl> stack = new ArrayDeque<>();
134
135 static {
136 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_0, DTD_RESOURCE_NAME_1_0);
137 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_1, DTD_RESOURCE_NAME_1_1);
138 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_2, DTD_RESOURCE_NAME_1_2);
139 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_3, DTD_RESOURCE_NAME_1_3);
140 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_4, DTD_RESOURCE_NAME_1_4);
141 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_CS_ID_1_0, DTD_RESOURCE_NAME_1_0);
142 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_CS_ID_1_1, DTD_RESOURCE_NAME_1_1);
143 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_CS_ID_1_2, DTD_RESOURCE_NAME_1_2);
144 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_CS_ID_1_3, DTD_RESOURCE_NAME_1_3);
145 DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_CS_ID_1_4, DTD_RESOURCE_NAME_1_4);
146 }
147
148
149
150
151
152
153
154 private ImportControlLoader() throws ParserConfigurationException,
155 SAXException {
156 super(DTD_RESOURCE_BY_ID);
157 }
158
159 @Override
160 public void startElement(String namespaceUri,
161 String localName,
162 String qName,
163 Attributes attributes)
164 throws SAXException {
165 if ("import-control".equals(qName)) {
166 final String pkg = safeGet(attributes, PKG_ATTRIBUTE_NAME);
167 final MismatchStrategy strategyOnMismatch = getStrategyForImportControl(attributes);
168 final boolean regex = containsRegexAttribute(attributes);
169 stack.push(new PkgImportControl(pkg, regex, strategyOnMismatch));
170 }
171 else if (SUBPACKAGE_ELEMENT_NAME.equals(qName)) {
172 final String name = safeGet(attributes, NAME_ATTRIBUTE_NAME);
173 final MismatchStrategy strategyOnMismatch = getStrategyForSubpackage(attributes);
174 final boolean regex = containsRegexAttribute(attributes);
175 final PkgImportControl parentImportControl = (PkgImportControl) stack.peek();
176 final AbstractImportControl importControl = new PkgImportControl(parentImportControl,
177 name, regex, strategyOnMismatch);
178 parentImportControl.addChild(importControl);
179 stack.push(importControl);
180 }
181 else if (FILE_ELEMENT_NAME.equals(qName)) {
182 final String name = safeGet(attributes, NAME_ATTRIBUTE_NAME);
183 final boolean regex = containsRegexAttribute(attributes);
184 final PkgImportControl parentImportControl = (PkgImportControl) stack.peek();
185 final AbstractImportControl importControl = new FileImportControl(parentImportControl,
186 name, regex);
187 parentImportControl.addChild(importControl);
188 stack.push(importControl);
189 }
190 else {
191 final AbstractImportRule rule = createImportRule(qName, attributes);
192 stack.peek().addImportRule(rule);
193 }
194 }
195
196
197
198
199
200
201
202
203
204
205 private static AbstractImportRule createImportRule(String qName, Attributes attributes)
206 throws SAXException {
207
208
209
210 final boolean isAllow = ALLOW_ELEMENT_NAME.equals(qName);
211 final boolean isLocalOnly = attributes.getValue("local-only") != null;
212 final String pkg = attributes.getValue(PKG_ATTRIBUTE_NAME);
213 final boolean regex = containsRegexAttribute(attributes);
214 final AbstractImportRule rule;
215 if (pkg == null) {
216
217
218 final String clazz = safeGet(attributes, "class");
219 rule = new ClassImportRule(isAllow, isLocalOnly, clazz, regex);
220 }
221 else {
222 final boolean exactMatch =
223 attributes.getValue("exact-match") != null;
224 rule = new PkgImportRule(isAllow, isLocalOnly, pkg, exactMatch, regex);
225 }
226 return rule;
227 }
228
229
230
231
232
233
234
235 private static boolean containsRegexAttribute(Attributes attributes) {
236 return attributes.getValue("regex") != null;
237 }
238
239 @Override
240 public void endElement(String namespaceUri, String localName,
241 String qName) {
242 if (SUBPACKAGE_ELEMENT_NAME.equals(qName) || FILE_ELEMENT_NAME.equals(qName)) {
243 stack.pop();
244 }
245 }
246
247
248
249
250
251
252
253
254 public static PkgImportControl load(URI uri) throws CheckstyleException {
255 return loadUri(uri);
256 }
257
258
259
260
261
262
263
264
265
266 private static PkgImportControl load(InputSource source,
267 URI uri) throws CheckstyleException {
268 try {
269 final ImportControlLoader loader = new ImportControlLoader();
270 loader.parseInputSource(source);
271 return loader.getRoot();
272 }
273 catch (ParserConfigurationException | SAXException ex) {
274 throw new CheckstyleException("unable to parse " + uri
275 + " - " + ex.getMessage(), ex);
276 }
277 catch (IOException ex) {
278 throw new CheckstyleException("unable to read " + uri, ex);
279 }
280 }
281
282
283
284
285
286
287
288
289 private static PkgImportControl loadUri(URI uri) throws CheckstyleException {
290 try (InputStream inputStream = uri.toURL().openStream()) {
291 final InputSource source = new InputSource(inputStream);
292 return load(source, uri);
293 }
294 catch (MalformedURLException ex) {
295 throw new CheckstyleException("syntax error in url " + uri, ex);
296 }
297 catch (IOException ex) {
298 throw new CheckstyleException("unable to find " + uri, ex);
299 }
300 }
301
302
303
304
305
306
307 private PkgImportControl getRoot() {
308 return (PkgImportControl) stack.peek();
309 }
310
311
312
313
314
315
316
317 private static MismatchStrategy getStrategyForImportControl(Attributes attributes) {
318 final String returnValue = attributes.getValue(STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME);
319 MismatchStrategy strategyOnMismatch = MismatchStrategy.DISALLOWED;
320 if (STRATEGY_ON_MISMATCH_ALLOWED_VALUE.equals(returnValue)) {
321 strategyOnMismatch = MismatchStrategy.ALLOWED;
322 }
323 return strategyOnMismatch;
324 }
325
326
327
328
329
330
331
332 private static MismatchStrategy getStrategyForSubpackage(Attributes attributes) {
333 final String returnValue = attributes.getValue(STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME);
334 MismatchStrategy strategyOnMismatch = MismatchStrategy.DELEGATE_TO_PARENT;
335 if (STRATEGY_ON_MISMATCH_ALLOWED_VALUE.equals(returnValue)) {
336 strategyOnMismatch = MismatchStrategy.ALLOWED;
337 }
338 else if (STRATEGY_ON_MISMATCH_DISALLOWED_VALUE.equals(returnValue)) {
339 strategyOnMismatch = MismatchStrategy.DISALLOWED;
340 }
341 return strategyOnMismatch;
342 }
343
344
345
346
347
348
349
350
351
352
353 private static String safeGet(Attributes attributes, String name)
354 throws SAXException {
355 final String returnValue = attributes.getValue(name);
356 if (returnValue == null) {
357
358
359 throw new SAXException("missing attribute " + name);
360 }
361 return returnValue;
362 }
363
364 }