5.2 Altering a method body
CtMethod
and CtConstructor
provide
setBody()
for substituting a whole
method body. They compile the given source text into Java bytecode
and substitutes it for the original method body. If the given source
text is null
, the substituted body includes only a
return
statement, which returns zero or null unless the
result type is void
.
In the source text given to setBody()
, the identifiers
starting with $
have special meaning
$0 , $1 , $2 , ...     |
Actual parameters |
$args |
An array of parameters.
The type of $args is Object[] .
|
$$ |
All actual parameters.
|
$cflow( ...) |
cflow variable |
$r |
The result type. It is used in a cast expression. |
$w |
The wrapper type. It is used in a cast expression. |
$sig |
An array of java.lang.Class objects representing
the formal parameter types.
|
$type |
A java.lang.Class object representing
the formal result type. |
$class |
A java.lang.Class object representing
the class currently edited. |
Note that $_
is not available.
Substituting source text for an existing expression
Javassist allows modifying only an expression included in a method body.
javassist.expr.ExprEditor
is a class
for replacing an expression in a method body.
The users can define a subclass of ExprEditor
to specify how an expression is modified.
To run an ExprEditor
object, the users must
call instrument()
in CtMethod
or
CtClass
.
For example,
CtMethod cm = ... ;
cm.instrument(
new ExprEditor() {
public void edit(MethodCall m)
throws CannotCompileException
{
if (m.getClassName().equals("Point")
&& m.getMethodName().equals("move"))
m.replace("{ $1 = 0; $_ = $proceed($$); }");
}
});
searches the method body represented by cm
and
replaces all calls to move()
in class Point
with a block:
{ $1 = 0; $_ = $proceed($$); }
so that the first parameter to move()
is always 0.
Note that the substituted code is not an expression but
a statement or a block.
The method instrument()
searches a method body.
If it finds an expression such as a method call, field access, and object
creation, then it calls edit()
on the given
ExprEditor
object. The parameter to edit()
is an object representing the found expression. The edit()
method can inspect and replace the expression through that object.
Calling replace()
on the parameter to edit()
substitutes the given statement or block for the expression. If the given
block is an empty block, that is, if replace("{}")
is executed, then the expression is removed from the method body.
If you want to insert a statement (or a block) before/after the
expression, a block like the following should be passed to
replace()
:
{ before-statements;
$_ = $proceed($$);
after-statements; }
whichever the expression is either a method call, field access,
object creation, or others. The second statement could be:
$_ = $proceed();
if the expression is read access, or
$proceed($$);
if the expression is write access.
Local variables available in the target expression is
also available in the source text passed to replace()
if the method searched by instrument()
was compiled
with the -g option (the class file includes a local variable
attribute).
javassist.expr.MethodCall
A MethodCall
object represents a method call.
The method replace()
in
MethodCall
substitutes a statement or
a block for the method call.
It receives source text representing the substitued statement or
block, in which the identifiers starting with $
have special meaning as in the source text passed to
insertBefore()
.
$0 |
The target object of the method call.
This is not equivalent to this , which represents
the caller-side this object.
$0 is null if the method is static.
|
  |
  |
$1 , $2 , ...     |
The parameters of the method call.
|
$_ |
The resulting value of the method call. |
$r |
The result type of the method call. |
$class     |
A java.lang.Class object representing
the class declaring the method.
|
$sig     |
An array of java.lang.Class objects representing
the formal parameter types. |
$type     |
A java.lang.Class object representing
the formal result type. |
$proceed     |
The name of the method originally called
in the expression. |
Here the method call means the one represented by the
MethodCall
object.
The other identifiers such as $w
,
$args
and $$
are also available.
Unless the result type of the method call is void
,
a value must be assigned to
$_
in the source text and the type of $_
is the result type.
If the result type is void
, the type of $_
is Object
and the value assigned to $_
is ignored.
$proceed
is not a String
value but special
syntax. It must be followed by an argument list surrounded by parentheses
( )
.
javassist.expr.FieldAccess
A FieldAccess
object represents field access.
The method edit()
in ExprEditor
receives this object if field access is found.
The method replace()
in
FieldAccess
receives
source text representing the substitued statement or
block for the field access.
In the source text, the identifiers starting with $
have also special meaning:
$0 |
The object containing the field accessed by the expression.
This is not equivalent to this .
this represents the object that the method including the
expression is invoked on.
$0 is null if the field is static.
|
  |
  |
$1 |
The value that would be stored in the field
if the expression is write access.
Otherwise, $1 is not available.
|
  |
