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