/* * Javassist, a Java-bytecode translator toolkit. * Copyright (C) 1999-2004 Shigeru Chiba. All Rights Reserved. * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. Alternatively, the contents of this file may be used under * the terms of the GNU Lesser General Public License Version 2.1 or later. * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. */ package javassist; import java.io.*; import java.net.URL; import java.util.Hashtable; /** * A driver class for controlling bytecode editing with Javassist. * It manages where a class file is obtained and how it is modified. * *

A ClassPool object can be regarded as a container * of CtClass objects. It reads class files on demand * from various * sources represented by ClassPath and create * CtClass objects representing those class files. * *

A CtClass * object contained in a ClassPool is written to an * output stream (or a file) if write() * (or writeFile()) is called on the * ClassPool. * write() is typically called by a class loader, * which obtains the bytecode image to be loaded. * *

The users can modify CtClass objects * before those objects are written out. * To obtain a reference * to a CtClass object contained in a * ClassPool, get() should be * called on the ClassPool. If a CtClass * object is modified, then the modification is reflected on the resulting * class file returned by write() in ClassPool. * *

In summary, * *

* *

The users can add a listener object receiving an event from a * ClassPool. An event occurs when a listener is * added to a ClassPool and when write() * is called on a ClassPool (not when get() * is called!). The listener class * must implement Translator. A typical listener object * is used for modifying a CtClass object on demand * when it is written to an output stream. * *

The implementation of this class is thread-safe. * *

Memory consumption memo: * *

ClassPool objects hold all the CtClasses * that have been created so that the consistency among modified classes * can be guaranteed. Thus if a large number of CtClasses * are processed, the ClassPool will consume a huge amount * of memory. To avoid this, multiple ClassPool objects * must be used. Note that getDefault() is a singleton * factory. * * * @see javassist.CtClass * @see javassist.ClassPath * @see javassist.Translator */ public class ClassPool extends AbsClassPool { protected AbsClassPool source; protected ClassPool parent; protected Translator translator; protected Hashtable classes; // should be synchronous /** * Table of registered cflow variables. */ private Hashtable cflow = null; // should be synchronous. /** * Creates a class pool. * * @param src the source of class files. If it is null, * the class search path is initially null. * @param p the parent of this class pool. * @see javassist.ClassPool#getDefault() */ public ClassPool(ClassPool p) { this(new ClassPoolTail(), p); } ClassPool(AbsClassPool src, ClassPool parent) { this.classes = new Hashtable(); this.source = src; this.parent = parent; if (parent == null) { // if this has no parent, it must include primitive types // even if this.source is not a ClassPoolTail. CtClass[] pt = CtClass.primitiveTypes; for (int i = 0; i < pt.length; ++i) classes.put(pt[i].getName(), pt[i]); } this.translator = null; this.cflow = null; } /** * Adds a new translator at the end of the translator chain. * * @param trans a new translator associated with this class pool. * @throws RuntimeException if trans.start() throws an exception. */ public void addTranslator(Translator trans) throws RuntimeException { ClassPool cp; if (translator == null) cp = this; else { ClassPool s = this; while (s.source instanceof ClassPool) s = (ClassPool)s.source; cp = new ClassPool(s.source, parent); s.source = cp; } cp.translator = trans; try { trans.start(cp); } catch (Exception e) { throw new RuntimeException( "Translator.start() throws an exception: " + e.toString()); } } /** * Returns the default class pool. * The returned object is always identical since this method is * a singleton factory. * *

The default class pool searches the system search path, * which usually includes the platform library, extension * libraries, and the search path specified by the * -classpath option or the CLASSPATH * environment variable. * *

When this method is called for the first time, the default * class pool is created with the following code snippet: * *

* *

If the default class pool cannot find any class files, * try ClassClassPath and LoaderClassPath. * * @param t null or the translator linked to the class pool. * @see ClassClassPath * @see LoaderClassPath */ public static synchronized ClassPool getDefault() { if (defaultPool == null) { defaultPool = new ClassPool(null); defaultPool.appendSystemPath(); } return defaultPool; } private static ClassPool defaultPool = null; /** * Provide a hook so that subclasses can do their own * caching of classes * * @see #removeCached(String) */ protected CtClass getCached(String classname) { return (CtClass)classes.get(classname); } /** * Provide a hook so that subclasses can do their own * caching of classes * * @see #getCached(String) */ protected void removeCached(String classname) { classes.remove(classname); } /** * Returns the class search path. */ public String toString() { return source.toString(); } /** * Records a name that never exists. * For example, a package name can be recorded by this method. * This would improve execution performance * since get() does not search the class path at all * if the given name is an invalid name recorded by this method. * Note that searching the class path takes relatively long time. * * @param name a class name (separeted by dot). */ public void recordInvalidClassName(String name) { source.recordInvalidClassName(name); } /** * Returns the Translator object associated with * this ClassPool. * * @deprecated */ Translator getTranslator() { return translator; } /** * Records the $cflow variable for the field specified * by cname and fname. * * @param name variable name * @param cname class name * @param fname field name */ void recordCflow(String name, String cname, String fname) { if (cflow == null) cflow = new Hashtable(); cflow.put(name, new Object[] { cname, fname }); } /** * Undocumented method. Do not use; internal-use only. * * @param name the name of $cflow variable */ public Object[] lookupCflow(String name) { if (cflow == null) cflow = new Hashtable(); return (Object[])cflow.get(name); } /** * Writes a class file specified with classname * in the current directory. * It never calls onWrite() on a translator. * It is provided for debugging. * * @param classname the name of the class written on a local disk. */ public void debugWriteFile(String classname) throws NotFoundException, CannotCompileException, IOException { debugWriteFile(classname, "."); } /** * Writes a class file specified with classname. * It never calls onWrite() on a translator. * It is provided for debugging. * * @param classname the name of the class written on a local disk. * @param directoryName it must end without a directory separator. */ public void debugWriteFile(String classname, String directoryName) throws NotFoundException, CannotCompileException, IOException { writeFile(classname, directoryName, false); } /* void writeFile(CtClass) should not be defined since writeFile() * may be called on the class pool that does not contain the given * CtClass object. */ /** * Writes a class file specified with classname * in the current directory. * It calls onWrite() on a translator. * * @param classname the name of the class written on a local disk. */ public void writeFile(String classname) throws NotFoundException, CannotCompileException, IOException { writeFile(classname, "."); } /** * Writes a class file specified with classname * on a local disk. * It calls onWrite() on a translator. * * @param classname the name of the class written on a local disk. * @param directoryName it must end without a directory separator. */ public void writeFile(String classname, String directoryName) throws NotFoundException, CannotCompileException, IOException { writeFile(classname, directoryName, true); } private void writeFile(String classname, String directoryName, boolean callback) throws NotFoundException, CannotCompileException, IOException { String filename = directoryName + File.separatorChar + classname.replace('.', File.separatorChar) + ".class"; int pos = filename.lastIndexOf(File.separatorChar); if (pos > 0) { String dir = filename.substring(0, pos); if (!dir.equals(".")) new File(dir).mkdirs(); } DataOutputStream out = new DataOutputStream(new BufferedOutputStream( new DelayedFileOutputStream(filename))); write(classname, out, callback); out.close(); } static class DelayedFileOutputStream extends OutputStream { private FileOutputStream file; private String filename; DelayedFileOutputStream(String name) { file = null; filename = name; } private void init() throws IOException { if (file == null) file = new FileOutputStream(filename); } public void write(int b) throws IOException { init(); file.write(b); } public void write(byte[] b) throws IOException { init(); file.write(b); } public void write(byte[] b, int off, int len) throws IOException { init(); file.write(b, off, len); } public void flush() throws IOException { init(); file.flush(); } public void close() throws IOException { init(); file.close(); } } /** * A simple class loader used by writeAsClass() * in ClassPool. * This class loader is provided for convenience. If you need more * complex functionality, you should write your own class loader. * * @see ClassPool#writeAsClass(String) * @see CtClass#toClass() */ public static class SimpleLoader extends ClassLoader { /** * Loads a class. * * @param name the fully qualified class name. * @param classfile the class file. * @throws ClassFormatError if the class file is wrong. */ public Class loadClass(String name, byte[] classfile) throws ClassFormatError { Class c = defineClass(name, classfile, 0, classfile.length); resolveClass(c); return c; } }; private static SimpleLoader classLoader = new SimpleLoader(); /** * Returns a java.lang.Class object that has been loaded * by writeAsClass(). Such an object cannot be * obtained by java.lang.Class.forName() because it has * been loaded by an internal class loader of Javassist. * * @see #writeAsClass(String) * @see javassist.CtClass#toClass() */ public static Class forName(String name) throws ClassNotFoundException { return classLoader.loadClass(name); } /** * Returns a java.lang.Class object. * It calls write() to obtain a class file and then * loads the obtained class file into the JVM. The returned * Class object represents the loaded class. * *

This method is provided for convenience. If you need more * complex functionality, you should write your own class loader. * *

To load a class file, this method uses an internal class loader, * which is an instance of ClassPool.SimpleLoader. * Thus, that class file is not loaded by the system class loader, * which should have loaded this ClassPool class. * The internal class loader * loads only the classes explicitly specified by this method * writeAsClass(). The other classes are loaded * by the parent class loader (the sytem class loader) by delegation. * *

For example, * *

* *

If the class Line is loaded by the internal class * loader and the class Point has not been loaded yet, * then the class Point that the class Line * refers to is loaded by the parent class loader. There is no * chance of modifying the definition of Point with * Javassist. * *

The internal class loader is shared among all the instances * of ClassPool. * * @param classname a fully-qualified class name. * * @see #forName(String) * @see javassist.CtClass#toClass() * @see javassist.Loader */ public Class writeAsClass(String classname) throws NotFoundException, IOException, CannotCompileException { try { return classLoader.loadClass(classname, write(classname)); } catch (ClassFormatError e) { throw new CannotCompileException(e, classname); } } /** * Returns a byte array representing the class file. * It calls onWrite() on a translator. * * @param classname a fully-qualified class name. */ public byte[] write(String classname) throws NotFoundException, IOException, CannotCompileException { ByteArrayOutputStream barray = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(barray); try { write(classname, out, true); } finally { out.close(); } return barray.toByteArray(); } /** * Writes a class file specified by classname * to a given output stream. * It calls onWrite() on a translator. * *

This method does not close the output stream in the end. * * @param classname a fully-qualified class name. * @param out an output stream */ public void write(String classname, DataOutputStream out) throws NotFoundException, CannotCompileException, IOException { write(classname, out, true); } private void write(String classname, DataOutputStream out, boolean callback) throws NotFoundException, CannotCompileException, IOException { if (!write0(classname, out, callback)) throw new NotFoundException(classname); } boolean write0(String classname, DataOutputStream out, boolean callback) throws NotFoundException, CannotCompileException, IOException { CtClass clazz = (CtClass)getCached(classname); if (callback && translator != null && (clazz == null || !clazz.isFrozen())) { translator.onWrite(this, classname); // The CtClass object might be overwritten. clazz = (CtClass)getCached(classname); } if (clazz == null || !clazz.isModified()) { if (clazz != null) clazz.freeze(); return source.write0(classname, out, callback); } else { clazz.toBytecode(out); return true; } } /* for CtClassType.getClassFile2(). Don't delegate to the parent. */ byte[] readSource(String classname) throws NotFoundException, IOException, CannotCompileException { return source.readSource(classname); } /* * Is invoked by CtClassType.setName(). Don't delegate to the parent. */ synchronized void classNameChanged(String oldname, CtClass clazz) { CtClass c = (CtClass)getCached(oldname); if (c == clazz) // must check this equation. removeCached(oldname); // see getAndRename(). String newName = clazz.getName(); checkNotFrozen(newName); classes.put(newName, clazz); } /* * Is invoked by CtClassType.setName() and methods in this class. * This method throws an exception if the class is already frozen or * if this class pool cannot edit the class since it is in a parent * class pool. */ void checkNotFrozen(String classname) throws RuntimeException { CtClass c; if (parent != null) { try { c = parent.get0(classname); } catch (NotFoundException e) { // some error happens. throw new RuntimeException(e.toString()); } if (c != null) throw new RuntimeException(classname + " is in a parent ClassPool. Use the parent."); } c = getCached(classname); if (c != null && c.isFrozen()) throw new RuntimeException(classname + ": frozen class (cannot edit)"); } /** * Reads a class file and constructs a CtClass * object with a new name. * This method is useful if you want to generate a new class as a copy * of another class (except the class name). For example, * *

* * returns a CtClass object representing Pair * class. The definition of Pair is the same as that of * Point class except the class name since Pair * is defined by reading Point.class. * * @param orgName the original (fully-qualified) class name * @param newName the new class name */ public CtClass getAndRename(String orgName, String newName) throws NotFoundException { CtClass clazz = null; if (parent != null) clazz = parent.get1(orgName); if (clazz == null) clazz = get1(orgName); if (clazz == null) throw new NotFoundException(orgName); clazz.setName(newName); // indirectly calls // classNameChanged() in this class return clazz; } /** * Reads a class file from the source and returns a reference * to the CtClass * object representing that class file. If that class file has been * already read, this method returns a reference to the * CtClass created when that class file was read at the * first time. * *

If classname ends with "[]", then this method * returns a CtClass object for that array type. * *

To obtain an inner class, use "$" instead of "." for separating * the enclosing class name and the inner class name. * * @param classname a fully-qualified class name. */ public CtClass get(String classname) throws NotFoundException { CtClass clazz = get0(classname); if (clazz == null) throw new NotFoundException(classname); else return clazz; } /** * @return null if the class could not be found. */ private synchronized CtClass get0(String classname) throws NotFoundException { CtClass clazz; clazz = getCached(classname); if (clazz != null) return clazz; if (parent != null) { clazz = parent.get0(classname); if (clazz != null) return clazz; } if (clazz == null) { clazz = get1(classname); if (clazz != null) classes.put(classname, clazz); } return clazz; } protected CtClass get1(String classname) throws NotFoundException { if (classname.endsWith("[]")) { String base = classname.substring(0, classname.indexOf('[')); if (getCached(base) == null && find(base) == null) return null; else return new CtArray(classname, this); } else if (find(classname) == null) return null; else return new CtClassType(classname, this); } /** * Obtains the URL of the class file specified by classname. * This method does not delegate to the parent pool. * * @param classname a fully-qualified class name. * @return null if the class file could not be found. */ URL find(String classname) { return source.find(classname); } /** * Reads class files from the source and returns an array of * CtClass * objects representing those class files. * *

If an element of classnames ends with "[]", * then this method * returns a CtClass object for that array type. * * @param classnames an array of fully-qualified class name. */ public CtClass[] get(String[] classnames) throws NotFoundException { if (classnames == null) return new CtClass[0]; int num = classnames.length; CtClass[] result = new CtClass[num]; for (int i = 0; i < num; ++i) result[i] = get(classnames[i]); return result; } /** * Reads a class file and obtains a compile-time method. * * @param classname the class name * @param methodname the method name * * @see CtClass#getDeclaredMethod(String) */ public CtMethod getMethod(String classname, String methodname) throws NotFoundException { CtClass c = get(classname); return c.getDeclaredMethod(methodname); } /** * Creates a new class (or interface) from the given class file. * If there already exists a class with the same name, the new class * overwrites that previous class. * *

This method is used for creating a CtClass object * directly from a class file. The qualified class name is obtained * from the class file; you do not have to explicitly give the name. * * @param classfile class file. * @exception RuntimeException if there is a frozen class with the * the same name. * @see javassist.ByteArrayClassPath */ public CtClass makeClass(InputStream classfile) throws IOException, RuntimeException { CtClass clazz = new CtClassType(classfile, this); clazz.checkModify(); String classname = clazz.getName(); checkNotFrozen(classname); classes.put(classname, clazz); return clazz; } /** * Creates a new public class. * If there already exists a class with the same name, the new class * overwrites that previous class. * * @param classname a fully-qualified class name. * @exception RuntimeException if the existing class is frozen. */ public CtClass makeClass(String classname) throws RuntimeException { return makeClass(classname, null); } /** * Creates a new public class. * If there already exists a class/interface with the same name, * the new class overwrites that previous class. * * @param classname a fully-qualified class name. * @param superclass the super class. * @exception RuntimeException if the existing class is frozen. */ public synchronized CtClass makeClass(String classname, CtClass superclass) throws RuntimeException { checkNotFrozen(classname); CtClass clazz = new CtNewClass(classname, this, false, superclass); classes.put(classname, clazz); return clazz; } /** * Creates a new public interface. * If there already exists a class/interface with the same name, * the new interface overwrites that previous one. * * @param name a fully-qualified interface name. * @exception RuntimeException if the existing interface is frozen. */ public CtClass makeInterface(String name) throws RuntimeException { return makeInterface(name, null); } /** * Creates a new public interface. * If there already exists a class/interface with the same name, * the new interface overwrites that previous one. * * @param name a fully-qualified interface name. * @param superclass the super interface. * @exception RuntimeException if the existing interface is frozen. */ public synchronized CtClass makeInterface(String name, CtClass superclass) throws RuntimeException { checkNotFrozen(name); CtClass clazz = new CtNewClass(name, this, true, superclass); classes.put(name, clazz); return clazz; } /** * Appends the system search path to the end of the * search path. The system search path * usually includes the platform library, extension * libraries, and the search path specified by the * -classpath option or the CLASSPATH * environment variable. * * @return the appended class path. */ public ClassPath appendSystemPath() { return source.appendSystemPath(); } /** * Insert a ClassPath object at the head of the * search path. * * @return the inserted class path. * * @see javassist.ClassPath * @see javassist.URLClassPath * @see javassist.ByteArrayClassPath */ public ClassPath insertClassPath(ClassPath cp) { return source.insertClassPath(cp); } /** * Appends a ClassPath object to the end of the * search path. * * @return the appended class path. * * @see javassist.ClassPath * @see javassist.URLClassPath * @see javassist.ByteArrayClassPath */ public ClassPath appendClassPath(ClassPath cp) { return source.appendClassPath(cp); } /** * Inserts a directory or a jar (or zip) file at the head of the * search path. * * @param pathname the path name of the directory or jar file. * It must not end with a path separator ("/"). * @return the inserted class path. * @exception NotFoundException if the jar file is not found. */ public ClassPath insertClassPath(String pathname) throws NotFoundException { return source.insertClassPath(pathname); } /** * Appends a directory or a jar (or zip) file to the end of the * search path. * * @param pathname the path name of the directory or jar file. * It must not end with a path separator ("/"). * @return the appended class path. * @exception NotFoundException if the jar file is not found. */ public ClassPath appendClassPath(String pathname) throws NotFoundException { return source.appendClassPath(pathname); } /** * Detatches the ClassPath object from the search path. * The detached ClassPath object cannot be added * to the pathagain. */ public void removeClassPath(ClassPath cp) { source.removeClassPath(cp); } /** * Appends directories and jar files for search. * *

The elements of the given path list must be separated by colons * in Unix or semi-colons in Windows. * * @param pathlist a (semi)colon-separated list of * the path names of directories and jar files. * The directory name must not end with a path * separator ("/"). * * @exception NotFoundException if a jar file is not found. */ public void appendPathList(String pathlist) throws NotFoundException { char sep = File.pathSeparatorChar; int i = 0; for (;;) { int j = pathlist.indexOf(sep, i); if (j < 0) { appendClassPath(pathlist.substring(i)); break; } else { appendClassPath(pathlist.substring(i, j)); i = j + 1; } } } }