com.feilong.lib.javassist.bytecode.ClassFile Maven / Gradle / Ivy
Show all versions of feilong Show documentation
/*
* 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 com.feilong.lib.javassist.bytecode;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import com.feilong.lib.javassist.CannotCompileException;
/**
* ClassFile
represents a Java .class
file, which
* consists of a constant pool, methods, fields, and attributes.
*
*
* For example,
*
*
*
*
* ClassFile cf = new ClassFile(false, "test.Foo", null);
* cf.setInterfaces(new String[] { "java.lang.Cloneable" });
*
* FieldInfo f = new FieldInfo(cf.getConstPool(), "width", "I");
* f.setAccessFlags(AccessFlag.PUBLIC);
* cf.addField(f);
*
* cf.write(new DataOutputStream(new FileOutputStream("Foo.class")));
*
*
*
*
* This code generates a class file Foo.class
for the following class:
*
*
*
*
* package test;
*
* class Foo implements Cloneable{
*
* public int width;
* }
*
*
*
*
* @see FieldInfo
* @see MethodInfo
* @see ClassFileWriter
* @see com.feilong.lib.javassist.CtClass#getClassFile()
* @see com.feilong.lib.javassist.ClassPool#makeClass(ClassFile)
*/
public final class ClassFile{
int major, minor; // version number
ConstPool constPool;
int thisClass;
int accessFlags;
int superClass;
int[] interfaces;
List fields;
List methods;
List attributes;
String thisclassname; // not JVM-internal name
String[] cachedInterfaces;
String cachedSuperclass;
/**
* The major version number of class files
* for JDK 1.1.
*/
public static final int JAVA_1 = 45;
/**
* The major version number of class files
* for JDK 1.2.
*/
public static final int JAVA_2 = 46;
/**
* The major version number of class files
* for JDK 1.3.
*/
public static final int JAVA_3 = 47;
/**
* The major version number of class files
* for JDK 1.4.
*/
public static final int JAVA_4 = 48;
/**
* The major version number of class files
* for JDK 1.5.
*/
public static final int JAVA_5 = 49;
/**
* The major version number of class files
* for JDK 1.6.
*/
public static final int JAVA_6 = 50;
/**
* The major version number of class files
* for JDK 1.7.
*/
public static final int JAVA_7 = 51;
/**
* The major version number of class files
* for JDK 1.8.
*/
public static final int JAVA_8 = 52;
/**
* The major version number of class files
* for JDK 1.9.
*/
public static final int JAVA_9 = 53;
/**
* The major version number of class files
* for JDK 10.
*/
public static final int JAVA_10 = 54;
/**
* The major version number of class files
* for JDK 11.
*/
public static final int JAVA_11 = 55;
/**
* The major version number of class files created
* from scratch. The default value is 47 (JDK 1.3).
* It is 49 (JDK 1.5)
* if the JVM supports java.lang.StringBuilder
.
* It is 50 (JDK 1.6)
* if the JVM supports java.util.zip.DeflaterInputStream
.
* It is 51 (JDK 1.7)
* if the JVM supports java.lang.invoke.CallSite
.
* It is 52 (JDK 1.8)
* if the JVM supports java.util.function.Function
.
* It is 53 (JDK 1.9)
* if the JVM supports java.lang.reflect.Module
.
* It is 54 (JDK 10)
* if the JVM supports java.util.List.copyOf(Collection)
.
* It is 55 (JDK 11)
* if the JVM supports java.util.Optional.isEmpty()
.
*/
public static final int MAJOR_VERSION;
static{
int ver = JAVA_3;
try{
Class.forName("java.lang.StringBuilder");
ver = JAVA_5;
Class.forName("java.util.zip.DeflaterInputStream");
ver = JAVA_6;
Class.forName("java.lang.invoke.CallSite", false, ClassLoader.getSystemClassLoader());
ver = JAVA_7;
Class.forName("java.util.function.Function");
ver = JAVA_8;
Class.forName("java.lang.Module");
ver = JAVA_9;
List.class.getMethod("copyOf", Collection.class);
ver = JAVA_10;
Class.forName("java.util.Optional").getMethod("isEmpty");
ver = JAVA_11;
}catch (Throwable t){}
MAJOR_VERSION = ver;
}
/**
* Constructs a class file from a byte stream.
*/
public ClassFile(DataInputStream in) throws IOException{
read(in);
}
/**
* Constructs a class file including no members.
*
* @param isInterface
* true if this is an interface. false if this is a class.
* @param classname
* a fully-qualified class name
* @param superclass
* a fully-qualified super class name or null.
*/
public ClassFile(boolean isInterface, String classname, String superclass){
major = MAJOR_VERSION;
minor = 0; // JDK 1.3 or later
constPool = new ConstPool(classname);
thisClass = constPool.getThisClassInfo();
if (isInterface){
accessFlags = AccessFlag.INTERFACE | AccessFlag.ABSTRACT;
}else{
accessFlags = AccessFlag.SUPER;
}
initSuperclass(superclass);
interfaces = null;
fields = new ArrayList<>();
methods = new ArrayList<>();
thisclassname = classname;
attributes = new ArrayList<>();
attributes.add(new SourceFileAttribute(constPool, getSourcefileName(thisclassname)));
}
private void initSuperclass(String superclass){
if (superclass != null){
this.superClass = constPool.addClassInfo(superclass);
cachedSuperclass = superclass;
}else{
this.superClass = constPool.addClassInfo("java.lang.Object");
cachedSuperclass = "java.lang.Object";
}
}
private static String getSourcefileName(String qname){
return qname.replaceAll("^.*\\.", "") + ".java";
}
/**
* Eliminates dead constant pool items. If a method or a field is removed,
* the constant pool items used by that method/field become dead items. This
* method recreates a constant pool.
*/
public void compact(){
ConstPool cp = compact0();
for (MethodInfo minfo : methods){
minfo.compact(cp);
}
for (FieldInfo finfo : fields){
finfo.compact(cp);
}
attributes = AttributeInfo.copyAll(attributes, cp);
constPool = cp;
}
private ConstPool compact0(){
ConstPool cp = new ConstPool(thisclassname);
thisClass = cp.getThisClassInfo();
String sc = getSuperclass();
if (sc != null){
superClass = cp.addClassInfo(getSuperclass());
}
if (interfaces != null){
for (int i = 0; i < interfaces.length; ++i){
interfaces[i] = cp.addClassInfo(constPool.getClassInfo(interfaces[i]));
}
}
return cp;
}
/**
* Discards all attributes, associated with both the class file and the
* members such as a code attribute and exceptions attribute. The unused
* constant pool entries are also discarded (a new packed constant pool is
* constructed).
*/
public void prune(){
ConstPool cp = compact0();
List newAttributes = new ArrayList<>();
AttributeInfo invisibleAnnotations = getAttribute(AnnotationsAttribute.invisibleTag);
if (invisibleAnnotations != null){
invisibleAnnotations = invisibleAnnotations.copy(cp, null);
newAttributes.add(invisibleAnnotations);
}
AttributeInfo visibleAnnotations = getAttribute(AnnotationsAttribute.visibleTag);
if (visibleAnnotations != null){
visibleAnnotations = visibleAnnotations.copy(cp, null);
newAttributes.add(visibleAnnotations);
}
AttributeInfo signature = getAttribute(SignatureAttribute.tag);
if (signature != null){
signature = signature.copy(cp, null);
newAttributes.add(signature);
}
for (MethodInfo minfo : methods){
minfo.prune(cp);
}
for (FieldInfo finfo : fields){
finfo.prune(cp);
}
attributes = newAttributes;
constPool = cp;
}
/**
* Returns a constant pool table.
*/
public ConstPool getConstPool(){
return constPool;
}
/**
* Returns true if this is an interface.
*/
public boolean isInterface(){
return (accessFlags & AccessFlag.INTERFACE) != 0;
}
/**
* Returns true if this is a final class or interface.
*/
public boolean isFinal(){
return (accessFlags & AccessFlag.FINAL) != 0;
}
/**
* Returns true if this is an abstract class or an interface.
*/
public boolean isAbstract(){
return (accessFlags & AccessFlag.ABSTRACT) != 0;
}
/**
* Returns access flags.
*
* @see com.feilong.lib.javassist.bytecode.AccessFlag
*/
public int getAccessFlags(){
return accessFlags;
}
/**
* Changes access flags.
*
* @see com.feilong.lib.javassist.bytecode.AccessFlag
*/
public void setAccessFlags(int acc){
if ((acc & AccessFlag.INTERFACE) == 0){
acc |= AccessFlag.SUPER;
}
accessFlags = acc;
}
/**
* Returns access and property flags of this nested class.
* This method returns -1 if the class is not a nested class.
*
*
* The returned value is obtained from inner_class_access_flags
* of the entry representing this nested class itself
* in InnerClasses_attribute
.
*/
public int getInnerAccessFlags(){
InnerClassesAttribute ica = (InnerClassesAttribute) getAttribute(InnerClassesAttribute.tag);
if (ica == null){
return -1;
}
String name = getName();
int n = ica.tableLength();
for (int i = 0; i < n; ++i){
if (name.equals(ica.innerClass(i))){
return ica.accessFlags(i);
}
}
return -1;
}
/**
* Returns the class name.
*/
public String getName(){
return thisclassname;
}
/**
* Sets the class name. This method substitutes the new name for all
* occurrences of the old class name in the class file.
*/
public void setName(String name){
renameClass(thisclassname, name);
}
/**
* Returns the super class name.
*/
public String getSuperclass(){
if (cachedSuperclass == null){
cachedSuperclass = constPool.getClassInfo(superClass);
}
return cachedSuperclass;
}
/**
* Returns the index of the constant pool entry representing the super
* class.
*/
public int getSuperclassId(){
return superClass;
}
/**
* Sets the super class.
*
*
* The new super class should inherit from the old super class.
* This method modifies constructors so that they call constructors declared
* in the new super class.
*/
public void setSuperclass(String superclass) throws CannotCompileException{
if (superclass == null){
superclass = "java.lang.Object";
}
try{
this.superClass = constPool.addClassInfo(superclass);
for (MethodInfo minfo : methods){
minfo.setSuperclass(superclass);
}
}catch (BadBytecode e){
throw new CannotCompileException(e);
}
cachedSuperclass = superclass;
}
/**
* Replaces all occurrences of a class name in the class file.
*
*
* If class X is substituted for class Y in the class file, X and Y must
* have the same signature. If Y provides a method m(), X must provide it
* even if X inherits m() from the super class. If this fact is not
* guaranteed, the bytecode verifier may cause an error.
*
* @param oldname
* the replaced class name
* @param newname
* the substituted class name
*/
public void renameClass(String oldname,String newname){
if (oldname.equals(newname)){
return;
}
if (oldname.equals(thisclassname)){
thisclassname = newname;
}
oldname = Descriptor.toJvmName(oldname);
newname = Descriptor.toJvmName(newname);
constPool.renameClass(oldname, newname);
AttributeInfo.renameClass(attributes, oldname, newname);
for (MethodInfo minfo : methods){
String desc = minfo.getDescriptor();
minfo.setDescriptor(Descriptor.rename(desc, oldname, newname));
AttributeInfo.renameClass(minfo.getAttributes(), oldname, newname);
}
for (FieldInfo finfo : fields){
String desc = finfo.getDescriptor();
finfo.setDescriptor(Descriptor.rename(desc, oldname, newname));
AttributeInfo.renameClass(finfo.getAttributes(), oldname, newname);
}
}
/**
* Replaces all occurrences of several class names in the class file.
*
* @param classnames
* specifies which class name is replaced with which new name.
* Class names must be described with the JVM-internal
* representation like java/lang/Object
.
* @see #renameClass(String,String)
*/
public void renameClass(Map classnames){
String jvmNewThisName = classnames.get(Descriptor.toJvmName(thisclassname));
if (jvmNewThisName != null){
thisclassname = Descriptor.toJavaName(jvmNewThisName);
}
constPool.renameClass(classnames);
AttributeInfo.renameClass(attributes, classnames);
for (MethodInfo minfo : methods){
String desc = minfo.getDescriptor();
minfo.setDescriptor(Descriptor.rename(desc, classnames));
AttributeInfo.renameClass(minfo.getAttributes(), classnames);
}
for (FieldInfo finfo : fields){
String desc = finfo.getDescriptor();
finfo.setDescriptor(Descriptor.rename(desc, classnames));
AttributeInfo.renameClass(finfo.getAttributes(), classnames);
}
}
/**
* Internal-use only.
* CtClass.getRefClasses()
calls this method.
*/
public void getRefClasses(Map classnames){
constPool.renameClass(classnames);
AttributeInfo.getRefClasses(attributes, classnames);
for (MethodInfo minfo : methods){
String desc = minfo.getDescriptor();
Descriptor.rename(desc, classnames);
AttributeInfo.getRefClasses(minfo.getAttributes(), classnames);
}
for (FieldInfo finfo : fields){
String desc = finfo.getDescriptor();
Descriptor.rename(desc, classnames);
AttributeInfo.getRefClasses(finfo.getAttributes(), classnames);
}
}
/**
* Returns the names of the interfaces implemented by the class.
* The returned array is read only.
*/
public String[] getInterfaces(){
if (cachedInterfaces != null){
return cachedInterfaces;
}
String[] rtn = null;
if (interfaces == null){
rtn = new String[0];
}else{
String[] list = new String[interfaces.length];
for (int i = 0; i < interfaces.length; ++i){
list[i] = constPool.getClassInfo(interfaces[i]);
}
rtn = list;
}
cachedInterfaces = rtn;
return rtn;
}
/**
* Sets the interfaces.
*
* @param nameList
* the names of the interfaces.
*/
public void setInterfaces(String[] nameList){
cachedInterfaces = null;
if (nameList != null){
interfaces = new int[nameList.length];
for (int i = 0; i < nameList.length; ++i){
interfaces[i] = constPool.addClassInfo(nameList[i]);
}
}
}
/**
* Appends an interface to the interfaces implemented by the class.
*/
public void addInterface(String name){
cachedInterfaces = null;
int info = constPool.addClassInfo(name);
if (interfaces == null){
interfaces = new int[1];
interfaces[0] = info;
}else{
int n = interfaces.length;
int[] newarray = new int[n + 1];
System.arraycopy(interfaces, 0, newarray, 0, n);
newarray[n] = info;
interfaces = newarray;
}
}
/**
* Returns all the fields declared in the class.
*
* @return a list of FieldInfo
.
* @see FieldInfo
*/
public List getFields(){
return fields;
}
/**
* Appends a field to the class.
*
* @throws DuplicateMemberException
* when the field is already included.
*/
public void addField(FieldInfo finfo) throws DuplicateMemberException{
testExistingField(finfo.getName(), finfo.getDescriptor());
fields.add(finfo);
}
/**
* Just appends a field to the class.
* It does not check field duplication.
* Use this method only when minimizing performance overheads
* is seriously required.
*
* @since 3.13
*/
public void addField2(FieldInfo finfo){
fields.add(finfo);
}
private void testExistingField(String name,String descriptor) throws DuplicateMemberException{
for (FieldInfo minfo : fields){
if (minfo.getName().equals(name)){
throw new DuplicateMemberException("duplicate field: " + name);
}
}
}
/**
* Returns all the methods declared in the class.
*
* @return a list of MethodInfo
.
* @see MethodInfo
*/
public List getMethods(){
return methods;
}
/**
* Returns the method with the specified name. If there are multiple methods
* with that name, this method returns one of them.
*
* @return null if no such method is found.
*/
public MethodInfo getMethod(String name){
for (MethodInfo minfo : methods){
if (minfo.getName().equals(name)){
return minfo;
}
}
return null;
}
/**
* Returns a static initializer (class initializer), or null if it does not
* exist.
*/
public MethodInfo getStaticInitializer(){
return getMethod(MethodInfo.nameClinit);
}
/**
* Appends a method to the class.
* If there is a bridge method with the same name and signature,
* then the bridge method is removed before a new method is added.
*
* @throws DuplicateMemberException
* when the method is already included.
*/
public void addMethod(MethodInfo minfo) throws DuplicateMemberException{
testExistingMethod(minfo);
methods.add(minfo);
}
/**
* Just appends a method to the class.
* It does not check method duplication or remove a bridge method.
* Use this method only when minimizing performance overheads
* is seriously required.
*
* @since 3.13
*/
public void addMethod2(MethodInfo minfo){
methods.add(minfo);
}
private void testExistingMethod(MethodInfo newMinfo) throws DuplicateMemberException{
String name = newMinfo.getName();
String descriptor = newMinfo.getDescriptor();
ListIterator it = methods.listIterator(0);
while (it.hasNext()){
if (isDuplicated(newMinfo, name, descriptor, it.next(), it)){
throw new DuplicateMemberException("duplicate method: " + name + " in " + this.getName());
}
}
}
private static boolean isDuplicated(MethodInfo newMethod,String newName,String newDesc,MethodInfo minfo,ListIterator it){
if (!minfo.getName().equals(newName)){
return false;
}
String desc = minfo.getDescriptor();
if (!Descriptor.eqParamTypes(desc, newDesc)){
return false;
}
if (desc.equals(newDesc)){
if (notBridgeMethod(minfo)){
return true;
}
// if the bridge method with the same signature
// already exists, replace it.
it.remove();
return false;
}
return false;
// return notBridgeMethod(minfo) && notBridgeMethod(newMethod);
}
/*
* For a bridge method, see Sec. 15.12.4.5 of JLS 3rd Ed.
*/
private static boolean notBridgeMethod(MethodInfo minfo){
return (minfo.getAccessFlags() & AccessFlag.BRIDGE) == 0;
}
/**
* Returns all the attributes. The returned List
object
* is shared with this object. If you add a new attribute to the list,
* the attribute is also added to the classs file represented by this
* object. If you remove an attribute from the list, it is also removed
* from the class file.
*
* @return a list of AttributeInfo
objects.
* @see AttributeInfo
*/
public List getAttributes(){
return attributes;
}
/**
* Returns the attribute with the specified name. If there are multiple
* attributes with that name, this method returns either of them. It
* returns null if the specified attributed is not found.
*
*
* An attribute name can be obtained by, for example,
* {@link AnnotationsAttribute#visibleTag} or
* {@link AnnotationsAttribute#invisibleTag}.
*
*
* @param name
* attribute name
* @see #getAttributes()
*/
public AttributeInfo getAttribute(String name){
for (AttributeInfo ai : attributes){
if (ai.getName().equals(name)){
return ai;
}
}
return null;
}
/**
* Removes an attribute with the specified name.
*
* @param name
* attribute name.
* @return the removed attribute or null.
* @since 3.21
*/
public AttributeInfo removeAttribute(String name){
return AttributeInfo.remove(attributes, name);
}
/**
* Appends an attribute. If there is already an attribute with the same
* name, the new one substitutes for it.
*
* @see #getAttributes()
*/
public void addAttribute(AttributeInfo info){
AttributeInfo.remove(attributes, info.getName());
attributes.add(info);
}
/**
* Returns the source file containing this class.
*
* @return null if this information is not available.
*/
public String getSourceFile(){
SourceFileAttribute sf = (SourceFileAttribute) getAttribute(SourceFileAttribute.tag);
if (sf == null){
return null;
}
return sf.getFileName();
}
private void read(DataInputStream in) throws IOException{
int i, n;
int magic = in.readInt();
if (magic != 0xCAFEBABE){
throw new IOException("bad magic number: " + Integer.toHexString(magic));
}
minor = in.readUnsignedShort();
major = in.readUnsignedShort();
constPool = new ConstPool(in);
accessFlags = in.readUnsignedShort();
thisClass = in.readUnsignedShort();
constPool.setThisClassInfo(thisClass);
superClass = in.readUnsignedShort();
n = in.readUnsignedShort();
if (n == 0){
interfaces = null;
}else{
interfaces = new int[n];
for (i = 0; i < n; ++i){
interfaces[i] = in.readUnsignedShort();
}
}
ConstPool cp = constPool;
n = in.readUnsignedShort();
fields = new ArrayList<>();
for (i = 0; i < n; ++i){
addField2(new FieldInfo(cp, in));
}
n = in.readUnsignedShort();
methods = new ArrayList<>();
for (i = 0; i < n; ++i){
addMethod2(new MethodInfo(cp, in));
}
attributes = new ArrayList<>();
n = in.readUnsignedShort();
for (i = 0; i < n; ++i){
addAttribute(AttributeInfo.read(cp, in));
}
thisclassname = constPool.getClassInfo(thisClass);
}
/**
* Writes a class file represented by this object into an output stream.
*/
public void write(DataOutputStream out) throws IOException{
int i, n;
out.writeInt(0xCAFEBABE); // magic
out.writeShort(minor); // minor version
out.writeShort(major); // major version
constPool.write(out); // constant pool
out.writeShort(accessFlags);
out.writeShort(thisClass);
out.writeShort(superClass);
if (interfaces == null){
n = 0;
}else{
n = interfaces.length;
}
out.writeShort(n);
for (i = 0; i < n; ++i){
out.writeShort(interfaces[i]);
}
n = fields.size();
out.writeShort(n);
for (i = 0; i < n; ++i){
FieldInfo finfo = fields.get(i);
finfo.write(out);
}
out.writeShort(methods.size());
for (MethodInfo minfo : methods){
minfo.write(out);
}
out.writeShort(attributes.size());
AttributeInfo.writeAll(attributes, out);
}
/**
* Get the Major version.
*
* @return the major version
*/
public int getMajorVersion(){
return major;
}
/**
* Set the major version.
*
* @param major
* the major version
*/
public void setMajorVersion(int major){
this.major = major;
}
/**
* Get the minor version.
*
* @return the minor version
*/
public int getMinorVersion(){
return minor;
}
/**
* Set the minor version.
*
* @param minor
* the minor version
*/
public void setMinorVersion(int minor){
this.minor = minor;
}
/**
* Sets the major and minor version to Java 5.
*
* If the major version is older than 49, Java 5
* extensions such as annotations are ignored
* by the JVM.
*/
public void setVersionToJava5(){
this.major = 49;
this.minor = 0;
}
}