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.Iterator;
030import java.util.LinkedHashSet;
031import java.util.Set;
032
033import javax.xml.parsers.ParserConfigurationException;
034
035import org.xml.sax.Attributes;
036import org.xml.sax.InputSource;
037import org.xml.sax.SAXException;
038
039import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
040import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
041
042/**
043 * Loads a list of package names from a package name XML file.
044 */
045public final class PackageNamesLoader
046    extends XmlLoader {
047
048    /** The public ID for the configuration dtd. */
049    private static final String DTD_PUBLIC_ID =
050        "-//Puppy Crawl//DTD Package Names 1.0//EN";
051
052    /** The resource for the configuration dtd. */
053    private static final String DTD_RESOURCE_NAME =
054        "com/puppycrawl/tools/checkstyle/packages_1_0.dtd";
055
056    /** Name of default checkstyle package names resource file.
057     * The file must be in the classpath.
058     */
059    private static final String CHECKSTYLE_PACKAGES =
060        "checkstyle_packages.xml";
061
062    /** Qualified name for element 'package'. */
063    private static final String PACKAGE_ELEMENT_NAME = "package";
064
065    /** The temporary stack of package name parts. */
066    private final Deque<String> packageStack = new ArrayDeque<>();
067
068    /** The fully qualified package names. */
069    private final Set<String> packageNames = new LinkedHashSet<>();
070
071    /**
072     * Creates a new {@code PackageNamesLoader} instance.
073     * @throws ParserConfigurationException if an error occurs
074     * @throws SAXException if an error occurs
075     */
076    private PackageNamesLoader()
077            throws ParserConfigurationException, SAXException {
078        super(DTD_PUBLIC_ID, DTD_RESOURCE_NAME);
079    }
080
081    @Override
082    public void startElement(String uri,
083                             String localName,
084                             String qName,
085                             Attributes attributes) {
086        if (PACKAGE_ELEMENT_NAME.equals(qName)) {
087            //push package name, name is mandatory attribute with not empty value by DTD
088            final String name = attributes.getValue("name");
089            packageStack.push(name);
090        }
091    }
092
093    /**
094     * Creates a full package name from the package names on the stack.
095     * @return the full name of the current package.
096     */
097    private String getPackageName() {
098        final StringBuilder buf = new StringBuilder(256);
099        final Iterator<String> iterator = packageStack.descendingIterator();
100        while (iterator.hasNext()) {
101            final String subPackage = iterator.next();
102            buf.append(subPackage);
103            if (!CommonUtils.endsWithChar(subPackage, '.') && iterator.hasNext()) {
104                buf.append('.');
105            }
106        }
107        return buf.toString();
108    }
109
110    @Override
111    public void endElement(String uri,
112                           String localName,
113                           String qName) {
114        if (PACKAGE_ELEMENT_NAME.equals(qName)) {
115            packageNames.add(getPackageName());
116            packageStack.pop();
117        }
118    }
119
120    /**
121     * Returns the set of package names, compiled from all
122     * checkstyle_packages.xml files found on the given class loaders
123     * classpath.
124     * @param classLoader the class loader for loading the
125     *          checkstyle_packages.xml files.
126     * @return the set of package names.
127     * @throws CheckstyleException if an error occurs.
128     */
129    public static Set<String> getPackageNames(ClassLoader classLoader)
130            throws CheckstyleException {
131        final Set<String> result;
132        try {
133            //create the loader outside the loop to prevent PackageObjectFactory
134            //being created anew for each file
135            final PackageNamesLoader namesLoader = new PackageNamesLoader();
136
137            final Enumeration<URL> packageFiles = classLoader.getResources(CHECKSTYLE_PACKAGES);
138
139            while (packageFiles.hasMoreElements()) {
140                processFile(packageFiles.nextElement(), namesLoader);
141            }
142
143            result = namesLoader.packageNames;
144        }
145        catch (IOException ex) {
146            throw new CheckstyleException("unable to get package file resources", ex);
147        }
148        catch (ParserConfigurationException | SAXException ex) {
149            throw new CheckstyleException("unable to open one of package files", ex);
150        }
151
152        return result;
153    }
154
155    /**
156     * Reads the file provided and parses it with package names loader.
157     * @param packageFile file from package
158     * @param namesLoader package names loader
159     * @throws SAXException if an error while parsing occurs
160     * @throws CheckstyleException if unable to open file
161     */
162    private static void processFile(URL packageFile, PackageNamesLoader namesLoader)
163            throws SAXException, CheckstyleException {
164        try (InputStream stream = new BufferedInputStream(packageFile.openStream())) {
165            final InputSource source = new InputSource(stream);
166            namesLoader.parseInputSource(source);
167        }
168        catch (IOException ex) {
169            throw new CheckstyleException("unable to open " + packageFile, ex);
170        }
171    }
172
173}