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}