$_ |
The resulting value of the field access
if the expression is read access.
Otherwise, the value stored in $_ is discarded.
|
  |
$r |
The type of the field if the expression is read access.
Otherwise, $r is void .
|
  |
$class     |
A java.lang.Class object representing
the class declaring the field.
|
$type |
A java.lang.Class object representing
the field type. |
$proceed     |
The name of a virtual method executing the original
field access.
. |
The other identifiers such as $w
,
$args
and $$
are also available.
If the expression is read access, a value must be assigned to
$_
in the source text. The type of $_
is the type of the field.
javassist.expr.NewExpr
A NewExpr
object represents object creation
with the new
operator.
The method edit()
in ExprEditor
receives this object if object creation is found.
The method replace()
in
NewExpr
receives
source text representing the substitued statement or
block for the object creation.
In the source text, the identifiers starting with $
have also special meaning:
$0 |
null .
|
$1 , $2 , ...     |
The parameters to the constructor.
|
$_ |
The resulting value of the object creation.
A newly created object must be stored in this variable.
|
  |
$r |
The type of the created object.
|
$class     |
A java.lang.Class object representing
the class of the created object.
|
$sig     |
An array of java.lang.Class objects representing
the formal parameter types. |
$proceed     |
The name of a virtual method executing the original
object creation.
. |
The other identifiers such as $w
,
$args
and $$
are also available.
javassist.expr.Instanceof
A Instanceof
object represents an instanceof
expression.
The method edit()
in ExprEditor
receives this object if an instanceof expression is found.
The method replace()
in
Instanceof
receives
source text representing the substitued statement or
block for the expression.
In the source text, the identifiers starting with $
have also special meaning:
$0 |
null .
|
$1 |
The value on the left hand side of the original
instanceof operator.
|
$_ |
The resulting value of the expression.
The type of $_ is boolean .
|
$r |
The type on the right hand side of the instanceof operator.
|
$type |
A java.lang.Class object representing
the type on the right hand side of the instanceof operator.
|
$proceed     |
The name of a virtual method executing the original
instanceof expression.
It takes one parameter (the type is java.lang.Object )
and returns true
if the parameter value is an instance of the type on the right
hand side of
the original instanceof operator.
Otherwise, it returns false.
|
  |
  |
  |
The other identifiers such as $w
,
$args
and $$
are also available.
javassist.expr.Cast
A Cast
object represents an expression for
explicit type casting.
The method edit()
in ExprEditor
receives this object if explicit type casting is found.
The method replace()
in
Cast
receives
source text representing the substitued statement or
block for the expression.
In the source text, the identifiers starting with $
have also special meaning:
$0 |
null .
|
$1 |
The value the type of which is explicitly cast.
|
$_ |
The resulting value of the expression.
The type of $_ is the same as the type
after the explicit casting, that is, the type surrounded
by ( ) .
|
  |
$r |
the type after the explicit casting, or the type surrounded
by ( ) .
|
$type |
A java.lang.Class object representing
the same type as $r .
|
$proceed     |
The name of a virtual method executing the original
type casting.
It takes one parameter of the type java.lang.Object
and returns it after
the explicit type casting specified by the original expression.
|
  |
  |
The other identifiers such as $w
,
$args
and $$
are also available.
javassist.expr.Handler
A Handler
object represents a catch
clause of try-catch
statement.
The method edit()
in ExprEditor
receives this object if a catch
is found.
The method insertBefore()
in
Handler
compiles the received
source text and inserts it at the beginning of the catch
clause.
In the source text, the identifiers starting with $
have special meaning:
$1 |
The exception object caught by the catch clause.
|
$r |
the type of the exception caught by the catch clause.
It is used in a cast expression.
|
$w |
The wrapper type. It is used in a cast expression.
|
$type     |
A java.lang.Class object representing
the type of the exception caught by the catch clause.
|
  |
