ClassGen.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.bcel.generic;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.AccessFlags;
import org.apache.bcel.classfile.Annotations;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.RuntimeInvisibleAnnotations;
import org.apache.bcel.classfile.RuntimeVisibleAnnotations;
import org.apache.bcel.classfile.SourceFile;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.util.BCELComparator;
import org.apache.commons.lang3.ArrayUtils;
/**
* Template class for building up a java class. May be initialized with an existing Java class (file).
*
* @see JavaClass
*/
public class ClassGen extends AccessFlags implements Cloneable {
private static BCELComparator<ClassGen> bcelComparator = new BCELComparator<ClassGen>() {
@Override
public boolean equals(final ClassGen a, final ClassGen b) {
return a == b || a != null && b != null && Objects.equals(a.getClassName(), b.getClassName());
}
@Override
public int hashCode(final ClassGen o) {
return o != null ? Objects.hashCode(o.getClassName()) : 0;
}
};
/**
* @return Comparison strategy object
*/
public static BCELComparator<ClassGen> getComparator() {
return bcelComparator;
}
/**
* @param comparator Comparison strategy object
*/
public static void setComparator(final BCELComparator<ClassGen> comparator) {
bcelComparator = comparator;
}
/*
* Corresponds to the fields found in a JavaClass object.
*/
private String className;
private String superClassName;
private final String fileName;
private int classNameIndex = -1;
private int superclassNameIndex = -1;
private int major = Const.MAJOR_1_1;
private int minor = Const.MINOR_1_1;
private ConstantPoolGen cp; // Template for building up constant pool
// ArrayLists instead of arrays to gather fields, methods, etc.
private final List<Field> fieldList = new ArrayList<>();
private final List<Method> methodList = new ArrayList<>();
private final List<Attribute> attributeList = new ArrayList<>();
private final List<String> interfaceList = new ArrayList<>();
private final List<AnnotationEntryGen> annotationList = new ArrayList<>();
private List<ClassObserver> observers;
/**
* Constructs a new instance from an existing class.
*
* @param clazz JavaClass object (e.g. read from file)
*/
public ClassGen(final JavaClass clazz) {
super(clazz.getAccessFlags());
classNameIndex = clazz.getClassNameIndex();
superclassNameIndex = clazz.getSuperclassNameIndex();
className = clazz.getClassName();
superClassName = clazz.getSuperclassName();
fileName = clazz.getSourceFileName();
cp = new ConstantPoolGen(clazz.getConstantPool());
major = clazz.getMajor();
minor = clazz.getMinor();
final Attribute[] attributes = clazz.getAttributes();
// J5TODO: Could make unpacking lazy, done on first reference
final AnnotationEntryGen[] annotations = unpackAnnotations(attributes);
final String[] interfaceNames = clazz.getInterfaceNames();
if (interfaceNames != null) {
Collections.addAll(interfaceList, interfaceNames);
}
if (attributes != null) {
for (final Attribute attribute : attributes) {
if (!(attribute instanceof Annotations)) {
addAttribute(attribute);
}
}
}
Collections.addAll(annotationList, annotations);
final Method[] methods = clazz.getMethods();
if (methods != null) {
Collections.addAll(methodList, methods);
}
final Field[] fields = clazz.getFields();
if (fields != null) {
Collections.addAll(fieldList, fields);
}
}
/**
* Convenience constructor to set up some important values initially.
*
* @param className fully qualified class name
* @param superClassName fully qualified superclass name
* @param fileName source file name
* @param accessFlags access qualifiers
* @param interfaces implemented interfaces
*/
public ClassGen(final String className, final String superClassName, final String fileName, final int accessFlags, final String[] interfaces) {
this(className, superClassName, fileName, accessFlags, interfaces, new ConstantPoolGen());
}
/**
* Convenience constructor to set up some important values initially.
*
* @param className fully qualified class name
* @param superClassName fully qualified superclass name
* @param fileName source file name
* @param accessFlags access qualifiers
* @param interfaces implemented interfaces
* @param cp constant pool to use
*/
public ClassGen(final String className, final String superClassName, final String fileName, final int accessFlags, final String[] interfaces,
final ConstantPoolGen cp) {
super(accessFlags);
this.className = className;
this.superClassName = superClassName;
this.fileName = fileName;
this.cp = cp;
// Put everything needed by default into the constant pool and the vectors
if (fileName != null) {
addAttribute(new SourceFile(cp.addUtf8("SourceFile"), 2, cp.addUtf8(fileName), cp.getConstantPool()));
}
classNameIndex = cp.addClass(className);
superclassNameIndex = cp.addClass(superClassName);
if (interfaces != null) {
Collections.addAll(interfaceList, interfaces);
}
}
public void addAnnotationEntry(final AnnotationEntryGen a) {
annotationList.add(a);
}
/**
* Add an attribute to this class.
*
* @param a attribute to add
*/
public void addAttribute(final Attribute a) {
attributeList.add(a);
}
/**
* Convenience method.
*
* Add an empty constructor to this class that does nothing but calling super().
*
* @param accessFlags rights for constructor
*/
public void addEmptyConstructor(final int accessFlags) {
final InstructionList il = new InstructionList();
il.append(InstructionConst.THIS); // Push 'this'
il.append(new INVOKESPECIAL(cp.addMethodref(superClassName, Const.CONSTRUCTOR_NAME, "()V")));
il.append(InstructionConst.RETURN);
final MethodGen mg = new MethodGen(accessFlags, Type.VOID, Type.NO_ARGS, null, Const.CONSTRUCTOR_NAME, className, il, cp);
mg.setMaxStack(1);
addMethod(mg.getMethod());
}
/**
* Add a field to this class.
*
* @param f field to add
*/
public void addField(final Field f) {
fieldList.add(f);
}
/**
* Add an interface to this class, i.e., this class has to implement it.
*
* @param name interface to implement (fully qualified class name)
*/
public void addInterface(final String name) {
interfaceList.add(name);
}
/**
* Add a method to this class.
*
* @param m method to add
*/
public void addMethod(final Method m) {
methodList.add(m);
}
/**
* Add observer for this object.
*/
public void addObserver(final ClassObserver o) {
if (observers == null) {
observers = new ArrayList<>();
}
observers.add(o);
}
@Override
public Object clone() {
try {
return super.clone();
} catch (final CloneNotSupportedException e) {
throw new UnsupportedOperationException("Clone Not Supported", e); // never happens
}
}
public boolean containsField(final Field f) {
return fieldList.contains(f);
}
/**
* @return field object with given name, or null
*/
public Field containsField(final String name) {
for (final Field f : fieldList) {
if (f.getName().equals(name)) {
return f;
}
}
return null;
}
/**
* @return method object with given name and signature, or null
*/
public Method containsMethod(final String name, final String signature) {
for (final Method m : methodList) {
if (m.getName().equals(name) && m.getSignature().equals(signature)) {
return m;
}
}
return null;
}
/**
* Return value as defined by given BCELComparator strategy. By default two ClassGen objects are said to be equal when
* their class names are equal.
*
* @see Object#equals(Object)
*/
@Override
public boolean equals(final Object obj) {
return obj instanceof ClassGen && bcelComparator.equals(this, (ClassGen) obj);
}
// J5TODO: Should we make calling unpackAnnotations() lazy and put it in here?
public AnnotationEntryGen[] getAnnotationEntries() {
return annotationList.toArray(AnnotationEntryGen.EMPTY_ARRAY);
}
public Attribute[] getAttributes() {
return attributeList.toArray(Attribute.EMPTY_ARRAY);
}
public String getClassName() {
return className;
}
public int getClassNameIndex() {
return classNameIndex;
}
public ConstantPoolGen getConstantPool() {
return cp;
}
public Field[] getFields() {
return fieldList.toArray(Field.EMPTY_ARRAY);
}
public String getFileName() {
return fileName;
}
public String[] getInterfaceNames() {
return interfaceList.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
}
public int[] getInterfaces() {
final int size = interfaceList.size();
final int[] interfaces = new int[size];
Arrays.setAll(interfaces, i -> cp.addClass(interfaceList.get(i)));
return interfaces;
}
/**
* @return the (finally) built up Java class object.
*/
public JavaClass getJavaClass() {
final int[] interfaces = getInterfaces();
final Field[] fields = getFields();
final Method[] methods = getMethods();
Attribute[] attributes = null;
if (annotationList.isEmpty()) {
attributes = getAttributes();
} else {
// TODO: Sometime later, trash any attributes called 'RuntimeVisibleAnnotations' or 'RuntimeInvisibleAnnotations'
final Attribute[] annAttributes = AnnotationEntryGen.getAnnotationAttributes(cp, getAnnotationEntries());
attributes = new Attribute[attributeList.size() + annAttributes.length];
attributeList.toArray(attributes);
System.arraycopy(annAttributes, 0, attributes, attributeList.size(), annAttributes.length);
}
// Must be last since the above calls may still add something to it
final ConstantPool cp = this.cp.getFinalConstantPool();
return new JavaClass(classNameIndex, superclassNameIndex, fileName, major, minor, super.getAccessFlags(), cp, interfaces, fields, methods,
attributes);
}
/**
* @return major version number of class file
*/
public int getMajor() {
return major;
}
public Method getMethodAt(final int pos) {
return methodList.get(pos);
}
public Method[] getMethods() {
return methodList.toArray(Method.EMPTY_ARRAY);
}
/**
* @return minor version number of class file
*/
public int getMinor() {
return minor;
}
public String getSuperclassName() {
return superClassName;
}
public int getSuperclassNameIndex() {
return superclassNameIndex;
}
/**
* Return value as defined by given BCELComparator strategy. By default return the hash code of the class name.
*
* @see Object#hashCode()
*/
@Override
public int hashCode() {
return bcelComparator.hashCode(this);
}
/**
* Remove an attribute from this class.
*
* @param a attribute to remove
*/
public void removeAttribute(final Attribute a) {
attributeList.remove(a);
}
/**
* Remove a field to this class.
*
* @param f field to remove
*/
public void removeField(final Field f) {
fieldList.remove(f);
}
/**
* Remove an interface from this class.
*
* @param name interface to remove (fully qualified name)
*/
public void removeInterface(final String name) {
interfaceList.remove(name);
}
/**
* Remove a method from this class.
*
* @param m method to remove
*/
public void removeMethod(final Method m) {
methodList.remove(m);
}
/**
* Remove observer for this object.
*/
public void removeObserver(final ClassObserver o) {
if (observers != null) {
observers.remove(o);
}
}
/**
* Replace given field with new one. If the old one does not exist add the new_ field to the class anyway.
*/
public void replaceField(final Field old, final Field newField) {
if (newField == null) {
throw new ClassGenException("Replacement method must not be null");
}
final int i = fieldList.indexOf(old);
if (i < 0) {
fieldList.add(newField);
} else {
fieldList.set(i, newField);
}
}
/**
* Replace given method with new one. If the old one does not exist add the newMethod method to the class anyway.
*/
public void replaceMethod(final Method old, final Method newMethod) {
if (newMethod == null) {
throw new ClassGenException("Replacement method must not be null");
}
final int i = methodList.indexOf(old);
if (i < 0) {
methodList.add(newMethod);
} else {
methodList.set(i, newMethod);
}
}
public void setClassName(final String name) {
className = Utility.pathToPackage(name);
classNameIndex = cp.addClass(name);
}
public void setClassNameIndex(final int classNameIndex) {
this.classNameIndex = classNameIndex;
this.className = Utility.pathToPackage(cp.getConstantPool().getConstantString(classNameIndex, Const.CONSTANT_Class));
}
public void setConstantPool(final ConstantPoolGen constantPool) {
cp = constantPool;
}
/**
* Sets major version number of class file, default value is 45 (JDK 1.1)
*
* @param major major version number
*/
public void setMajor(final int major) { // TODO could be package-protected - only called by test code
this.major = major;
}
public void setMethodAt(final Method method, final int pos) {
methodList.set(pos, method);
}
public void setMethods(final Method[] methods) {
methodList.clear();
if (methods != null) {
Collections.addAll(methodList, methods);
}
}
/**
* Sets minor version number of class file, default value is 3 (JDK 1.1)
*
* @param minor minor version number
*/
public void setMinor(final int minor) { // TODO could be package-protected - only called by test code
this.minor = minor;
}
public void setSuperclassName(final String name) {
superClassName = Utility.pathToPackage(name);
superclassNameIndex = cp.addClass(name);
}
public void setSuperclassNameIndex(final int superclassNameIndex) {
this.superclassNameIndex = superclassNameIndex;
superClassName = Utility.pathToPackage(cp.getConstantPool().getConstantString(superclassNameIndex, Const.CONSTANT_Class));
}
/**
* Unpacks attributes representing annotations.
*/
private AnnotationEntryGen[] unpackAnnotations(final Attribute[] attributes) {
final List<AnnotationEntryGen> annotationGenObjs = new ArrayList<>();
if (attributes != null) {
for (final Attribute attr : attributes) {
if (attr instanceof RuntimeVisibleAnnotations) {
final RuntimeVisibleAnnotations rva = (RuntimeVisibleAnnotations) attr;
rva.forEach(a -> annotationGenObjs.add(new AnnotationEntryGen(a, getConstantPool(), false)));
} else if (attr instanceof RuntimeInvisibleAnnotations) {
final RuntimeInvisibleAnnotations ria = (RuntimeInvisibleAnnotations) attr;
ria.forEach(a -> annotationGenObjs.add(new AnnotationEntryGen(a, getConstantPool(), false)));
}
}
}
return annotationGenObjs.toArray(AnnotationEntryGen.EMPTY_ARRAY);
}
/**
* Call notify() method on all observers. This method is not called automatically whenever the state has changed, but
* has to be called by the user after they have finished editing the object.
*/
public void update() {
if (observers != null) {
for (final ClassObserver observer : observers) {
observer.notify(this);
}
}
}
}