View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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   * Responsible for loading the contents of an import control configuration file.
42   * @author Oliver Burn
43   */
44  final class ImportControlLoader extends XmlLoader {
45  
46      /** The public ID for the configuration dtd. */
47      private static final String DTD_PUBLIC_ID_1_0 =
48          "-//Puppy Crawl//DTD Import Control 1.0//EN";
49  
50      /** The public ID for the configuration dtd. */
51      private static final String DTD_PUBLIC_ID_1_1 =
52          "-//Puppy Crawl//DTD Import Control 1.1//EN";
53  
54      /** The public ID for the configuration dtd. */
55      private static final String DTD_PUBLIC_ID_1_2 =
56          "-//Puppy Crawl//DTD Import Control 1.2//EN";
57  
58      /** The public ID for the configuration dtd. */
59      private static final String DTD_PUBLIC_ID_1_3 =
60              "-//Puppy Crawl//DTD Import Control 1.3//EN";
61  
62      /** The resource for the configuration dtd. */
63      private static final String DTD_RESOURCE_NAME_1_0 =
64          "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_0.dtd";
65  
66      /** The resource for the configuration dtd. */
67      private static final String DTD_RESOURCE_NAME_1_1 =
68          "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_1.dtd";
69  
70      /** The resource for the configuration dtd. */
71      private static final String DTD_RESOURCE_NAME_1_2 =
72          "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_2.dtd";
73  
74      /** The resource for the configuration dtd. */
75      private static final String DTD_RESOURCE_NAME_1_3 =
76              "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_3.dtd";
77  
78      /** The map to lookup the resource name by the id. */
79      private static final Map<String, String> DTD_RESOURCE_BY_ID = new HashMap<>();
80  
81      /** Name for attribute 'pkg'. */
82      private static final String PKG_ATTRIBUTE_NAME = "pkg";
83  
84      /** Name for attribute 'strategyOnMismatch'. */
85      private static final String STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME = "strategyOnMismatch";
86  
87      /** Value "allowed" for attribute 'strategyOnMismatch'. */
88      private static final String STRATEGY_ON_MISMATCH_ALLOWED_VALUE = "allowed";
89  
90      /** Value "disallowed" for attribute 'strategyOnMismatch'. */
91      private static final String STRATEGY_ON_MISMATCH_DISALLOWED_VALUE = "disallowed";
92  
93      /** Qualified name for element 'subpackage'. */
94      private static final String SUBPACKAGE_ELEMENT_NAME = "subpackage";
95  
96      /** Qualified name for element 'allow'. */
97      private static final String ALLOW_ELEMENT_NAME = "allow";
98  
99      /** Used to hold the {@link ImportControl} objects. */
100     private final Deque<ImportControl> stack = new ArrayDeque<>();
101 
102     static {
103         DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_0, DTD_RESOURCE_NAME_1_0);
104         DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_1, DTD_RESOURCE_NAME_1_1);
105         DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_2, DTD_RESOURCE_NAME_1_2);
106         DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_3, DTD_RESOURCE_NAME_1_3);
107     }
108 
109     /**
110      * Constructs an instance.
111      * @throws ParserConfigurationException if an error occurs.
112      * @throws SAXException if an error occurs.
113      */
114     private ImportControlLoader() throws ParserConfigurationException,
115             SAXException {
116         super(DTD_RESOURCE_BY_ID);
117     }
118 
119     @Override
120     public void startElement(String namespaceUri,
121                              String localName,
122                              String qName,
123                              Attributes attributes)
124             throws SAXException {
125         if ("import-control".equals(qName)) {
126             final String pkg = safeGet(attributes, PKG_ATTRIBUTE_NAME);
127             final MismatchStrategy strategyOnMismatch = getStrategyForImportControl(attributes);
128             final boolean regex = containsRegexAttribute(attributes);
129             stack.push(new ImportControl(pkg, regex, strategyOnMismatch));
130         }
131         else if (SUBPACKAGE_ELEMENT_NAME.equals(qName)) {
132             final String name = safeGet(attributes, "name");
133             final MismatchStrategy strategyOnMismatch = getStrategyForSubpackage(attributes);
134             final boolean regex = containsRegexAttribute(attributes);
135             final ImportControl parentImportControl = stack.peek();
136             final ImportControl importControl = new ImportControl(parentImportControl, name,
137                     regex, strategyOnMismatch);
138             parentImportControl.addChild(importControl);
139             stack.push(importControl);
140         }
141         else if (ALLOW_ELEMENT_NAME.equals(qName) || "disallow".equals(qName)) {
142             // Need to handle either "pkg" or "class" attribute.
143             // May have "exact-match" for "pkg"
144             // May have "local-only"
145             final boolean isAllow = ALLOW_ELEMENT_NAME.equals(qName);
146             final boolean isLocalOnly = attributes.getValue("local-only") != null;
147             final String pkg = attributes.getValue(PKG_ATTRIBUTE_NAME);
148             final boolean regex = containsRegexAttribute(attributes);
149             final AbstractImportRule rule;
150             if (pkg == null) {
151                 // handle class names which can be normal class names or regular
152                 // expressions
153                 final String clazz = safeGet(attributes, "class");
154                 rule = new ClassImportRule(isAllow, isLocalOnly, clazz, regex);
155             }
156             else {
157                 final boolean exactMatch =
158                         attributes.getValue("exact-match") != null;
159                 rule = new PkgImportRule(isAllow, isLocalOnly, pkg, exactMatch, regex);
160             }
161             stack.peek().addImportRule(rule);
162         }
163     }
164 
165     /**
166      * Check if the given attributes contain the regex attribute.
167      * @param attributes the attributes.
168      * @return if the regex attribute is contained.
169      */
170     private static boolean containsRegexAttribute(Attributes attributes) {
171         return attributes.getValue("regex") != null;
172     }
173 
174     @Override
175     public void endElement(String namespaceUri, String localName,
176         String qName) {
177         if (SUBPACKAGE_ELEMENT_NAME.equals(qName)) {
178             stack.pop();
179         }
180     }
181 
182     /**
183      * Loads the import control file from a file.
184      * @param uri the uri of the file to load.
185      * @return the root {@link ImportControl} object.
186      * @throws CheckstyleException if an error occurs.
187      */
188     public static ImportControl load(URI uri) throws CheckstyleException {
189         InputStream inputStream = null;
190         try {
191             inputStream = uri.toURL().openStream();
192             final InputSource source = new InputSource(inputStream);
193             return load(source, uri);
194         }
195         catch (MalformedURLException ex) {
196             throw new CheckstyleException("syntax error in url " + uri, ex);
197         }
198         catch (IOException ex) {
199             throw new CheckstyleException("unable to find " + uri, ex);
200         }
201         finally {
202             closeStream(inputStream);
203         }
204     }
205 
206     /**
207      * Loads the import control file from a {@link InputSource}.
208      * @param source the source to load from.
209      * @param uri uri of the source being loaded.
210      * @return the root {@link ImportControl} object.
211      * @throws CheckstyleException if an error occurs.
212      */
213     private static ImportControl load(InputSource source,
214         URI uri) throws CheckstyleException {
215         try {
216             final ImportControlLoader loader = new ImportControlLoader();
217             loader.parseInputSource(source);
218             return loader.getRoot();
219         }
220         catch (ParserConfigurationException | SAXException ex) {
221             throw new CheckstyleException("unable to parse " + uri
222                     + " - " + ex.getMessage(), ex);
223         }
224         catch (IOException ex) {
225             throw new CheckstyleException("unable to read " + uri, ex);
226         }
227     }
228 
229     /**
230      * This method exists only due to bug in cobertura library
231      * https://github.com/cobertura/cobertura/issues/170
232      * @param inputStream the InputStream to close
233      * @throws CheckstyleException if an error occurs.
234      */
235     private static void closeStream(InputStream inputStream) throws CheckstyleException {
236         if (inputStream != null) {
237             try {
238                 inputStream.close();
239             }
240             catch (IOException ex) {
241                 throw new CheckstyleException("unable to close input stream", ex);
242             }
243         }
244     }
245 
246     /**
247      * Returns root ImportControl.
248      * @return the root {@link ImportControl} object loaded.
249      */
250     private ImportControl getRoot() {
251         return stack.peek();
252     }
253 
254     /**
255      * Utility to get a strategyOnMismatch property for "import-control" tag.
256      * @param attributes collect to get attribute from.
257      * @return the value of the attribute.
258      */
259     private static MismatchStrategy getStrategyForImportControl(Attributes attributes) {
260         final String returnValue = attributes.getValue(STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME);
261         MismatchStrategy strategyOnMismatch = MismatchStrategy.DISALLOWED;
262         if (STRATEGY_ON_MISMATCH_ALLOWED_VALUE.equals(returnValue)) {
263             strategyOnMismatch = MismatchStrategy.ALLOWED;
264         }
265         return strategyOnMismatch;
266     }
267 
268     /**
269      * Utility to get a strategyOnMismatch property for "subpackage" tag.
270      * @param attributes collect to get attribute from.
271      * @return the value of the attribute.
272      */
273     private static MismatchStrategy getStrategyForSubpackage(Attributes attributes) {
274         final String returnValue = attributes.getValue(STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME);
275         MismatchStrategy strategyOnMismatch = MismatchStrategy.DELEGATE_TO_PARENT;
276         if (STRATEGY_ON_MISMATCH_ALLOWED_VALUE.equals(returnValue)) {
277             strategyOnMismatch = MismatchStrategy.ALLOWED;
278         }
279         else if (STRATEGY_ON_MISMATCH_DISALLOWED_VALUE.equals(returnValue)) {
280             strategyOnMismatch = MismatchStrategy.DISALLOWED;
281         }
282         return strategyOnMismatch;
283     }
284 
285     /**
286      * Utility to safely get an attribute. If it does not exist an exception
287      * is thrown.
288      * @param attributes collect to get attribute from.
289      * @param name name of the attribute to get.
290      * @return the value of the attribute.
291      * @throws SAXException if the attribute does not exist.
292      */
293     private static String safeGet(Attributes attributes, String name)
294             throws SAXException {
295         final String returnValue = attributes.getValue(name);
296         if (returnValue == null) {
297             // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
298             // of the only method which calls the current one
299             throw new SAXException("missing attribute " + name);
300         }
301         return returnValue;
302     }
303 
304 }