Coverage Report - com.puppycrawl.tools.checkstyle.checks.imports.ImportControlLoader
 
Classes in this File Line Coverage Branch Coverage Complexity
ImportControlLoader
100%
82/82
100%
28/28
3.182
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 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.api.AbstractLoader;
 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 AbstractLoader {
 45  
     /** The public ID for the configuration dtd. */
 46  
     private static final String DTD_PUBLIC_ID_1_0 =
 47  
         "-//Puppy Crawl//DTD Import Control 1.0//EN";
 48  
 
 49  
     /** The public ID for the configuration dtd. */
 50  
     private static final String DTD_PUBLIC_ID_1_1 =
 51  
         "-//Puppy Crawl//DTD Import Control 1.1//EN";
 52  
 
 53  
     /** The public ID for the configuration dtd. */
 54  
     private static final String DTD_PUBLIC_ID_1_2 =
 55  
         "-//Puppy Crawl//DTD Import Control 1.2//EN";
 56  
 
 57  
     /** The public ID for the configuration dtd. */
 58  
     private static final String DTD_PUBLIC_ID_1_3 =
 59  
             "-//Puppy Crawl//DTD Import Control 1.3//EN";
 60  
 
 61  
     /** The resource for the configuration dtd. */
 62  
     private static final String DTD_RESOURCE_NAME_1_0 =
 63  
         "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_0.dtd";
 64  
 
 65  
     /** The resource for the configuration dtd. */
 66  
     private static final String DTD_RESOURCE_NAME_1_1 =
 67  
         "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_1.dtd";
 68  
 
 69  
     /** The resource for the configuration dtd. */
 70  
     private static final String DTD_RESOURCE_NAME_1_2 =
 71  
         "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_2.dtd";
 72  
 
 73  
     /** The resource for the configuration dtd. */
 74  
     private static final String DTD_RESOURCE_NAME_1_3 =
 75  
             "com/puppycrawl/tools/checkstyle/checks/imports/import_control_1_3.dtd";
 76  
 
 77  
     /** The map to lookup the resource name by the id. */
 78  2
     private static final Map<String, String> DTD_RESOURCE_BY_ID = new HashMap<>();
 79  
 
 80  
     /** Name for attribute 'pkg'. */
 81  
     private static final String PKG_ATTRIBUTE_NAME = "pkg";
 82  
 
 83  
     /** Name for attribute 'strategyOnMismatch'. */
 84  
     private static final String STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME = "strategyOnMismatch";
 85  
 
 86  
     /** Value "allowed" for attribute 'strategyOnMismatch'. */
 87  
     private static final String STRATEGY_ON_MISMATCH_ALLOWED_VALUE = "allowed";
 88  
 
 89  
     /** Value "disallowed" for attribute 'strategyOnMismatch'. */
 90  
     private static final String STRATEGY_ON_MISMATCH_DISALLOWED_VALUE = "disallowed";
 91  
 
 92  
     /** Qualified name for element 'subpackage'. */
 93  
     private static final String SUBPACKAGE_ELEMENT_NAME = "subpackage";
 94  
 
 95  
     /** Qualified name for element 'allow'. */
 96  
     private static final String ALLOW_ELEMENT_NAME = "allow";
 97  
 
 98  
     /** Used to hold the {@link ImportControl} objects. */
 99  30
     private final Deque<ImportControl> stack = new ArrayDeque<>();
 100  
 
 101  
     static {
 102  2
         DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_0, DTD_RESOURCE_NAME_1_0);
 103  2
         DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_1, DTD_RESOURCE_NAME_1_1);
 104  2
         DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_2, DTD_RESOURCE_NAME_1_2);
 105  2
         DTD_RESOURCE_BY_ID.put(DTD_PUBLIC_ID_1_3, DTD_RESOURCE_NAME_1_3);
 106  2
     }
 107  
 
 108  
     /**
 109  
      * Constructs an instance.
 110  
      * @throws ParserConfigurationException if an error occurs.
 111  
      * @throws SAXException if an error occurs.
 112  
      */
 113  
     private ImportControlLoader() throws ParserConfigurationException,
 114  
             SAXException {
 115  30
         super(DTD_RESOURCE_BY_ID);
 116  30
     }
 117  
 
 118  
     @Override
 119  
     public void startElement(String namespaceUri,
 120  
                              String localName,
 121  
                              String qName,
 122  
                              Attributes attributes)
 123  
             throws SAXException {
 124  409
         if ("import-control".equals(qName)) {
 125  26
             final String pkg = safeGet(attributes, PKG_ATTRIBUTE_NAME);
 126  26
             final MismatchStrategy strategyOnMismatch = getStrategyForImportControl(attributes);
 127  26
             final boolean regex = containsRegexAttribute(attributes);
 128  26
             stack.push(new ImportControl(pkg, regex, strategyOnMismatch));
 129  26
         }
 130  383
         else if (SUBPACKAGE_ELEMENT_NAME.equals(qName)) {
 131  45
             final String name = safeGet(attributes, "name");
 132  45
             final MismatchStrategy strategyOnMismatch = getStrategyForSubpackage(attributes);
 133  45
             final boolean regex = containsRegexAttribute(attributes);
 134  45
             final ImportControl parentImportControl = stack.peek();
 135  45
             final ImportControl importControl = new ImportControl(parentImportControl, name,
 136  
                     regex, strategyOnMismatch);
 137  45
             parentImportControl.addChild(importControl);
 138  45
             stack.push(importControl);
 139  45
         }
 140  338
         else if (ALLOW_ELEMENT_NAME.equals(qName) || "disallow".equals(qName)) {
 141  
             // Need to handle either "pkg" or "class" attribute.
 142  
             // May have "exact-match" for "pkg"
 143  
             // May have "local-only"
 144  337
             final boolean isAllow = ALLOW_ELEMENT_NAME.equals(qName);
 145  337
             final boolean isLocalOnly = attributes.getValue("local-only") != null;
 146  337
             final String pkg = attributes.getValue(PKG_ATTRIBUTE_NAME);
 147  337
             final boolean regex = containsRegexAttribute(attributes);
 148  
             final AbstractImportRule rule;
 149  337
             if (pkg == null) {
 150  
                 // handle class names which can be normal class names or regular
 151  
                 // expressions
 152  201
                 final String clazz = safeGet(attributes, "class");
 153  201
                 rule = new ClassImportRule(isAllow, isLocalOnly, clazz, regex);
 154  201
             }
 155  
             else {
 156  136
                 final boolean exactMatch =
 157  136
                         attributes.getValue("exact-match") != null;
 158  136
                 rule = new PkgImportRule(isAllow, isLocalOnly, pkg, exactMatch, regex);
 159  
             }
 160  337
             stack.peek().addImportRule(rule);
 161  
         }
 162  409
     }
 163  
 
 164  
     /**
 165  
      * Check if the given attributes contain the regex attribute.
 166  
      * @param attributes the attributes.
 167  
      * @return if the regex attribute is contained.
 168  
      */
 169  
     private static boolean containsRegexAttribute(Attributes attributes) {
 170  408
         return attributes.getValue("regex") != null;
 171  
     }
 172  
 
 173  
     @Override
 174  
     public void endElement(String namespaceUri, String localName,
 175  
         String qName) {
 176  409
         if (SUBPACKAGE_ELEMENT_NAME.equals(qName)) {
 177  45
             stack.pop();
 178  
         }
 179  409
     }
 180  
 
 181  
     /**
 182  
      * Loads the import control file from a file.
 183  
      * @param uri the uri of the file to load.
 184  
      * @return the root {@link ImportControl} object.
 185  
      * @throws CheckstyleException if an error occurs.
 186  
      */
 187  
     public static ImportControl load(URI uri) throws CheckstyleException {
 188  
 
 189  31
         InputStream inputStream = null;
 190  
         try {
 191  31
             inputStream = uri.toURL().openStream();
 192  29
             final InputSource source = new InputSource(inputStream);
 193  55
             return load(source, uri);
 194  
         }
 195  1
         catch (MalformedURLException ex) {
 196  1
             throw new CheckstyleException("syntax error in url " + uri, ex);
 197  
         }
 198  1
         catch (IOException ex) {
 199  1
             throw new CheckstyleException("unable to find " + uri, ex);
 200  
         }
 201  
         finally {
 202  31
             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  30
             final ImportControlLoader loader = new ImportControlLoader();
 217  30
             loader.parseInputSource(source);
 218  26
             return loader.getRoot();
 219  
         }
 220  3
         catch (ParserConfigurationException | SAXException ex) {
 221  3
             throw new CheckstyleException("unable to parse " + uri
 222  3
                     + " - " + ex.getMessage(), ex);
 223  
         }
 224  1
         catch (IOException ex) {
 225  1
             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  31
         if (inputStream != null) {
 237  
             try {
 238  29
                 inputStream.close();
 239  
             }
 240  1
             catch (IOException ex) {
 241  1
                 throw new CheckstyleException("unable to close input stream", ex);
 242  28
             }
 243  
         }
 244  30
     }
 245  
 
 246  
     /**
 247  
      * Returns root ImportControl.
 248  
      * @return the root {@link ImportControl} object loaded.
 249  
      */
 250  
     private ImportControl getRoot() {
 251  26
         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  26
         final String returnValue = attributes.getValue(STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME);
 261  26
         MismatchStrategy strategyOnMismatch = MismatchStrategy.DISALLOWED;
 262  26
         if (STRATEGY_ON_MISMATCH_ALLOWED_VALUE.equals(returnValue)) {
 263  2
             strategyOnMismatch = MismatchStrategy.ALLOWED;
 264  
         }
 265  26
         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  45
         final String returnValue = attributes.getValue(STRATEGY_ON_MISMATCH_ATTRIBUTE_NAME);
 275  45
         MismatchStrategy strategyOnMismatch = MismatchStrategy.DELEGATE_TO_PARENT;
 276  45
         if (STRATEGY_ON_MISMATCH_ALLOWED_VALUE.equals(returnValue)) {
 277  1
             strategyOnMismatch = MismatchStrategy.ALLOWED;
 278  
         }
 279  44
         else if (STRATEGY_ON_MISMATCH_DISALLOWED_VALUE.equals(returnValue)) {
 280  5
             strategyOnMismatch = MismatchStrategy.DISALLOWED;
 281  
         }
 282  45
         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  273
         final String returnValue = attributes.getValue(name);
 296  273
         if (returnValue == null) {
 297  
             // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
 298  
             // of the only method which calls the current one
 299  1
             throw new SAXException("missing attribute " + name);
 300  
         }
 301  272
         return returnValue;
 302  
     }
 303  
 }