001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle;
021
022import java.io.BufferedInputStream;
023import java.io.IOException;
024import java.io.InputStream;
025import java.net.URL;
026import java.util.ArrayDeque;
027import java.util.Deque;
028import java.util.Enumeration;
029import java.util.HashMap;
030import java.util.Iterator;
031import java.util.LinkedHashSet;
032import java.util.Map;
033import java.util.Set;
034
035import javax.xml.parsers.ParserConfigurationException;
036
037import org.xml.sax.Attributes;
038import org.xml.sax.InputSource;
039import org.xml.sax.SAXException;
040
041import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
042import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
043
044/**
045 * Loads a list of package names from a package name XML file.
046 */
047public final class PackageNamesLoader
048    extends XmlLoader {
049
050    /** The public ID for the configuration dtd. */
051    private static final String DTD_PUBLIC_ID =
052        "-//Puppy Crawl//DTD Package Names 1.0//EN";
053
054    /** The new public ID for the configuration dtd. */
055    private static final String DTD_PUBLIC_CS_ID =
056        "-//Checkstyle//DTD Package Names Configuration 1.0//EN";
057
058    /** The resource for the configuration dtd. */
059    private static final String DTD_RESOURCE_NAME =
060        "com/puppycrawl/tools/checkstyle/packages_1_0.dtd";
061
062    /** Name of default checkstyle package names resource file.
063     * The file must be in the classpath.
064     */
065    private static final String CHECKSTYLE_PACKAGES =
066        "checkstyle_packages.xml";
067
068    /** Qualified name for element 'package'. */
069    private static final String PACKAGE_ELEMENT_NAME = "package";
070
071    /** The temporary stack of package name parts. */
072    private final Deque<String> packageStack = new ArrayDeque<>();
073
074    /** The fully qualified package names. */
075    private final Set<String> packageNames = new LinkedHashSet<>();
076
077    /**
078     * Creates a new {@code PackageNamesLoader} instance.
079     * @throws ParserConfigurationException if an error occurs
080     * @throws SAXException if an error occurs
081     */
082    private PackageNamesLoader()
083            throws ParserConfigurationException, SAXException {
084        super(createIdToResourceNameMap());
085    }
086
087    @Override
088    public void startElement(String uri,
089                             String localName,
090                             String qName,
091                             Attributes attributes) {
092        if (PACKAGE_ELEMENT_NAME.equals(qName)) {
093            //push package name, name is mandatory attribute with not empty value by DTD
094            final String name = attributes.getValue("name");
095            packageStack.push(name);
096        }
097    }
098
099    /**
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}