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