<p>-version 3.17
<ul>
<li>OSGi bundle info is now included in the jar file.
- <li>JIRA JASSIST-166, 168 have been fixed.
+ <li>JIRA JASSIST-166, 168, 170 have been fixed.
</ul>
<p>-version 3.16.1 on March 6, 2012
*
* <p>Note that the returned string is not the type signature
* contained in the <code>SignatureAttirbute</code>. It is
- * a descriptor. To obtain a type signature, call the following
- * methods:
- *
- * <ul><pre>getMethodInfo().getAttribute(SignatureAttribute.tag)
- * </pre></ul>
+ * a descriptor.
*
* @see javassist.bytecode.Descriptor
- * @see javassist.bytecode.SignatureAttribute
+ * @see #getGenericSignature()
*/
public String getSignature() {
return methodInfo.getDescriptor();
}
+ /**
+ * Returns the generic signature of the method.
+ * It represents parameter types including type variables.
+ *
+ * @see SignatureAttribute#toMethodSignature(String)
+ * @since 3.17
+ */
+ public String getGenericSignature() {
+ SignatureAttribute sa
+ = (SignatureAttribute)methodInfo.getAttribute(SignatureAttribute.tag);
+ return sa == null ? null : sa.getSignature();
+ }
+
+ /**
+ * Set the generic signature of the method.
+ * It represents parameter types including type variables.
+ * See {@link javassist.CtClass#setGenericSignature(String)}
+ * for a code sample.
+ *
+ * @param sig a new generic signature.
+ * @see javassist.bytecode.SignatureAttribute.MethodSignature#encode()
+ * @since 3.17
+ */
+ public void setGenericSignature(String sig) {
+ declaringClass.checkModify();
+ methodInfo.addAttribute(new SignatureAttribute(methodInfo.getConstPool(), sig));
+ }
+
/**
* Obtains exceptions that this method/constructor may throw.
*
import javassist.bytecode.ClassFile;
import javassist.bytecode.Descriptor;
import javassist.bytecode.Opcode;
+import javassist.bytecode.SignatureAttribute;
import javassist.expr.ExprEditor;
/* Note:
qualifiedName = name;
}
+ /**
+ * Returns the generic signature of the class.
+ *
+ * <p>The generics of Java is implemented by the erasure technique.
+ * After compilation, all type parameters are dropped off from the
+ * main part of a class file. However, for reflection, the type
+ * parameters are encoded into generic signatures and attached
+ * to a class file.
+ *
+ * @return null if the generic signature is not included.
+ * @see javassist.bytecode.SignatureAttribute#toClassSignature(String)
+ * @see CtMember#getGenericSignature()
+ * @since 3.17
+ */
+ public String getGenericSignature() { return null; }
+
+ /**
+ * Sets the generic signature of the class.
+ *
+ * <p>The generics of Java is implemented by the erasure technique.
+ * After compilation, all type parameters are dropped off from the
+ * main part of a class file. However, for reflection, the type
+ * parameters must be encoded into generic signatures and attached
+ * to a class file.
+ *
+ * <p>For example,
+ *
+ * <pre>class List<T> {
+ * T value;
+ * T get() { return value; }
+ * void set(T v) { value = v; }
+ * }
+ * </pre>
+ *
+ * <p>this class is generated by the following code:
+ *
+ * <pre>
+ * ClassPool pool = ClassPool.getDefault();
+ * CtClass cc = pool.makeClass("List");
+ * CtClass objectClass = pool.get(CtClass.javaLangObject);
+ * ClassSignature cs = new ClassSignature(
+ * new TypeParameter[] { new TypeParameter("T") });
+ * cc.setGenericSignature(cs.encode()); // <T:Ljava/lang/Object;>Ljava/lang/Object;
+ *
+ * CtField f = new CtField(objClass, "value", cc);
+ * TypeVariable tvar = new TypeVariable("T");
+ * f.setGenericSignature(tvar.encode()); // TT;
+ * cc.addField(f);
+ *
+ * CtMethod m = CtNewMethod.make("public Object get(){return value;}", cc);
+ * MethodSignature ms = new MethodSignature(null, null, tvar, null);
+ * m.setGenericSignature(ms.encode()); // ()TT;
+ * cc.addMethod(m);
+ *
+ * CtMethod m2 = CtNewMethod.make("public void set(Object v){value = v;}", cc);
+ * MethodSignature ms2 = new MethodSignature(null, new Type[] { tvar },
+ * new BaseType("void"), null);
+ * m2.setGenericSignature(ms2.encode()); // (TT;)V;
+ * cc.addMethod(m2);
+ *
+ * cc.writeFile();
+ * </pre>
+ *
+ * <p>The generated class file is equivalent to the following:
+ *
+ * <pre>class List {
+ * Object value;
+ * Object get() { return value; }
+ * void set(Object v) { value = v; }
+ * }</pre>
+ *
+ * <p>but it includes generic signatures for the class, the field,
+ * and the methods so that the type variable <code>T</code> can be
+ * accessible through reflection.
+ *
+ * @param sig a generic signature.
+ * @see javassist.bytecode.SignatureAttribute.ClassSignature#encode()
+ * @see javassist.bytecode.SignatureAttribute.MethodSignature#encode()
+ * @see CtMember#setGenericSignature(String)
+ * @since 3.17
+ */
+ public void setGenericSignature(String sig) { checkModify(); }
+
/**
* Substitutes <code>newName</code> for all occurrences of a class
* name <code>oldName</code> in the class file.
import javassist.bytecode.InnerClassesAttribute;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.ParameterAnnotationsAttribute;
+import javassist.bytecode.SignatureAttribute;
import javassist.bytecode.annotation.Annotation;
import javassist.compiler.AccessorMaker;
import javassist.compiler.CompileError;
classPool.classNameChanged(oldname, this);
}
+ public String getGenericSignature() {
+ SignatureAttribute sa
+ = (SignatureAttribute)getClassFile2().getAttribute(SignatureAttribute.tag);
+ return sa == null ? null : sa.getSignature();
+ }
+
+ public void setGenericSignature(String sig) {
+ ClassFile cf = getClassFile();
+ SignatureAttribute sa = new SignatureAttribute(cf.getConstPool(), sig);
+ cf.addAttribute(sa);
+ }
+
public void replaceClassName(ClassMap classnames)
throws RuntimeException
{
*
* <p>Note that the returned string is not the type signature
* contained in the <code>SignatureAttirbute</code>. It is
- * a descriptor. To obtain a type signature, call the following
- * methods:
- *
- * <ul><pre>getFieldInfo().getAttribute(SignatureAttribute.tag)
- * </pre></ul>
+ * a descriptor.
*
* @see javassist.bytecode.Descriptor
- * @see javassist.bytecode.SignatureAttribute
+ * @see #getGenericSignature()
*/
public String getSignature() {
return fieldInfo.getDescriptor();
}
+ /**
+ * Returns the generic signature of the field.
+ * It represents a type including type variables.
+ *
+ * @see SignatureAttribute#toFieldSignature(String)
+ * @since 3.17
+ */
+ public String getGenericSignature() {
+ SignatureAttribute sa
+ = (SignatureAttribute)fieldInfo.getAttribute(SignatureAttribute.tag);
+ return sa == null ? null : sa.getSignature();
+ }
+
+ /**
+ * Set the generic signature of the field.
+ * It represents a type including type variables.
+ * See {@link javassist.CtClass#setGenericSignature(String)}
+ * for a code sample.
+ *
+ * @param sig a new generic signature.
+ * @see javassist.bytecode.SignatureAttribute.ObjectType#encode()
+ * @since 3.17
+ */
+ public void setGenericSignature(String sig) {
+ declaringClass.checkModify();
+ fieldInfo.addAttribute(new SignatureAttribute(fieldInfo.getConstPool(), sig));
+ }
+
/**
* Returns the type of the field.
*/
public String getSignature() { return null; }
public void setAttribute(String name, byte[] data) {}
public void setModifiers(int mod) {}
+ public String getGenericSignature() { return null; }
+ public void setGenericSignature(String sig) {}
private CtMember methodTail;
private CtMember consTail; // constructor tail
*/
public abstract String getSignature();
+ /**
+ * Returns the generic signature of the member.
+ *
+ * @see javassist.bytecode.SignatureAttribute#toFieldSignature(String)
+ * @see javassist.bytecode.SignatureAttribute#toMethodSignature(String)
+ * @see CtClass#getGenericSignature()
+ * @since 3.17
+ */
+ public abstract String getGenericSignature();
+
+ /**
+ * Sets the generic signature of the member.
+ *
+ * @param sig a new generic signature.
+ * @see javassist.bytecode.SignatureAttribute.ObjectType#encode()
+ * @see javassist.bytecode.SignatureAttribute.MethodSignature#encode()
+ * @see CtClass#setGenericSignature(String)
+ * @since 3.17
+ */
+ public abstract void setGenericSignature(String sig);
+
/**
* Obtains a user-defined attribute with the given name.
* If that attribute is not found in the class file, this
}
/**
- * Constructs a Signature attribute.
+ * Constructs a <code>Signature</code> attribute.
*
* @param cp a constant pool table.
* @param signature the signature represented by this attribute.
}
/**
- * Returns the signature indicated by <code>signature_index</code>.
+ * Returns the generic signature indicated by <code>signature_index</code>.
*
* @see #toClassSignature(String)
* @see #toMethodSignature(String)
+ * @see #toFieldSignature(String)
*/
public String getSignature() {
return getConstPool().getUtf8Info(ByteArray.readU16bit(get(), 0));
}
/**
- * Sets <code>signature_index</code> to the index of the given signature,
+ * Sets <code>signature_index</code> to the index of the given generic signature,
* which is added to a constant pool.
*
* @param sig new signature.
TypeParameter[] params;
ClassType superClass;
ClassType[] interfaces;
- ClassSignature(TypeParameter[] p, ClassType s, ClassType[] i) {
- params = p;
- superClass = s;
- interfaces = i;
+
+ /**
+ * Constructs a class signature.
+ *
+ * @param p type parameters.
+ * @param s the super class.
+ * @param i the interface types.
+ */
+ public ClassSignature(TypeParameter[] p, ClassType s, ClassType[] i) {
+ params = p == null ? new TypeParameter[0] : p;
+ superClass = s == null ? ClassType.OBJECT : s;
+ interfaces = i == null ? new ClassType[0] : i;
+ }
+
+ /**
+ * Constructs a class signature.
+ *
+ * @param p type parameters.
+ */
+ public ClassSignature(TypeParameter[] p) {
+ this(p, null, null);
}
/**
return sbuf.toString();
}
+
+ /**
+ * Returns the encoded string representing the method type signature.
+ */
+ public String encode() {
+ StringBuffer sbuf = new StringBuffer();
+ if (params.length > 0) {
+ sbuf.append('<');
+ for (int i = 0; i < params.length; i++)
+ params[i].encode(sbuf);
+
+ sbuf.append('>');
+ }
+
+ superClass.encode(sbuf);
+ for (int i = 0; i < interfaces.length; i++)
+ interfaces[i].encode(sbuf);
+
+ return sbuf.toString();
+ }
}
/**
Type retType;
ObjectType[] exceptions;
- MethodSignature(TypeParameter[] tp, Type[] p, Type ret, ObjectType[] ex) {
- typeParams = tp;
- params = p;
- retType = ret;
- exceptions = ex;
+ /**
+ * Constructs a method type signature. Any parameter can be null
+ * to represent <code>void</code> or nothing.
+ *
+ * @param tp type parameters.
+ * @param p parameter types.
+ * @param ret a return type.
+ * @param ex exception types.
+ */
+ public MethodSignature(TypeParameter[] tp, Type[] p, Type ret, ObjectType[] ex) {
+ typeParams = tp == null ? new TypeParameter[0] : tp;
+ params = p == null ? new Type[0] : p;
+ retType = ret == null ? new BaseType("void") : ret;
+ exceptions = ex == null ? new ObjectType[0] : ex;
}
/**
return sbuf.toString();
}
+
+ /**
+ * Returns the encoded string representing the method type signature.
+ */
+ public String encode() {
+ StringBuffer sbuf = new StringBuffer();
+ if (typeParams.length > 0) {
+ sbuf.append('<');
+ for (int i = 0; i < typeParams.length; i++)
+ typeParams[i].encode(sbuf);
+
+ sbuf.append('>');
+ }
+
+ sbuf.append('(');
+ for (int i = 0; i < params.length; i++)
+ params[i].encode(sbuf);
+
+ sbuf.append(')');
+ retType.encode(sbuf);
+ if (exceptions.length > 0)
+ for (int i = 0; i < exceptions.length; i++) {
+ sbuf.append('^');
+ exceptions[i].encode(sbuf);
+ }
+
+ return sbuf.toString();
+ }
}
/**
* Formal type parameters.
+ *
+ * @see TypeArgument
*/
public static class TypeParameter {
String name;
superInterfaces = si;
}
+ /**
+ * Constructs a <code>TypeParameter</code> representing a type parametre
+ * like <code><T extends ... ><code>.
+ *
+ * @param name parameter name.
+ * @param superClass an upper bound class-type (or null).
+ * @param superInterfaces an upper bound interface-type (or null).
+ */
+ public TypeParameter(String name, ObjectType superClass, ObjectType[] superInterfaces) {
+ this.name = name;
+ this.superClass = superClass;
+ if (superInterfaces == null)
+ this.superInterfaces = new ObjectType[0];
+ else
+ this.superInterfaces = superInterfaces;
+ }
+
+ /**
+ * Constructs a <code>TypeParameter</code> representing a type parameter
+ * like <code><T><code>.
+ *
+ * @param name parameter name.
+ */
+ public TypeParameter(String name) {
+ this(name, null, null);
+ }
+
/**
* Returns the name of the type parameter.
*/
/**
* Returns the class bound of this parameter.
- *
- * @return null if the class bound is not specified.
*/
public ObjectType getClassBound() { return superClass; }
sbuf.append('>');
}
+
+ void encode(StringBuffer sb) {
+ sb.append(name);
+ if (superClass == null)
+ sb.append(":Ljava/lang/Object;");
+ else {
+ sb.append(':');
+ superClass.encode(sb);
+ }
+
+ for (int i = 0; i < superInterfaces.length; i++) {
+ sb.append(':');
+ superInterfaces[i].encode(sb);
+ }
+ }
}
/**
* Type argument.
+ *
+ * @see TypeParameter
*/
public static class TypeArgument {
ObjectType arg;
wildcard = w;
}
+ /**
+ * Constructs a <code>TypeArgument</code>.
+ * A type argument is <code><String></code>, <code><int[]></code>,
+ * or a type variable <code><T></code>, etc.
+ *
+ * @param t a class type, an array type, or a type variable.
+ */
+ public TypeArgument(ObjectType t) {
+ this(t, ' ');
+ }
+
+ /**
+ * Constructs a <code>TypeArgument</code> representing <code><?></code>.
+ */
+ public TypeArgument() {
+ this(null, '*');
+ }
+
+ /**
+ * A factory method constructing a <code>TypeArgument</code> with an upper bound.
+ * It represents <code><? extends ... ></code>
+ *
+ * @param t an upper bound type.
+ */
+ public static TypeArgument subclassOf(ObjectType t) {
+ return new TypeArgument(t, '+');
+ }
+
+ /**
+ * A factory method constructing a <code>TypeArgument</code> with an lower bound.
+ * It represents <code><? super ... ></code>
+ *
+ * @param t an lower bbound type.
+ */
+ public static TypeArgument superOf(ObjectType t) {
+ return new TypeArgument(t, '-');
+ }
+
/**
* Returns the kind of this type argument.
*
else
return "? super " + type;
}
+
+ static void encode(StringBuffer sb, TypeArgument[] args) {
+ sb.append('<');
+ for (int i = 0; i < args.length; i++) {
+ TypeArgument ta = args[i];
+ if (ta.isWildcard())
+ sb.append(ta.wildcard);
+
+ if (ta.getType() != null)
+ ta.getType().encode(sb);
+ }
+
+ sb.append('>');
+ }
}
/**
* Primitive types and object types.
*/
public static abstract class Type {
+ abstract void encode(StringBuffer sb);
static void toString(StringBuffer sbuf, Type[] ts) {
for (int i = 0; i < ts.length; i++) {
if (i > 0)
char descriptor;
BaseType(char c) { descriptor = c; }
+ /**
+ * Constructs a <code>BaseType</code>.
+ *
+ * @param typeName <code>void</code>, <code>int</code>, ...
+ */
+ public BaseType(String typeName) {
+ this(Descriptor.of(typeName).charAt(0));
+ }
+
/**
* Returns the descriptor representing this primitive type.
*
public String toString() {
return Descriptor.toClassName(Character.toString(descriptor));
}
+
+ void encode(StringBuffer sb) {
+ sb.append(descriptor);
+ }
}
/**
* Class types, array types, and type variables.
+ * This class is also used for representing a field type.
*/
- public static abstract class ObjectType extends Type {}
+ public static abstract class ObjectType extends Type {
+ /**
+ * Returns the encoded string representing the object type signature.
+ */
+ public String encode() {
+ StringBuffer sb = new StringBuffer();
+ encode(sb);
+ return sb.toString();
+ }
+ }
/**
* Class types.
arguments = targs;
}
+ /**
+ * A class type representing <code>java.lang.Object</code>.
+ */
+ public static ClassType OBJECT = new ClassType("java.lang.Object", null);
+
+ /**
+ * Constructs a <code>ClassType</code>. It represents
+ * the name of a non-nested class.
+ *
+ * @param className a fully qualified class name.
+ * @param args type arguments or null.
+ */
+ public ClassType(String className, TypeArgument[] args) {
+ name = className;
+ arguments = args;
+ }
+
/**
* Returns the class name.
*/
return sbuf.toString();
}
+
+ void encode(StringBuffer sb) {
+ sb.append('L');
+ encode2(sb);
+ sb.append(';');
+ }
+
+ void encode2(StringBuffer sb) {
+ ClassType parent = getDeclaringClass();
+ if (parent != null) {
+ parent.encode2(sb);
+ sb.append('$');
+ }
+
+ sb.append(name.replace('.', '/'));
+ if (arguments != null)
+ TypeArgument.encode(sb, arguments);
+ }
}
/**
parent = p;
}
+ /**
+ * Constructs a <code>NestedClassType</code>.
+ *
+ * @param parent the class surrounding this class type.
+ * @param className a simple class name. It does not include
+ * a package name or a parent's class name.
+ * @param args type parameters or null.
+ */
+ public NestedClassType(ClassType parent, String className, TypeArgument[] args) {
+ super(className, args);
+ this.parent = parent;
+ }
+
/**
* Returns the class that declares this nested class.
* This nested class is a member of that declaring class.
int dim;
Type componentType;
+ /**
+ * Constructs an <code>ArrayType</code>.
+ *
+ * @param d dimension.
+ * @param comp the component type.
+ */
public ArrayType(int d, Type comp) {
dim = d;
componentType = comp;
return sbuf.toString();
}
+
+ void encode(StringBuffer sb) {
+ for (int i = 0; i < dim; i++)
+ sb.append('[');
+
+ componentType.encode(sb);
+ }
}
/**
name = sig.substring(begin, end);
}
+ /**
+ * Constructs a <code>TypeVariable</code>.
+ *
+ * @param name the name of a type variable.
+ */
+ public TypeVariable(String name) {
+ this.name = name;
+ }
+
/**
* Returns the variable name.
*/
public String toString() {
return name;
}
+
+ void encode(StringBuffer sb) {
+ sb.append('T').append(name).append(';');
+ }
}
/**
* Parses the given signature string as a class signature.
*
- * @param sig the signature.
+ * @param sig the signature obtained from the <code>SignatureAttribute</code>
+ * of a <code>ClassFile</code>.
+ * @return a tree-like data structure representing a class signature. It provides
+ * convenient accessor methods.
* @throws BadBytecode thrown when a syntactical error is found.
+ * @see #getSignature()
* @since 3.5
*/
public static ClassSignature toClassSignature(String sig) throws BadBytecode {
/**
* Parses the given signature string as a method type signature.
*
- * @param sig the signature.
+ * @param sig the signature obtained from the <code>SignatureAttribute</code>
+ * of a <code>MethodInfo</code>.
+ * @return @return a tree-like data structure representing a method signature. It provides
+ * convenient accessor methods.
* @throws BadBytecode thrown when a syntactical error is found.
+ * @see #getSignature()
* @since 3.5
*/
public static MethodSignature toMethodSignature(String sig) throws BadBytecode {
/**
* Parses the given signature string as a field type signature.
*
- * @param sig the signature string.
+ * @param sig the signature string obtained from the <code>SignatureAttribute</code>
+ * of a <code>FieldInfo</code>.
* @return the field type signature.
* @throws BadBytecode thrown when a syntactical error is found.
+ * @see #getSignature()
* @since 3.5
*/
public static ObjectType toFieldSignature(String sig) throws BadBytecode {
Object obj = make(cc.getName());
assertEquals(1, invoke(obj, "run"));
}
+
+ public void testGenericSignature() throws Exception {
+ CtClass cc = sloader.makeClass("test4.GenSig");
+ CtClass objClass = sloader.get(CtClass.javaLangObject);
+ SignatureAttribute.ClassSignature cs
+ = new SignatureAttribute.ClassSignature(
+ new SignatureAttribute.TypeParameter[] {
+ new SignatureAttribute.TypeParameter("T") });
+ cc.setGenericSignature(cs.encode()); // <T:Ljava/lang/Object;>Ljava/lang/Object;
+
+ CtField f = new CtField(objClass, "value", cc);
+ SignatureAttribute.TypeVariable tvar = new SignatureAttribute.TypeVariable("T");
+ f.setGenericSignature(tvar.encode()); // TT;
+ cc.addField(f);
+
+ CtMethod m = CtNewMethod.make("public Object get(){return value;}", cc);
+ SignatureAttribute.MethodSignature ms
+ = new SignatureAttribute.MethodSignature(null, null, tvar, null);
+ m.setGenericSignature(ms.encode()); // ()TT;
+ cc.addMethod(m);
+
+ CtMethod m2 = CtNewMethod.make("public void set(Object v){value = v;}", cc);
+ SignatureAttribute.MethodSignature ms2
+ = new SignatureAttribute.MethodSignature(null, new SignatureAttribute.Type[] { tvar },
+ new SignatureAttribute.BaseType("void"), null);
+ m2.setGenericSignature(ms2.encode()); // (TT;)V;
+ cc.addMethod(m2);
+
+ cc.writeFile();
+ Object obj = make(cc.getName());
+ Class clazz = obj.getClass();
+ assertEquals("T", clazz.getTypeParameters()[0].getName());
+ assertEquals("T", ((java.lang.reflect.TypeVariable)clazz.getDeclaredField("value").getGenericType()).getName());
+ java.lang.reflect.Method rm = clazz.getDeclaredMethod("get", new Class[0]);
+ assertEquals("T", ((java.lang.reflect.TypeVariable)rm.getGenericReturnType()).getName());
+ java.lang.reflect.Method rm2 = clazz.getDeclaredMethod("set", new Class[] { Object.class });
+ assertEquals("T", ((java.lang.reflect.TypeVariable)rm2.getGenericParameterTypes()[0]).getName());
+ }
}
<br>6. <a href="tutorial3.html#generics">Generics</a>
<br>7. <a href="tutorial3.html#varargs">Varargs</a>
<br>8. <a href="tutorial3.html#j2me">J2ME</a>
-<br>9. <a href="tutorial3.html#debug">Debug</a>
+<br>9. <a href="tutorial3.html#boxing">Boxing/Unboxing</a>
+<br>10. <a href="tutorial3.html#debug">Debug</a>
</ul>
<p><br>
<p><a href="#j2me">8. J2ME</a>
-<p><a href="#debug">9. Debug</a>
+<p><a href="#boxing">9. Boxing/Unboxing
+
+<p><a href="#debug">10. Debug</a>
<p><br>
<p>Note that no type parameters are necessary.
+<p>However, if you need to make type parameters accessible through reflection
+during runtime, you have to add generic signatures to the class file.
+For more details, see the API documentation (javadoc) of the
+<code>setGenericSignature</code> method in the <code>CtClass</code>.
+
<p><br>
<h2><a name="varargs">7. Varargs</a></h2>
<p><br>
-<h2><a name="debug">9. Debug</h2>
+<h2><a name="boxing">9. Boxing/Unboxing</h2>
+
+<p>Boxing and unboxing in Java are syntactic sugar. There is no bytecode for
+boxing or unboxing. So the compiler of Javassist does not support them.
+For example, the following statement is valid in Java:
+
+<ul><pre>
+Integer i = 3;
+</pre></ul>
+
+<p>since boxing is implicitly performed. For Javassist, however, you must explicitly
+convert a value type from <code>int</code> to <code>Integer</code>:
+
+<ul><pre>
+Integer i = new Integer(3);
+</pre></ul>
+
+<p><br>
+
+<h2><a name="debug">10. Debug</h2>
<p>Set <code>CtClass.debugDump</code> to a directory name.
Then all class files modified and generated by Javassist are saved in that