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