If a new exception object is assigned to $1
,
it is passed to the original catch
clause as the caught
exception.
5.3 Adding a new method or field
Adding a method
Javassist allows the users to create a new method and constructor
from scratch. CtNewMethod
and CtNewConstructor
provide several factory methods,
which are static methods for creating CtMethod
or
CtConstructor
objects.
Especially, make()
creates
a CtMethod
or CtConstructor
object
from the given source text.
For example, this program:
CtClass point = ClassPool.getDefault().get("Point");
CtMethod m = CtNewMethod.make(
"public int xmove(int dx) { x += dx; }",
point);
point.addMethod(m);
adds a public method xmove()
to class Point
.
In this example, x
is a int
field in
the class Point
.
The source text passed to make()
can include the
identifiers starting with $
except $_
as in setBody()
.
It can also include
$proceed
if the target object and the target method name
are also given to make()
. For example,
CtClass point = ClassPool.getDefault().get("Point");
CtMethod m = CtNewMethod.make(
"public int ymove(int dy) { $proceed(0, dy); }",
point, "this", "move");
this program creates a method ymove()
defined below:
public int ymove(int dy) { this.move(0, dy); }
Note that $proceed
has been replaced with
this.move
.
Javassist provides another way to add a new method.
You can first create an abstract method and later give it a method body:
CtClass cc = ... ;
CtMethod m = new CtMethod(CtClass.intType, "move",
new CtClass[] { CtClass.intType }, cc);
cc.addMethod(m);
m.setBody("{ x += $1; }");
cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT);
Since Javassist makes a class abstract if an abstract method is
added to the class, you have to explicitly change the class back to a
non-abstract one after calling setBody()
.
Mutual recursive methods
Javassist cannot compile a method if it calls another method that
has not been added to a class. (Javassist can compile a method that
calls itself recursively.) To add mutual recursive methods to a class,
you need a trick shown below. Suppose that you want to add methods
m()
and n()
to a class represented
by cc
:
CtClass cc = ... ;
CtMethod m = CtNewMethod.make("public abstract int m(int i);", cc);
CtMethod n = CtNewMethod.make("public abstract int n(int i);", cc);
cc.addMethod(m);
cc.addMethod(n);
m.setBody("{ return ($1 <= 0) ? 1 : (n($1 - 1) * $1); }");
n.setBody("{ return m($1); }");
cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT);
You must first make two abstract methods and add them to the class.
Then you can give the method bodies to these methods even if the method
bodies include method calls to each other. Finally you must change the
class to a not-abstract class since addMethod()
automatically
changes a class into an abstract one if an abstract method is added.
Adding a field
Javassist also allows the users to create a new field.
CtClass point = ClassPool.getDefault().get("Point");
CtField f = new CtField(CtClass.intType, "z", point);
point.addField(f);
This program adds a field named z
to class
Point
.
If the initial value of the added field must be specified,
the program shown above must be modified into:
CtClass point = ClassPool.getDefault().get("Point");
CtField f = new CtField(CtClass.intType, "z", point);
point.addField(f, "0"); // initial value is 0.
Now, the method addField()
receives the second parameter,
which is the source text representing an expression computing the initial
value. This source text can be any Java expression if the result type
of the expression matches the type of the field. Note that an expression
does not end with a semi colon (;
).
5.4 Runtime support classes
In most cases, a class modified by Javassist does not require
Javassist to run. However, some kinds of bytecode generated by the
Javassist compiler need runtime support classes, which are in the
javassist.runtime
package (for details, please read
the API reference of that package). Note that the
javassist.runtime
package is the only package that
classes modified by Javassist may need for running. The other
Javassist classes are never used at runtime of the modified classes.
5.5 Limitations
In the current implementation, the Java compiler included in Javassist
has several limitations with respect to the language that the compiler can
accept. Those limitations are:
All the class names must be fully qualified (they must include
package names). This is because the compiler does not support
import
declarations. However, the java.lang
package is an
exception; for example, the compiler accepts Object
as
well as java.lang.Object
.
The .class
notation is not supported. Use the
method Class.forName()
.
In regular
Java, an expression Point.class
means a Class
object representing the Point
class. This notation is
not available.
Array initializers, a comma-separated list of expressions
enclosed by braces {
and }
, are not
supported.
Inner classes or anonymous classes are not supported.
switch
or synchronized
statements are not supported yet.
Labeled continue
and break
statements
are not supported.
The finally
clause following
try
and catch
clauses is not supported.
The compiler does not correctly implement the Java method dispatch
algorithm. The compiler may confuse if methods defined in a class
have the same name but take different parameter lists.
The users are recommended to use #
as the separator
between a class name and a static method or field name.
For example, in regular Java,
javassist.CtClass.intType.getName()
calls a method getName()
on
the object indicated by the static field intType
in javassist.CtClass
. In Javassist, the users can
write the expression shown above but they are recommended to
write:
javassist.CtClass#intType.getName()
so that the compiler can quickly parse the expression.
Previous page
Next page
Java(TM) is a trademark of Sun Microsystems, Inc.
Copyright (C) 2000-2004 by Shigeru Chiba, All rights reserved.