View Javadoc
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;
21  
22  import java.io.BufferedInputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.net.URL;
26  import java.util.ArrayDeque;
27  import java.util.Deque;
28  import java.util.Enumeration;
29  import java.util.Iterator;
30  import java.util.LinkedHashSet;
31  import java.util.Set;
32  
33  import javax.xml.parsers.ParserConfigurationException;
34  
35  import org.xml.sax.Attributes;
36  import org.xml.sax.InputSource;
37  import org.xml.sax.SAXException;
38  
39  import com.google.common.io.Closeables;
40  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
41  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
42  
43  /**
44   * Loads a list of package names from a package name XML file.
45   * @author Rick Giles
46   */
47  public final class PackageNamesLoader
48      extends XmlLoader {
49      /** The public ID for the configuration dtd. */
50      private static final String DTD_PUBLIC_ID =
51          "-//Puppy Crawl//DTD Package Names 1.0//EN";
52  
53      /** The resource for the configuration dtd. */
54      private static final String DTD_RESOURCE_NAME =
55          "com/puppycrawl/tools/checkstyle/packages_1_0.dtd";
56  
57      /** Name of default checkstyle package names resource file.
58       * The file must be in the classpath.
59       */
60      private static final String CHECKSTYLE_PACKAGES =
61          "checkstyle_packages.xml";
62  
63      /** Qualified name for element 'package'. */
64      private static final String PACKAGE_ELEMENT_NAME = "package";
65  
66      /** The temporary stack of package name parts. */
67      private final Deque<String> packageStack = new ArrayDeque<>();
68  
69      /** The fully qualified package names. */
70      private final Set<String> packageNames = new LinkedHashSet<>();
71  
72      /**
73       * Creates a new {@code PackageNamesLoader} instance.
74       * @throws ParserConfigurationException if an error occurs
75       * @throws SAXException if an error occurs
76       */
77      private PackageNamesLoader()
78              throws ParserConfigurationException, SAXException {
79          super(DTD_PUBLIC_ID, DTD_RESOURCE_NAME);
80      }
81  
82      @Override
83      public void startElement(String uri,
84                               String localName,
85                               String qName,
86                               Attributes attributes) {
87          if (PACKAGE_ELEMENT_NAME.equals(qName)) {
88              //push package name, name is mandatory attribute with not empty value by DTD
89              final String name = attributes.getValue("name");
90              packageStack.push(name);
91          }
92      }
93  
94      /**
95       * Creates a full package name from the package names on the stack.
96       * @return the full name of the current package.
97       */
98      private String getPackageName() {
99          final StringBuilder buf = new StringBuilder(256);
100         final Iterator<String> iterator = packageStack.descendingIterator();
101         while (iterator.hasNext()) {
102             final String subPackage = iterator.next();
103             buf.append(subPackage);
104             if (!CommonUtils.endsWithChar(subPackage, '.') && iterator.hasNext()) {
105                 buf.append('.');
106             }
107         }
108         return buf.toString();
109     }
110 
111     @Override
112     public void endElement(String uri,
113                            String localName,
114                            String qName) {
115         if (PACKAGE_ELEMENT_NAME.equals(qName)) {
116 
117             packageNames.add(getPackageName());
118             packageStack.pop();
119         }
120     }
121 
122     /**
123      * Returns the set of package names, compiled from all
124      * checkstyle_packages.xml files found on the given class loaders
125      * classpath.
126      * @param classLoader the class loader for loading the
127      *          checkstyle_packages.xml files.
128      * @return the set of package names.
129      * @throws CheckstyleException if an error occurs.
130      */
131     public static Set<String> getPackageNames(ClassLoader classLoader)
132             throws CheckstyleException {
133 
134         final Set<String> result;
135         try {
136             //create the loader outside the loop to prevent PackageObjectFactory
137             //being created anew for each file
138             final PackageNamesLoader namesLoader = new PackageNamesLoader();
139 
140             final Enumeration<URL> packageFiles = classLoader.getResources(CHECKSTYLE_PACKAGES);
141 
142             while (packageFiles.hasMoreElements()) {
143                 processFile(packageFiles.nextElement(), namesLoader);
144             }
145 
146             result = namesLoader.packageNames;
147         }
148         catch (IOException ex) {
149             throw new CheckstyleException("unable to get package file resources", ex);
150         }
151         catch (ParserConfigurationException | SAXException ex) {
152             throw new CheckstyleException("unable to open one of package files", ex);
153         }
154 
155         return result;
156     }
157 
158     /**
159      * Reads the file provided and parses it with package names loader.
160      * @param packageFile file from package
161      * @param namesLoader package names loader
162      * @throws SAXException if an error while parsing occurs
163      * @throws CheckstyleException if unable to open file
164      */
165     private static void processFile(URL packageFile, PackageNamesLoader namesLoader)
166             throws SAXException, CheckstyleException {
167         InputStream stream = null;
168         try {
169             stream = new BufferedInputStream(packageFile.openStream());
170             final InputSource source = new InputSource(stream);
171             namesLoader.parseInputSource(source);
172         }
173         catch (IOException ex) {
174             throw new CheckstyleException("unable to open " + packageFile, ex);
175         }
176         finally {
177             Closeables.closeQuietly(stream);
178         }
179     }
180 }