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 exc) {
274             throw new CheckstyleException("unable to parse " + uri
275                     + " - " + exc.getMessage(), exc);
276         }
277         catch (IOException exc) {
278             throw new CheckstyleException("unable to read " + uri, exc);
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 exc) {
295             throw new CheckstyleException("syntax error in url " + uri, exc);
296         }
297         catch (IOException exc) {
298             throw new CheckstyleException("unable to find " + uri, exc);
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 }