scouter.javassist.CtConstructor Maven / Gradle / Ivy
/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- 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,
* or the Apache License Version 2.0.
*
* 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 scouter.javassist;
import scouter.javassist.bytecode.BadBytecode;
import scouter.javassist.bytecode.Bytecode;
import scouter.javassist.bytecode.ClassFile;
import scouter.javassist.bytecode.CodeAttribute;
import scouter.javassist.bytecode.CodeIterator;
import scouter.javassist.bytecode.ConstPool;
import scouter.javassist.bytecode.Descriptor;
import scouter.javassist.bytecode.MethodInfo;
import scouter.javassist.bytecode.Opcode;
import scouter.javassist.compiler.CompileError;
import scouter.javassist.compiler.Javac;
/**
* An instance of CtConstructor represents a constructor.
* It may represent a static constructor
* (class initializer). To distinguish a constructor and a class
* initializer, call isClassInitializer()
.
*
* See the super class CtBehavior
as well since
* a number of useful methods are in CtBehavior
.
*
* @see CtClass#getDeclaredConstructors()
* @see CtClass#getClassInitializer()
* @see CtNewConstructor
*/
public final class CtConstructor extends CtBehavior {
protected CtConstructor(MethodInfo minfo, CtClass declaring) {
super(declaring, minfo);
}
/**
* Creates a constructor with no constructor body.
* The created constructor
* must be added to a class with CtClass.addConstructor()
.
*
*
The created constructor does not include a constructor body,
* which must be specified with setBody()
.
*
* @param declaring the class to which the created method is added.
* @param parameters a list of the parameter types
*
* @see CtClass#addConstructor(CtConstructor)
* @see CtConstructor#setBody(String)
* @see CtConstructor#setBody(CtConstructor,ClassMap)
*/
public CtConstructor(CtClass[] parameters, CtClass declaring) {
this((MethodInfo)null, declaring);
ConstPool cp = declaring.getClassFile2().getConstPool();
String desc = Descriptor.ofConstructor(parameters);
methodInfo = new MethodInfo(cp, "", desc);
setModifiers(Modifier.PUBLIC);
}
/**
* Creates a copy of a CtConstructor
object.
* The created constructor must be
* added to a class with CtClass.addConstructor()
.
*
* All occurrences of class names in the created constructor
* are replaced with names specified by
* map
if map
is not null
.
*
*
By default, all the occurrences of the names of the class
* declaring src
and the superclass are replaced
* with the name of the class and the superclass that
* the created constructor is added to.
* This is done whichever map
is null or not.
* To prevent this replacement, call ClassMap.fix()
* or put()
to explicitly specify replacement.
*
*
Note: if the .class
notation (for example,
* String.class
) is included in an expression, the
* Javac compiler may produce a helper method.
* Since this constructor never
* copies this helper method, the programmers have the responsiblity of
* copying it. Otherwise, use Class.forName()
in the
* expression.
*
* @param src the source method.
* @param declaring the class to which the created method is added.
* @param map the hashtable associating original class names
* with substituted names.
* It can be null
.
*
* @see CtClass#addConstructor(CtConstructor)
* @see ClassMap#fix(String)
*/
public CtConstructor(CtConstructor src, CtClass declaring, ClassMap map)
throws CannotCompileException
{
this((MethodInfo)null, declaring);
copy(src, true, map);
}
/**
* Returns true if this object represents a constructor.
*/
public boolean isConstructor() {
return methodInfo.isConstructor();
}
/**
* Returns true if this object represents a static initializer.
*/
public boolean isClassInitializer() {
return methodInfo.isStaticInitializer();
}
/**
* Returns the constructor name followed by parameter types
* such as CtConstructor(CtClass[],CtClass)
.
*
* @since 3.5
*/
public String getLongName() {
return getDeclaringClass().getName()
+ (isConstructor() ? Descriptor.toString(getSignature())
: ("." + MethodInfo.nameClinit + "()"));
}
/**
* Obtains the name of this constructor.
* It is the same as the simple name of the class declaring this
* constructor. If this object represents a class initializer,
* then this method returns "<clinit>"
.
*/
public String getName() {
if (methodInfo.isStaticInitializer())
return MethodInfo.nameClinit;
else
return declaringClass.getSimpleName();
}
/**
* Returns true if the constructor (or static initializer)
* is the default one. This method returns true if the constructor
* takes some arguments but it does not perform anything except
* calling super()
(the no-argument constructor of
* the super class).
*/
public boolean isEmpty() {
CodeAttribute ca = getMethodInfo2().getCodeAttribute();
if (ca == null)
return false; // native or abstract??
// they are not allowed, though.
ConstPool cp = ca.getConstPool();
CodeIterator it = ca.iterator();
try {
int pos, desc;
int op0 = it.byteAt(it.next());
return op0 == Opcode.RETURN // empty static initializer
|| (op0 == Opcode.ALOAD_0
&& it.byteAt(pos = it.next()) == Opcode.INVOKESPECIAL
&& (desc = cp.isConstructor(getSuperclassName(),
it.u16bitAt(pos + 1))) != 0
&& "()V".equals(cp.getUtf8Info(desc))
&& it.byteAt(it.next()) == Opcode.RETURN
&& !it.hasNext());
}
catch (BadBytecode e) {}
return false;
}
private String getSuperclassName() {
ClassFile cf = declaringClass.getClassFile2();
return cf.getSuperclass();
}
/**
* Returns true if this constructor calls a constructor
* of the super class. This method returns false if it
* calls another constructor of this class by this()
.
*/
public boolean callsSuper() throws CannotCompileException {
CodeAttribute codeAttr = methodInfo.getCodeAttribute();
if (codeAttr != null) {
CodeIterator it = codeAttr.iterator();
try {
int index = it.skipSuperConstructor();
return index >= 0;
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
return false;
}
/**
* Sets a constructor body.
*
* @param src the source code representing the constructor body.
* It must be a single statement or block.
* If it is null
, the substituted
* constructor body does nothing except calling
* super()
.
*/
public void setBody(String src) throws CannotCompileException {
if (src == null)
if (isClassInitializer())
src = ";";
else
src = "super();";
super.setBody(src);
}
/**
* Copies a constructor body from another constructor.
*
*
All occurrences of the class names in the copied body
* are replaced with the names specified by
* map
if map
is not null
.
*
* @param src the method that the body is copied from.
* @param map the hashtable associating original class names
* with substituted names.
* It can be null
.
*/
public void setBody(CtConstructor src, ClassMap map)
throws CannotCompileException
{
setBody0(src.declaringClass, src.methodInfo,
declaringClass, methodInfo, map);
}
/**
* Inserts bytecode just after another constructor in the super class
* or this class is called.
* It does not work if this object represents a class initializer.
*
* @param src the source code representing the inserted bytecode.
* It must be a single statement or block.
*/
public void insertBeforeBody(String src) throws CannotCompileException {
CtClass cc = declaringClass;
cc.checkModify();
if (isClassInitializer())
throw new CannotCompileException("class initializer");
CodeAttribute ca = methodInfo.getCodeAttribute();
CodeIterator iterator = ca.iterator();
Bytecode b = new Bytecode(methodInfo.getConstPool(),
ca.getMaxStack(), ca.getMaxLocals());
b.setStackDepth(ca.getMaxStack());
Javac jv = new Javac(b, cc);
try {
jv.recordParams(getParameterTypes(), false);
jv.compileStmnt(src);
ca.setMaxStack(b.getMaxStack());
ca.setMaxLocals(b.getMaxLocals());
iterator.skipConstructor();
int pos = iterator.insertEx(b.get());
iterator.insert(b.getExceptionTable(), pos);
methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
}
catch (NotFoundException e) {
throw new CannotCompileException(e);
}
catch (CompileError e) {
throw new CannotCompileException(e);
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
/* This method is called by addCatch() in CtBehavior.
* super() and this() must not be in a try statement.
*/
int getStartPosOfBody(CodeAttribute ca) throws CannotCompileException {
CodeIterator ci = ca.iterator();
try {
ci.skipConstructor();
return ci.next();
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
/**
* Makes a copy of this constructor and converts it into a method.
* The signature of the mehtod is the same as the that of this constructor.
* The return type is void
. The resulting method must be
* appended to the class specified by declaring
.
* If this constructor is a static initializer, the resulting method takes
* no parameter.
*
*
An occurrence of another constructor call this()
* or a super constructor call super()
is
* eliminated from the resulting method.
*
*
The immediate super class of the class declaring this constructor
* must be also a super class of the class declaring the resulting method.
* If the constructor accesses a field, the class declaring the resulting method
* must also declare a field with the same name and type.
*
* @param name the name of the resulting method.
* @param declaring the class declaring the resulting method.
*/
public CtMethod toMethod(String name, CtClass declaring)
throws CannotCompileException
{
return toMethod(name, declaring, null);
}
/**
* Makes a copy of this constructor and converts it into a method.
* The signature of the method is the same as the that of this constructor.
* The return type is void
. The resulting method must be
* appended to the class specified by declaring
.
* If this constructor is a static initializer, the resulting method takes
* no parameter.
*
*
An occurrence of another constructor call this()
* or a super constructor call super()
is
* eliminated from the resulting method.
*
*
The immediate super class of the class declaring this constructor
* must be also a super class of the class declaring the resulting method
* (this is obviously true if the second parameter declaring
is
* the same as the class declaring this constructor).
* If the constructor accesses a field, the class declaring the resulting method
* must also declare a field with the same name and type.
*
* @param name the name of the resulting method.
* @param declaring the class declaring the resulting method.
* It is normally the same as the class declaring this
* constructor.
* @param map the hash table associating original class names
* with substituted names. The original class names will be
* replaced while making a copy.
* map
can be null
.
*/
public CtMethod toMethod(String name, CtClass declaring, ClassMap map)
throws CannotCompileException
{
CtMethod method = new CtMethod(null, declaring);
method.copy(this, false, map);
if (isConstructor()) {
MethodInfo minfo = method.getMethodInfo2();
CodeAttribute ca = minfo.getCodeAttribute();
if (ca != null) {
removeConsCall(ca);
try {
methodInfo.rebuildStackMapIf6(declaring.getClassPool(),
declaring.getClassFile2());
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
}
method.setName(name);
return method;
}
private static void removeConsCall(CodeAttribute ca)
throws CannotCompileException
{
CodeIterator iterator = ca.iterator();
try {
int pos = iterator.skipConstructor();
if (pos >= 0) {
int mref = iterator.u16bitAt(pos + 1);
String desc = ca.getConstPool().getMethodrefType(mref);
int num = Descriptor.numOfParameters(desc) + 1;
if (num > 3)
pos = iterator.insertGapAt(pos, num - 3, false).position;
iterator.writeByte(Opcode.POP, pos++); // this
iterator.writeByte(Opcode.NOP, pos);
iterator.writeByte(Opcode.NOP, pos + 1);
Descriptor.Iterator it = new Descriptor.Iterator(desc);
while (true) {
it.next();
if (it.isParameter())
iterator.writeByte(it.is2byte() ? Opcode.POP2 : Opcode.POP,
pos++);
else
break;
}
}
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
}