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 private ImportControlLoader() throws ParserConfigurationException,
154 SAXException {
155 super(DTD_RESOURCE_BY_ID);
156 }
157
158 @Override
159 public void startElement(String namespaceUri,
160 String localName,
161 String qName,
162 Attributes attributes)
163 throws SAXException {
164 if ("import-control".equals(qName)) {
165 final String pkg = safeGet(attributes, PKG_ATTRIBUTE_NAME);
166 final MismatchStrategy strategyOnMismatch = getStrategyForImportControl(attributes);
167 final boolean regex = containsRegexAttribute(attributes);
168 stack.push(new PkgImportControl(pkg, regex, strategyOnMismatch));
169 }
170 else if (SUBPACKAGE_ELEMENT_NAME.equals(qName)) {
171 final String name = safeGet(attributes, NAME_ATTRIBUTE_NAME);
172 final MismatchStrategy strategyOnMismatch = getStrategyForSubpackage(attributes);
173 final boolean regex = containsRegexAttribute(attributes);
174 final PkgImportControl parentImportControl = (PkgImportControl) stack.peek();
175 final AbstractImportControl importControl = new PkgImportControl(parentImportControl,
176 name, regex, strategyOnMismatch);
177 parentImportControl.addChild(importControl);
178 stack.push(importControl);
179 }
180 else if (FILE_ELEMENT_NAME.equals(qName)) {
181 final String name = safeGet(attributes, NAME_ATTRIBUTE_NAME);
182 final boolean regex = containsRegexAttribute(attributes);
183 final PkgImportControl parentImportControl = (PkgImportControl) stack.peek();
184 final AbstractImportControl importControl = new FileImportControl(parentImportControl,
185 name, regex);
186 parentImportControl.addChild(importControl);
187 stack.push(importControl);
188 }
189 else if (ALLOW_ELEMENT_NAME.equals(qName) || "disallow".equals(qName)) {
190 final AbstractImportRule rule = createImportRule(qName, attributes);
191 stack.peek().addImportRule(rule);
192 }
193 }
194
195
196
197
198
199
200
201
202
203 private static AbstractImportRule createImportRule(String qName, Attributes attributes)
204 throws SAXException {
205
206
207
208 final boolean isAllow = ALLOW_ELEMENT_NAME.equals(qName);
209 final boolean isLocalOnly = attributes.getValue("local-only") != null;
210 final String pkg = attributes.getValue(PKG_ATTRIBUTE_NAME);
211 final boolean regex = containsRegexAttribute(attributes);
212 final AbstractImportRule rule;
213 if (pkg == null) {
214
215
216 final String clazz = safeGet(attributes, "class");
217 rule = new ClassImportRule(isAllow, isLocalOnly, clazz, regex);
218 }
219 else {
220 final boolean exactMatch =
221 attributes.getValue("exact-match") != null;
222 rule = new PkgImportRule(isAllow, isLocalOnly, pkg, exactMatch, regex);
223 }
224 return rule;
225 }
226
227
228
229
230
231
232 private static boolean containsRegexAttribute(Attributes attributes) {
233 return attributes.getValue("regex") != null;
234 }
235
236 @Override
237 public void endElement(String namespaceUri, String localName,
238 String qName) {
239 if (SUBPACKAGE_ELEMENT_NAME.equals(qName) || FILE_ELEMENT_NAME.equals(qName)) {
240 stack.pop();
241 }
242 }
243
244
245
246
247
248
249
250 public static PkgImportControl load(URI uri) throws CheckstyleException {
251 try (InputStream inputStream = uri.toURL().openStream()) {
252 final InputSource source = new InputSource(inputStream);
253 return load(source, uri);
254 }
255 catch (MalformedURLException ex) {
256 throw new CheckstyleException("syntax error in url " + uri, ex);
257 }
258 catch (IOException ex) {
259 throw new CheckstyleException("unable to find " + uri, ex);
260 }
261 }
262
263
264
265
266
267
268
269
270 private static PkgImportControl load(InputSource source,
271 URI uri) throws CheckstyleException {
272 try {
273 final ImportControlLoader loader = new ImportControlLoader();
274 loader.parseInputSource(source);
275 return loader.getRoot();
276 }
277 catch (ParserConfigurationException | SAXException ex) {
278 throw new CheckstyleException("unable to parse " + uri
279 + " - " + ex.getMessage(), ex);
280 }
281 catch (IOException ex) {
282 throw new CheckstyleException("unable to read " + uri, ex);
283 }
284 }
285
286
287
288
289
290 private PkgImportControl getRoot() {
291 return (PkgImportControl) stack.peek();
292 }
293
294
295
296
297
298
299 private static MismatchStrategy getStrategyForImportControl(Attributes attributes) {
300 final String returnValue = attributes.getValue(STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME);
301 MismatchStrategy strategyOnMismatch = MismatchStrategy.DISALLOWED;
302 if (STRATEGY_ON_MISMATCH_ALLOWED_VALUE.equals(returnValue)) {
303 strategyOnMismatch = MismatchStrategy.ALLOWED;
304 }
305 return strategyOnMismatch;
306 }
307
308
309
310
311
312
313 private static MismatchStrategy getStrategyForSubpackage(Attributes attributes) {
314 final String returnValue = attributes.getValue(STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME);
315 MismatchStrategy strategyOnMismatch = MismatchStrategy.DELEGATE_TO_PARENT;
316 if (STRATEGY_ON_MISMATCH_ALLOWED_VALUE.equals(returnValue)) {
317 strategyOnMismatch = MismatchStrategy.ALLOWED;
318 }
319 else if (STRATEGY_ON_MISMATCH_DISALLOWED_VALUE.equals(returnValue)) {
320 strategyOnMismatch = MismatchStrategy.DISALLOWED;
321 }
322 return strategyOnMismatch;
323 }
324
325
326
327
328
329
330
331
332
333 private static String safeGet(Attributes attributes, String name)
334 throws SAXException {
335 final String returnValue = attributes.getValue(name);
336 if (returnValue == null) {
337
338
339 throw new SAXException("missing attribute " + name);
340 }
341 return returnValue;
342 }
343
344 }