/* * Javassist, a Java-bytecode translator toolkit. * Copyright (C) 1999-2003 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.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. * The source may be another ClassPool. If so, * write() is called on the source ClassPool * for obtaining a class file. * *

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. 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. * * @see javassist.CtClass * @see javassist.ClassPath * @see javassist.Translator */ public class ClassPool { /* If this field is null, then the object must be an instance of * ClassPoolTail. */ protected ClassPool source; protected Translator translator; protected Hashtable classes; // 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. * @see javassist.ClassPool#getDefault() */ public ClassPool(ClassPool src) { this(src, null); } /** * Creates a class pool. * * @param src the source of class files. If it is null, * the class search path is initially null. * @param trans the translator linked to this class pool. * It may be null. * @see javassist.ClassPool#getDefault() */ public ClassPool(ClassPool src, Translator trans) throws RuntimeException { classes = new Hashtable(); CtClass[] pt = CtClass.primitiveTypes; for (int i = 0; i < pt.length; ++i) classes.put(pt[i].getName(), pt[i]); if (src != null) source = src; else source = new ClassPoolTail(); translator = trans; if (trans != null) try { trans.start(this); } catch (Exception e) { throw new RuntimeException( "Translator.start() throws an exception: " + e.toString()); } } protected ClassPool() { source = null; classes = null; translator = null; } /** * Returns the default class pool. * The returned object is always identical. * *

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. * * @param t null or the translator linked to the class pool. */ public static synchronized ClassPool getDefault(Translator t) { if (defaultPool == null) { ClassPoolTail tail = new ClassPoolTail(); tail.appendSystemPath(); defaultPool = new ClassPool(tail, t); } return defaultPool; } private static ClassPool defaultPool = null; /** * Returns the default class pool. * The returned object is always identical. * *

This returns the result of getDefault(null). * * @see #getDefault(Translator) */ public static ClassPool getDefault() { return getDefault(null); } /** * Returns the class search path. */ public String toString() { return source.toString(); } /** * Returns the Translator object associated with * this ClassPool. */ public Translator getTranslator() { return translator; } /** * Table of registered cflow variables. */ private Hashtable cflow = null; // should be synchronous. /** * 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(); } } static class LocalClassLoader extends ClassLoader { public Class loadClass(String name, byte[] classfile) throws ClassFormatError { Class c = defineClass(name, classfile, 0, classfile.length); resolveClass(c); return c; } }; private static LocalClassLoader classLoader = new LocalClassLoader(); /** * Returns a java.lang.Class object that has been loaded * by writeAsClass(). Note that such a class cannot be * obtained by java.lang.Class.forName() because it has * been loaded by an internal class loader. * * @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. * 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. * Thus, if a class X loaded by the internal class * loader refers to a class Y, then the class * Y is loaded by the parent class loader. * * @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); write(classname, out, true); 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 { CtClass clazz = (CtClass)classes.get(classname); if (callback && translator != null && (clazz == null || !clazz.isFrozen())) { translator.onWrite(this, classname); // The CtClass object might be overwritten. clazz = (CtClass)classes.get(classname); } if (clazz == null || !clazz.isModified()) { if (clazz != null) clazz.freeze(); source.write(classname, out); } else clazz.toBytecode(out); } /* for CtClassType.getClassFile2() */ byte[] readSource(String classname) throws NotFoundException, IOException, CannotCompileException { return source.write(classname); } /* * Is invoked by CtClassType.setName(). */ synchronized void classNameChanged(String oldname, CtClass clazz) { CtClass c = (CtClass)classes.get(oldname); if (c == clazz) // must check this equation classes.remove(c); String newName = clazz.getName(); checkNotFrozen(newName, "the class with the new name is frozen."); classes.put(newName, clazz); } /* * Is invoked by CtClassType.setName() and methods in this class. */ void checkNotFrozen(String classname, String errmsg) throws RuntimeException { CtClass c = (CtClass)classes.get(classname); if (c != null && c.isFrozen()) throw new RuntimeException(errmsg); } /** * Reads a class file and constructs a CtClass * object with a new name. * This method is useful if that class file has been already * loaded and the resulting class is frozen. * * @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 = get0(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. * * @param classname a fully-qualified class name. */ public synchronized CtClass get(String classname) throws NotFoundException { CtClass clazz = (CtClass)classes.get(classname); if (clazz == null) { clazz = get0(classname); classes.put(classname, clazz); } return clazz; } private CtClass get0(String classname) throws NotFoundException { if (classname.endsWith("[]")) return new CtArray(classname, this); else { checkClassName(classname); return new CtClassType(classname, this); } } /** * 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 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. */ public CtClass makeClass(InputStream classfile) throws IOException, RuntimeException { CtClass clazz = new CtClassType(classfile, this); clazz.checkModify(); String classname = clazz.getName(); checkNotFrozen(classname, "there is a frozen class with the same name."); 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, "the class with the given name is frozen."); 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, "the interface with the given name is frozen."); CtClass clazz = new CtNewClass(name, this, true, superclass); classes.put(name, clazz); return clazz; } /** * Throws an exception if the class with the specified name does not * exist. */ void checkClassName(String classname) throws NotFoundException { source.checkClassName(classname); } /** * 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 synchronized 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; } } } }