Mirror of the BLOAT repository
https://www.cs.purdue.edu/homes/hosking/bloat/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
463 lines
12 KiB
463 lines
12 KiB
/**
|
|
* All files in the distribution of BLOAT (Bytecode Level Optimization and
|
|
* Analysis tool for Java(tm)) are Copyright 1997-2001 by the Purdue
|
|
* Research Foundation of Purdue University. All rights reserved.
|
|
*
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
package EDU.purdue.cs.bloat.file;
|
|
|
|
import java.io.*;
|
|
import java.util.*;
|
|
import java.util.zip.*;
|
|
|
|
import EDU.purdue.cs.bloat.reflect.*;
|
|
|
|
/**
|
|
* ClassFileLoder provides an interface for loading classes from files. The
|
|
* actual loading is done by the ClassFile itself.
|
|
* <p>
|
|
* Classes may be specified by their full package name (<tt>java.lang.String</tt>),
|
|
* or by the name of their class file (<tt>myclasses/Test.class</tt>). The
|
|
* class path may contain directories or Zip or Jar files. Any classes that are
|
|
* written back to disk ("committed") are placed in the output directory.
|
|
*
|
|
* @author Nate Nystrom (<a
|
|
* href="mailto:nystrom@cs.purdue.edu">nystrom@cs.purdue.edu</a>)
|
|
*/
|
|
public class ClassFileLoader implements ClassInfoLoader {
|
|
public static boolean DEBUG = false;
|
|
|
|
public static boolean USE_SYSTEM_CLASSES = true;
|
|
|
|
private File outputDir; // Directory in which to write committed class files
|
|
|
|
private String classpath; // Path to search for classes
|
|
|
|
private Map openZipFiles; // zip files to search for class files
|
|
|
|
private LinkedList cache; // We keep a cache of CACHE_LIMIT class files
|
|
|
|
private boolean verbose;
|
|
|
|
private static final int CACHE_LIMIT = 10;
|
|
|
|
/**
|
|
* Constructor. The classpath initially consists of the contents of the
|
|
* <tt>java.class.path</tt> and <tt>sun.boot.class.path</tt> system
|
|
* properties.
|
|
*/
|
|
public ClassFileLoader() {
|
|
outputDir = new File(".");
|
|
classpath = System.getProperty("java.class.path");
|
|
classpath += File.pathSeparator
|
|
+ System.getProperty("sun.boot.class.path");
|
|
if (ClassFileLoader.USE_SYSTEM_CLASSES) {
|
|
classpath += File.pathSeparator
|
|
+ System.getProperty("java.sys.class.path");
|
|
}
|
|
openZipFiles = new HashMap();
|
|
cache = new LinkedList();
|
|
verbose = false;
|
|
}
|
|
|
|
public void setVerbose(final boolean verbose) {
|
|
this.verbose = verbose;
|
|
}
|
|
|
|
/**
|
|
* Sets the classpath.
|
|
*/
|
|
public void setClassPath(final String classpath) {
|
|
this.classpath = classpath;
|
|
}
|
|
|
|
/**
|
|
* Adds to the classpath (CLASSPATH = CLASSPATH + morePath).
|
|
*/
|
|
public void appendClassPath(final String morePath) {
|
|
this.classpath += File.pathSeparator + morePath;
|
|
}
|
|
|
|
/**
|
|
* Adds to the classpath (CLASSPATH = morePath + CLASSPATH).
|
|
*/
|
|
public void prependClassPath(final String morePath) {
|
|
this.classpath = morePath + File.pathSeparator + this.classpath;
|
|
}
|
|
|
|
/**
|
|
* Returns the path used to search for class files.
|
|
*/
|
|
public String getClassPath() {
|
|
return (this.classpath);
|
|
}
|
|
|
|
/**
|
|
* Load the class from a stream.
|
|
*
|
|
* @param inputFile
|
|
* The file from which to load the class.
|
|
* @param stream
|
|
* The stream from which to load the class.
|
|
* @return A ClassInfo for the class.
|
|
* @exception ClassNotFoundException
|
|
* The class cannot be found in the class path.
|
|
*/
|
|
private ClassInfo loadClassFromStream(final File inputFile,
|
|
final InputStream stream) throws ClassNotFoundException {
|
|
|
|
final DataInputStream in = new DataInputStream(stream);
|
|
final ClassFile file = new ClassFile(inputFile, this, in);
|
|
|
|
return file;
|
|
}
|
|
|
|
/**
|
|
* Load the class from the file.
|
|
*
|
|
* @param file
|
|
* The File from which to load a class.
|
|
* @return A ClassInfo for the class.
|
|
* @exception ClassNotFoundException
|
|
* The class cannot be found in the class path.
|
|
*/
|
|
private ClassInfo loadClassFromFile(final File file)
|
|
throws ClassNotFoundException {
|
|
try {
|
|
final InputStream in = new FileInputStream(file);
|
|
|
|
final ClassInfo info = loadClassFromStream(file, in);
|
|
|
|
if (verbose) {
|
|
System.out.println("[Loaded " + info.name() + " from "
|
|
+ file.getPath() + "]");
|
|
}
|
|
|
|
try {
|
|
in.close();
|
|
} catch (final IOException ex) {
|
|
}
|
|
|
|
return info;
|
|
} catch (final FileNotFoundException e) {
|
|
throw new ClassNotFoundException(file.getPath());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads all of the classes that are contained in a zip (or jar) file.
|
|
* Returns an array of the <tt>ClassInfo</tt>s for the classes in the zip
|
|
* file.
|
|
*/
|
|
public ClassInfo[] loadClassesFromZipFile(final ZipFile zipFile)
|
|
throws ClassNotFoundException {
|
|
final ClassInfo[] infos = new ClassInfo[zipFile.size()];
|
|
|
|
// Examine each entry in the zip file
|
|
final Enumeration entries = zipFile.entries();
|
|
for (int i = 0; entries.hasMoreElements(); i++) {
|
|
final ZipEntry entry = (ZipEntry) entries.nextElement();
|
|
if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
final InputStream stream = zipFile.getInputStream(entry);
|
|
final File file = new File(entry.getName());
|
|
|
|
infos[i] = loadClassFromStream(file, stream);
|
|
|
|
} catch (final IOException ex) {
|
|
System.err.println("IOException: " + ex);
|
|
}
|
|
}
|
|
|
|
return (infos);
|
|
}
|
|
|
|
public ClassInfo newClass(final int modifiers, final int classIndex,
|
|
final int superClassIndex, final int[] interfaceIndexes,
|
|
final List constants) {
|
|
return new ClassFile(modifiers, classIndex, superClassIndex,
|
|
interfaceIndexes, constants, this);
|
|
}
|
|
|
|
/**
|
|
* Loads the class with the given name. Searches the class path, including
|
|
* zip files, for the class and then returns a data stream for the class
|
|
* file.
|
|
*
|
|
* @param name
|
|
* The name of the class to load, including the package name.
|
|
* @return A ClassInfo for the class.
|
|
* @exception ClassNotFoundException
|
|
* The class cannot be found in the class path.
|
|
*/
|
|
public ClassInfo loadClass(String name) throws ClassNotFoundException {
|
|
ClassInfo file = null;
|
|
|
|
// Check to see if name ends with ".class". If so, load the class from
|
|
// that file. Note that this is okay because we can never have a class
|
|
// named "class" (i.e. a class named "class" with a lower-case 'c' can
|
|
// never be specified in a fully-specified java class name) because
|
|
// "class" is a reserved word.
|
|
|
|
if (name.endsWith(".class")) {
|
|
final File nameFile = new File(name);
|
|
|
|
if (!nameFile.exists()) {
|
|
throw new ClassNotFoundException(name);
|
|
|
|
} else {
|
|
return (loadClassFromFile(nameFile));
|
|
}
|
|
}
|
|
|
|
// Otherwise, we have a (possibly fully-specified) class name.
|
|
name = name.replace('.', '/');
|
|
|
|
// Check the cache for the class file.
|
|
if (ClassFileLoader.DEBUG) {
|
|
System.out
|
|
.println(" Looking for " + name + " in cache = " + cache);
|
|
}
|
|
|
|
final Iterator iter = cache.iterator();
|
|
|
|
while (iter.hasNext()) {
|
|
file = (ClassFile) iter.next();
|
|
|
|
if (name.equals(file.name())) {
|
|
if (ClassFileLoader.DEBUG) {
|
|
System.out.println(" Found " + file.name() + " in cache");
|
|
}
|
|
|
|
// Move to the front of the cache.
|
|
iter.remove();
|
|
cache.addFirst(file);
|
|
|
|
return file;
|
|
}
|
|
}
|
|
|
|
file = null;
|
|
|
|
final String classFile = name.replace('/', File.separatorChar)
|
|
+ ".class";
|
|
|
|
// For each entry in the class path, search zip files and directories
|
|
// for classFile. When found, open an InputStream and break
|
|
// out of the loop to read the class file.
|
|
final String path = classpath + File.pathSeparator;
|
|
|
|
if (ClassFileLoader.DEBUG) {
|
|
System.out.println("CLASSPATH = " + path);
|
|
}
|
|
|
|
int index = 0;
|
|
int end = path.indexOf(File.pathSeparator, index);
|
|
|
|
SEARCH: while (end >= 0) {
|
|
final String dir = path.substring(index, end);
|
|
|
|
File f = new File(dir);
|
|
|
|
if (f.isDirectory()) {
|
|
// The directory is really a directory. If the class file
|
|
// exists, open a stream and return.
|
|
f = new File(dir, classFile);
|
|
|
|
if (f.exists()) {
|
|
try {
|
|
final InputStream in = new FileInputStream(f);
|
|
|
|
if (verbose) {
|
|
System.out.println(" [Loaded " + name + " from "
|
|
+ f.getPath() + "]");
|
|
}
|
|
|
|
file = loadClassFromStream(f, in);
|
|
|
|
try {
|
|
in.close();
|
|
|
|
} catch (final IOException ex) {
|
|
}
|
|
|
|
break SEARCH;
|
|
|
|
} catch (final FileNotFoundException ex) {
|
|
}
|
|
}
|
|
|
|
} else if (dir.endsWith(".zip") || dir.endsWith(".jar")) {
|
|
// Maybe a zip file?
|
|
try {
|
|
ZipFile zip = (ZipFile) openZipFiles.get(dir);
|
|
|
|
if (zip == null) {
|
|
zip = new ZipFile(f);
|
|
openZipFiles.put(dir, zip);
|
|
}
|
|
|
|
final String zipEntry = classFile.replace(
|
|
File.separatorChar, '/');
|
|
|
|
final ZipEntry entry = zip.getEntry(zipEntry);
|
|
|
|
if (entry != null) {
|
|
// Found the class file in the zip file.
|
|
// Open a stream and return.
|
|
if (verbose) {
|
|
System.out.println(" [Loaded " + name + " from "
|
|
+ f.getPath() + "]");
|
|
}
|
|
|
|
final InputStream in = zip.getInputStream(entry);
|
|
file = loadClassFromStream(f, in);
|
|
|
|
try {
|
|
in.close();
|
|
|
|
} catch (final IOException ex) {
|
|
}
|
|
break SEARCH;
|
|
}
|
|
} catch (final ZipException ex) {
|
|
} catch (final IOException ex) {
|
|
}
|
|
}
|
|
|
|
index = end + 1;
|
|
end = path.indexOf(File.pathSeparator, index);
|
|
}
|
|
|
|
if (file == null) {
|
|
// The class file wasn't in the class path. Try the currnet
|
|
// directory. If not there, give up.
|
|
final File f = new File(classFile);
|
|
|
|
if (!f.exists()) {
|
|
throw new ClassNotFoundException(name);
|
|
}
|
|
|
|
if (verbose) {
|
|
System.out.println(" [Loaded " + name + " from " + f.getPath()
|
|
+ "]");
|
|
}
|
|
|
|
try {
|
|
final InputStream in = new FileInputStream(f);
|
|
file = loadClassFromStream(f, in);
|
|
|
|
try {
|
|
in.close();
|
|
} catch (final IOException ex) {
|
|
}
|
|
} catch (final FileNotFoundException ex) {
|
|
throw new ClassNotFoundException(name);
|
|
}
|
|
}
|
|
|
|
if (file == null) {
|
|
throw new ClassNotFoundException(name);
|
|
}
|
|
|
|
// If we've reached the cache size limit, remove the oldest file
|
|
// in the cache. Then add the new file.
|
|
if (cache.size() == ClassFileLoader.CACHE_LIMIT) {
|
|
cache.removeLast();
|
|
}
|
|
|
|
cache.addFirst(file);
|
|
|
|
return file;
|
|
}
|
|
|
|
/**
|
|
* Set the directory into which commited class files should be written.
|
|
*
|
|
* @param dir
|
|
* The directory.
|
|
*/
|
|
public void setOutputDir(final File dir) {
|
|
outputDir = dir;
|
|
}
|
|
|
|
/**
|
|
* Get the directory into which commited class files should be written.
|
|
*/
|
|
public File outputDir() {
|
|
return outputDir;
|
|
}
|
|
|
|
/**
|
|
* Writes a bunch of <code>byte</code>s to an output entry with the given
|
|
* name.
|
|
*/
|
|
public void writeEntry(final byte[] bytes, final String name)
|
|
throws IOException {
|
|
final OutputStream os = outputStreamFor(name);
|
|
os.write(bytes);
|
|
os.flush();
|
|
os.close();
|
|
}
|
|
|
|
/**
|
|
* Returns an <tt>OutputStream</tt> to which a class file should be
|
|
* written.
|
|
*/
|
|
public OutputStream outputStreamFor(final ClassInfo info)
|
|
throws IOException {
|
|
// Format the name of the output file
|
|
final String name = info.name().replace('/', File.separatorChar)
|
|
+ ".class";
|
|
return outputStreamFor(name);
|
|
}
|
|
|
|
/**
|
|
* Returns an <code>OutputStream</code> to which somed named entity is
|
|
* written. Any forward slashes in the name are replaced by
|
|
* <code>File.separatorChar</code>.
|
|
*/
|
|
protected OutputStream outputStreamFor(String name) throws IOException {
|
|
|
|
name = name.replace('/', File.separatorChar);
|
|
|
|
final File f = new File(outputDir, name);
|
|
|
|
if (f.exists()) {
|
|
f.delete();
|
|
}
|
|
|
|
final File dir = new File(f.getParent());
|
|
dir.mkdirs();
|
|
|
|
if (!dir.exists()) {
|
|
throw new RuntimeException("Couldn't create directory: " + dir);
|
|
}
|
|
|
|
return (new FileOutputStream(f));
|
|
}
|
|
|
|
/**
|
|
* Signifies that we are done with this <code>ClassFileLoader</code>
|
|
*/
|
|
public void done() throws IOException {
|
|
// Nothing for this guy
|
|
}
|
|
}
|
|
|