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