/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2004, University of Maryland
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.ba;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;

import edu.umd.cs.findbugs.FindBugs;
import edu.umd.cs.findbugs.util.Archive;

A work-alike class to use instead of BCEL's ClassPath class. The main difference is that URLClassPath can load classfiles from URLs.
Author:David Hovemeyer
/** * A work-alike class to use instead of BCEL's ClassPath class. The main * difference is that URLClassPath can load classfiles from URLs. * * @author David Hovemeyer */
public class URLClassPath implements Serializable { private static final long serialVersionUID = 1L;
Interface describing a single classpath entry.
/** * Interface describing a single classpath entry. */
private interface Entry {
Open an input stream to read a resource in the codebase described by this classpath entry.
Params:
  • resourceName – name of resource to load: e.g., "java/lang/Object.class"
Throws:
Returns:an InputStream, or null if the resource wasn't found
/** * Open an input stream to read a resource in the codebase described by * this classpath entry. * * @param resourceName * name of resource to load: e.g., "java/lang/Object.class" * @return an InputStream, or null if the resource wasn't found * @throws IOException * if an I/O error occurs */
public InputStream openStream(String resourceName) throws IOException;
Get filename or URL as string.
/** * Get filename or URL as string. */
public String getURL();
Close the underlying resource.
/** * Close the underlying resource. */
public void close(); }
Classpath entry class to load files from a zip/jar file in the local filesystem.
/** * Classpath entry class to load files from a zip/jar file in the local * filesystem. */
private static class LocalArchiveEntry implements Entry { private ZipFile zipFile; public LocalArchiveEntry(String fileName) throws IOException { try { zipFile = new ZipFile(fileName); } catch (IOException e) { IOException ioe = new IOException("Could not open archive file " + fileName); ioe.initCause(e); throw ioe; } } /* * (non-Javadoc) * * @see * edu.umd.cs.findbugs.URLClassPath.Entry#openStream(java.lang.String) */ @Override public InputStream openStream(String resourceName) throws IOException { ZipEntry zipEntry = zipFile.getEntry(resourceName); if (zipEntry == null) { return null; } return zipFile.getInputStream(zipEntry); } /* * (non-Javadoc) * * @see edu.umd.cs.findbugs.URLClassPath.Entry#getURL() */ @Override public String getURL() { return zipFile.getName(); } @Override public void close() { try { zipFile.close(); } catch (IOException e) { // Ignore } } }
Classpath entry class to load files from a directory in the local filesystem.
/** * Classpath entry class to load files from a directory in the local * filesystem. */
private static class LocalDirectoryEntry implements Entry { private final String dirName;
Constructor.
Params:
  • dirName – name of the local directory
Throws:
/** * Constructor. * * @param dirName * name of the local directory * @throws IOException * if dirName is not a directory */
public LocalDirectoryEntry(String dirName) throws IOException { this.dirName = dirName; if (!(new File(dirName).isDirectory())) { throw new IOException(dirName + " is not a directory"); } } /* * (non-Javadoc) * * @see * edu.umd.cs.findbugs.URLClassPath.Entry#openStream(java.lang.String) */ @Override public InputStream openStream(String resourceName) throws IOException { File file = new File(dirName, resourceName); if (!file.exists()) { return null; } return new BufferedInputStream(new FileInputStream(file)); } /* * (non-Javadoc) * * @see edu.umd.cs.findbugs.URLClassPath.Entry#getURL() */ @Override public String getURL() { return dirName; } @Override public void close() { // Nothing to do here } }
Classpath entry class to load files from a remote archive URL. It uses jar URLs to specify individual files within the remote archive.
/** * Classpath entry class to load files from a remote archive URL. It uses * jar URLs to specify individual files within the remote archive. */
private static class RemoteArchiveEntry implements Entry { private final URL remoteArchiveURL;
Constructor.
Params:
  • remoteArchiveURL – the remote zip/jar file URL
/** * Constructor. * * @param remoteArchiveURL * the remote zip/jar file URL */
public RemoteArchiveEntry(URL remoteArchiveURL) { this.remoteArchiveURL = remoteArchiveURL; } /* * (non-Javadoc) * * @see * edu.umd.cs.findbugs.URLClassPath.Entry#openStream(java.lang.String) */ @Override public InputStream openStream(String resourceName) throws IOException { URL remoteFileURL = new URL("jar:" + remoteArchiveURL.toString() + "/" + resourceName); try { return remoteFileURL.openStream(); } catch (IOException e) { return null; } } /* * (non-Javadoc) * * @see edu.umd.cs.findbugs.URLClassPath.Entry#getURL() */ @Override public String getURL() { return remoteArchiveURL.toString(); } @Override public void close() { // Nothing to do } }
Classpath entry class to load files from a remote directory URL.
/** * Classpath entry class to load files from a remote directory URL. */
private static class RemoteDirectoryEntry implements Entry { private final URL remoteDirURL;
Constructor.
Params:
  • remoteDirURL – URL of the remote directory; must end in "/"
/** * Constructor. * * @param remoteDirURL * URL of the remote directory; must end in "/" */
public RemoteDirectoryEntry(URL remoteDirURL) { this.remoteDirURL = remoteDirURL; } /* * (non-Javadoc) * * @see * edu.umd.cs.findbugs.URLClassPath.Entry#openStream(java.lang.String) */ @Override public InputStream openStream(String resourceName) throws IOException { URL remoteFileURL = new URL(remoteDirURL.toString() + resourceName); try { return remoteFileURL.openStream(); } catch (IOException e) { return null; } } /* * (non-Javadoc) * * @see edu.umd.cs.findbugs.URLClassPath.Entry#getURL() */ @Override public String getURL() { return remoteDirURL.toString(); } @Override public void close() { // Nothing to do } } // Fields private final List<Entry> entryList;
Constructor. Creates a classpath with no elements.
/** * Constructor. Creates a classpath with no elements. */
public URLClassPath() { this.entryList = new LinkedList<Entry>(); }
Add given filename/URL to the classpath. If no URL protocol is given, the filename is assumed to be a local file or directory. Remote directories must be specified with a "/" character at the end of the URL.
Params:
  • fileName – filename or URL of codebase (directory or archive file)
Throws:
/** * Add given filename/URL to the classpath. If no URL protocol is given, the * filename is assumed to be a local file or directory. Remote directories * must be specified with a "/" character at the end of the URL. * * @param fileName * filename or URL of codebase (directory or archive file) * @throws IOException * if entry is invalid or does not exist */
public void addURL(String fileName) throws IOException { String protocol = URLClassPath.getURLProtocol(fileName); if (protocol == null) { fileName = "file:" + fileName; protocol = "file"; } String fileExtension = URLClassPath.getFileExtension(fileName); boolean isArchive = fileExtension != null && URLClassPath.isArchiveExtension(fileExtension); Entry entry; if ("file".equals(protocol)) { String localFileName = fileName.substring("file:".length()); if (fileName.endsWith("/") || new File(localFileName).isDirectory()) { entry = new LocalDirectoryEntry(localFileName); } else if (isArchive) { entry = new LocalArchiveEntry(localFileName); } else { throw new IOException("Classpath entry " + fileName + " is not a directory or archive file"); } } else { if (fileName.endsWith("/")) { entry = new RemoteDirectoryEntry(new URL(fileName)); } else if (isArchive) { entry = new RemoteArchiveEntry(new URL(fileName)); } else { throw new IOException("Classpath entry " + fileName + " is not a remote directory or archive file"); } } entryList.add(entry); }
Return the classpath string.
Returns:the classpath string
/** * Return the classpath string. * * @return the classpath string */
public String getClassPath() { StringBuilder buf = new StringBuilder(); for (Entry entry : entryList) { if (buf.length() > 0) { buf.append(File.pathSeparator); } buf.append(entry.getURL()); } return buf.toString(); }
Open a stream to read given resource.
Params:
  • resourceName – name of resource to load, e.g. "java/lang/Object.class"
Throws:
  • IOException – if an IO error occurs trying to determine whether or not the resource exists
Returns:input stream to read resource, or null if resource could not be found
/** * Open a stream to read given resource. * * @param resourceName * name of resource to load, e.g. "java/lang/Object.class" * @return input stream to read resource, or null if resource could not be * found * @throws IOException * if an IO error occurs trying to determine whether or not the * resource exists */
private InputStream getInputStreamForResource(String resourceName) { // Try each classpath entry, in order, until we find one // that has the resource. Catch and ignore IOExceptions. // FIXME: The following code should throw IOException. // // URL.openStream() does not seem to distinguish // whether the resource does not exist, vs. some // transient error occurring while trying to access it. // This is unfortunate, because we really should throw // an exception out of this method in the latter case, // since it means our knowledge of the classpath is // incomplete. // // Short of reimplementing HTTP, etc., ourselves, // there is probably nothing we can do about this problem. for (Entry entry : entryList) { InputStream in; try { in = entry.openStream(resourceName); if (in != null) { if (URLClassPathRepository.DEBUG) { System.out.println("\t==> found " + resourceName + " in " + entry.getURL()); } return in; } } catch (IOException ignore) { // Ignore } } if (URLClassPathRepository.DEBUG) { System.out.println("\t==> could not find " + resourceName + " on classpath"); } return null; } private final Set<String> classesThatCantBeFound = new HashSet<String>();
Look up a class from the classpath.
Params:
  • className – name of class to look up
Throws:
Returns:the JavaClass object for the class
/** * Look up a class from the classpath. * * @param className * name of class to look up * @return the JavaClass object for the class * @throws ClassNotFoundException * if the class couldn't be found */
public JavaClass lookupClass(String className) throws ClassNotFoundException { if (classesThatCantBeFound.contains(className)) { throw new ClassNotFoundException("Error while looking for class " + className + ": class not found"); } String resourceName = className.replace('.', '/') + ".class"; InputStream in = null; boolean parsedClass = false; try { in = getInputStreamForResource(resourceName); if (in == null) { classesThatCantBeFound.add(className); throw new ClassNotFoundException("Error while looking for class " + className + ": class not found"); } ClassParser classParser = new ClassParser(in, resourceName); JavaClass javaClass = classParser.parse(); parsedClass = true; return javaClass; } catch (IOException e) { classesThatCantBeFound.add(className); throw new ClassNotFoundException("IOException while looking for class " + className, e); } finally { if (in != null && !parsedClass) { try { in.close(); } catch (IOException ignore) { // Ignore } } } }
Close all underlying resources.
/** * Close all underlying resources. */
public void close() { for (Entry entry : entryList) { entry.close(); } entryList.clear(); }
Get the URL protocol of given URL string.
Params:
  • urlString – the URL string
Returns:the protocol name ("http", "file", etc.), or null if there is no protocol
/** * Get the URL protocol of given URL string. * * @param urlString * the URL string * @return the protocol name ("http", "file", etc.), or null if there is no * protocol */
public static String getURLProtocol(String urlString) { String protocol = null; int firstColon = urlString.indexOf(':'); if (firstColon >= 0) { String specifiedProtocol = urlString.substring(0, firstColon); if (FindBugs.knownURLProtocolSet.contains(specifiedProtocol)) { protocol = specifiedProtocol; } } return protocol; }
Get the file extension of given fileName.
Returns:the file extension, or null if there is no file extension
/** * Get the file extension of given fileName. * * @return the file extension, or null if there is no file extension */
public static String getFileExtension(String fileName) { int lastDot = fileName.lastIndexOf('.'); return (lastDot >= 0) ? fileName.substring(lastDot) : null; }
Determine if given file extension indicates an archive file.
Params:
  • fileExtension – the file extension (e.g., ".jar")
Returns:true if the file extension indicates an archive, false otherwise
/** * Determine if given file extension indicates an archive file. * * @param fileExtension * the file extension (e.g., ".jar") * @return true if the file extension indicates an archive, false otherwise */
public static boolean isArchiveExtension(String fileExtension) { return Archive.ARCHIVE_EXTENSION_SET.contains(fileExtension); } }