001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle;
020
021import com.puppycrawl.tools.checkstyle.api.Utils;
022
023import com.google.common.collect.Sets;
024import com.puppycrawl.tools.checkstyle.api.AbstractLoader;
025import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
026import com.puppycrawl.tools.checkstyle.api.FastStack;
027import java.io.BufferedInputStream;
028import java.io.IOException;
029import java.io.InputStream;
030import java.net.URL;
031import java.util.Enumeration;
032import java.util.Set;
033import javax.xml.parsers.ParserConfigurationException;
034import org.xml.sax.Attributes;
035import org.xml.sax.InputSource;
036import org.xml.sax.SAXException;
037
038/**
039 * Loads a list of package names from a package name XML file.
040 * @author Rick Giles
041 * @version 4-Dec-2002
042 */
043public final class PackageNamesLoader
044    extends AbstractLoader
045{
046    /** the public ID for the configuration dtd */
047    private static final String DTD_PUBLIC_ID =
048        "-//Puppy Crawl//DTD Package Names 1.0//EN";
049
050    /** the resource for the configuration dtd */
051    private static final String DTD_RESOURCE_NAME =
052        "com/puppycrawl/tools/checkstyle/packages_1_0.dtd";
053
054    /** Name of default checkstyle package names resource file.
055     * The file must be in the classpath.
056     */
057    private static final String CHECKSTYLE_PACKAGES =
058        "checkstyle_packages.xml";
059
060    /** The temporary stack of package name parts */
061    private final FastStack<String> packageStack = FastStack.newInstance();
062
063    /** The fully qualified package names. */
064    private final Set<String> packageNames = Sets.newLinkedHashSet();
065
066    /**
067     * Creates a new <code>PackageNamesLoader</code> instance.
068     * @throws ParserConfigurationException if an error occurs
069     * @throws SAXException if an error occurs
070     */
071    private PackageNamesLoader()
072        throws ParserConfigurationException, SAXException
073    {
074        super(DTD_PUBLIC_ID, DTD_RESOURCE_NAME);
075    }
076
077    /**
078     * Returns the set of fully qualified package names this
079     * this loader processed.
080     * @return the set of package names
081     */
082    private Set<String> getPackageNames()
083    {
084        return packageNames;
085    }
086
087    @Override
088    public void startElement(String namespaceURI,
089                             String localName,
090                             String qName,
091                             Attributes atts)
092        throws SAXException
093    {
094        if ("package".equals(qName)) {
095            //push package name
096            final String name = atts.getValue("name");
097            if (name == null) {
098                throw new SAXException("missing package name");
099            }
100            packageStack.push(name);
101        }
102    }
103
104    /**
105     * Creates a full package name from the package names on the stack.
106     * @return the full name of the current package.
107     */
108    private String getPackageName()
109    {
110        final StringBuffer buf = new StringBuffer();
111        for (String subPackage : packageStack) {
112            buf.append(subPackage);
113            if (!subPackage.endsWith(".")) {
114                buf.append(".");
115            }
116        }
117        return buf.toString();
118    }
119
120    @Override
121    public void endElement(String namespaceURI,
122                           String localName,
123                           String qName)
124    {
125        if ("package".equals(qName)) {
126
127            packageNames.add(getPackageName());
128            packageStack.pop();
129        }
130    }
131
132    /**
133     * Returns the set of package names, compiled from all
134     * checkstyle_packages.xml files found on the given classloaders
135     * classpath.
136     * @param classLoader the class loader for loading the
137     *          checkstyle_packages.xml files.
138     * @return the set of package names.
139     * @throws CheckstyleException if an error occurs.
140     */
141    public static Set<String> getPackageNames(ClassLoader classLoader)
142        throws CheckstyleException
143    {
144
145        Enumeration<URL> packageFiles = null;
146        try {
147            packageFiles = classLoader.getResources(CHECKSTYLE_PACKAGES);
148        }
149        catch (IOException e) {
150            throw new CheckstyleException(
151                    "unable to get package file resources", e);
152        }
153
154        //create the loader outside the loop to prevent PackageObjectFactory
155        //being created anew for each file
156        final PackageNamesLoader namesLoader = newPackageNamesLoader();
157
158        while ((null != packageFiles) && packageFiles.hasMoreElements()) {
159            final URL packageFile = packageFiles.nextElement();
160            InputStream stream = null;
161
162            try {
163                stream = new BufferedInputStream(packageFile.openStream());
164                final InputSource source = new InputSource(stream);
165                loadPackageNamesSource(source, "default package names",
166                    namesLoader);
167            }
168            catch (IOException e) {
169                throw new CheckstyleException(
170                        "unable to open " + packageFile, e);
171            }
172            finally {
173                Utils.closeQuietly(stream);
174            }
175        }
176        return namesLoader.getPackageNames();
177    }
178
179    /**
180     * Creates a PackageNamesLoader instance.
181     * @return the PackageNamesLoader
182     * @throws CheckstyleException if the creation failed
183     */
184    private static PackageNamesLoader newPackageNamesLoader()
185        throws CheckstyleException
186    {
187        try {
188            return new PackageNamesLoader();
189        }
190        catch (final ParserConfigurationException e) {
191            throw new CheckstyleException(
192                    "unable to create PackageNamesLoader ", e);
193        }
194        catch (final SAXException e) {
195            throw new CheckstyleException(
196                    "unable to create PackageNamesLoader - "
197                    + e.getMessage(), e);
198        }
199    }
200
201    /**
202     * Returns the list of package names in a specified source.
203     * @param source the source for the list.
204     * @param sourceName the name of the source.
205     * @param nameLoader the PackageNamesLoader instance
206     * @throws CheckstyleException if an error occurs.
207     */
208    private static void loadPackageNamesSource(
209            InputSource source, String sourceName,
210            PackageNamesLoader nameLoader)
211        throws CheckstyleException
212    {
213        try {
214            nameLoader.parseInputSource(source);
215        }
216        catch (final SAXException e) {
217            throw new CheckstyleException("unable to parse "
218                    + sourceName + " - " + e.getMessage(), e);
219        }
220        catch (final IOException e) {
221            throw new CheckstyleException("unable to read " + sourceName, e);
222        }
223    }
224}