blob: 917bc4c31f02e8d12b515180d4fbbc4a93b66d21 [file] [log] [blame]
/*
* 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.commons.bcel6.generic;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.bcel6.Constants;
import org.apache.commons.bcel6.classfile.AccessFlags;
import org.apache.commons.bcel6.classfile.AnnotationEntry;
import org.apache.commons.bcel6.classfile.Annotations;
import org.apache.commons.bcel6.classfile.Attribute;
import org.apache.commons.bcel6.classfile.ConstantPool;
import org.apache.commons.bcel6.classfile.Field;
import org.apache.commons.bcel6.classfile.JavaClass;
import org.apache.commons.bcel6.classfile.Method;
import org.apache.commons.bcel6.classfile.RuntimeInvisibleAnnotations;
import org.apache.commons.bcel6.classfile.RuntimeVisibleAnnotations;
import org.apache.commons.bcel6.classfile.SourceFile;
import org.apache.commons.bcel6.classfile.Utility;
import org.apache.commons.bcel6.util.BCELComparator;
/**
* Template class for building up a java class. May be initialized with an
* existing java class (file).
*
* @see JavaClass
* @version $Id$
* @author <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
*/
public class ClassGen extends AccessFlags implements Cloneable {
private static final long serialVersionUID = 6880879387392827211L;
/* Corresponds to the fields found in a JavaClass object.
*/
private String class_name, super_class_name;
private final String file_name;
private int class_name_index = -1, superclass_name_index = -1;
private int major = Constants.MAJOR_1_1, minor = Constants.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> field_vec = new ArrayList<Field>();
private final List<Method> method_vec = new ArrayList<Method>();
private final List<Attribute> attribute_vec = new ArrayList<Attribute>();
private final List<String> interface_vec = new ArrayList<String>();
private final List<AnnotationEntryGen> annotation_vec = new ArrayList<AnnotationEntryGen>();
private static BCELComparator _cmp = new BCELComparator() {
public boolean equals( Object o1, Object o2 ) {
ClassGen THIS = (ClassGen) o1;
ClassGen THAT = (ClassGen) o2;
return THIS.getClassName().equals(THAT.getClassName());
}
public int hashCode( Object o ) {
ClassGen THIS = (ClassGen) o;
return THIS.getClassName().hashCode();
}
};
/** Convenience constructor to set up some important values initially.
*
* @param class_name fully qualified class name
* @param super_class_name fully qualified superclass name
* @param file_name source file name
* @param access_flags access qualifiers
* @param interfaces implemented interfaces
* @param cp constant pool to use
*/
public ClassGen(String class_name, String super_class_name, String file_name, int access_flags,
String[] interfaces, ConstantPoolGen cp) {
this.class_name = class_name;
this.super_class_name = super_class_name;
this.file_name = file_name;
this.access_flags = access_flags;
this.cp = cp;
// Put everything needed by default into the constant pool and the vectors
if (file_name != null) {
addAttribute(new SourceFile(cp.addUtf8("SourceFile"), 2, cp.addUtf8(file_name), cp
.getConstantPool()));
}
class_name_index = cp.addClass(class_name);
superclass_name_index = cp.addClass(super_class_name);
if (interfaces != null) {
for (String interface1 : interfaces) {
addInterface(interface1);
}
}
}
/** Convenience constructor to set up some important values initially.
*
* @param class_name fully qualified class name
* @param super_class_name fully qualified superclass name
* @param file_name source file name
* @param access_flags access qualifiers
* @param interfaces implemented interfaces
*/
public ClassGen(String class_name, String super_class_name, String file_name, int access_flags,
String[] interfaces) {
this(class_name, super_class_name, file_name, access_flags, interfaces,
new ConstantPoolGen());
}
/**
* Initialize with existing class.
* @param clazz JavaClass object (e.g. read from file)
*/
public ClassGen(JavaClass clazz) {
class_name_index = clazz.getClassNameIndex();
superclass_name_index = clazz.getSuperclassNameIndex();
class_name = clazz.getClassName();
super_class_name = clazz.getSuperclassName();
file_name = clazz.getSourceFileName();
access_flags = clazz.getAccessFlags();
cp = new ConstantPoolGen(clazz.getConstantPool());
major = clazz.getMajor();
minor = clazz.getMinor();
Attribute[] attributes = clazz.getAttributes();
// J5TODO: Could make unpacking lazy, done on first reference
AnnotationEntryGen[] annotations = unpackAnnotations(attributes);
Method[] methods = clazz.getMethods();
Field[] fields = clazz.getFields();
String[] interfaces = clazz.getInterfaceNames();
for (String interface1 : interfaces) {
addInterface(interface1);
}
for (Attribute attribute : attributes) {
if (!(attribute instanceof Annotations)) {
addAttribute(attribute);
}
}
for (AnnotationEntryGen annotation : annotations) {
addAnnotationEntry(annotation);
}
for (Method method : methods) {
addMethod(method);
}
for (Field field : fields) {
addField(field);
}
}
/**
* Look for attributes representing annotations and unpack them.
*/
private AnnotationEntryGen[] unpackAnnotations(Attribute[] attrs)
{
List<AnnotationEntryGen> annotationGenObjs = new ArrayList<AnnotationEntryGen>();
for (Attribute attr : attrs) {
if (attr instanceof RuntimeVisibleAnnotations)
{
RuntimeVisibleAnnotations rva = (RuntimeVisibleAnnotations) attr;
AnnotationEntry[] annos = rva.getAnnotationEntries();
for (AnnotationEntry a : annos) {
annotationGenObjs.add(new AnnotationEntryGen(a,
getConstantPool(), false));
}
}
else
if (attr instanceof RuntimeInvisibleAnnotations)
{
RuntimeInvisibleAnnotations ria = (RuntimeInvisibleAnnotations) attr;
AnnotationEntry[] annos = ria.getAnnotationEntries();
for (AnnotationEntry a : annos) {
annotationGenObjs.add(new AnnotationEntryGen(a,
getConstantPool(), false));
}
}
}
return annotationGenObjs.toArray(new AnnotationEntryGen[annotationGenObjs.size()]);
}
/**
* @return the (finally) built up Java class object.
*/
public JavaClass getJavaClass() {
int[] interfaces = getInterfaces();
Field[] fields = getFields();
Method[] methods = getMethods();
Attribute[] attributes = null;
if (annotation_vec.isEmpty()) {
attributes = getAttributes();
} else {
// TODO: Sometime later, trash any attributes called 'RuntimeVisibleAnnotations' or 'RuntimeInvisibleAnnotations'
Attribute[] annAttributes = AnnotationEntryGen.getAnnotationAttributes(cp,annotation_vec);
attributes = new Attribute[attribute_vec.size()+annAttributes.length];
attribute_vec.toArray(attributes);
System.arraycopy(annAttributes,0,attributes,attribute_vec.size(),annAttributes.length);
}
// Must be last since the above calls may still add something to it
ConstantPool _cp = this.cp.getFinalConstantPool();
return new JavaClass(class_name_index, superclass_name_index, file_name, major, minor,
access_flags, _cp, interfaces, fields, methods, attributes);
}
/**
* 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( String name ) {
interface_vec.add(name);
}
/**
* Remove an interface from this class.
* @param name interface to remove (fully qualified name)
*/
public void removeInterface( String name ) {
interface_vec.remove(name);
}
/**
* @return major version number of class file
*/
public int getMajor() {
return major;
}
/** Set major version number of class file, default value is 45 (JDK 1.1)
* @param major major version number
*/
public void setMajor( int major ) {
this.major = major;
}
/** Set minor version number of class file, default value is 3 (JDK 1.1)
* @param minor minor version number
*/
public void setMinor( int minor ) {
this.minor = minor;
}
/**
* @return minor version number of class file
*/
public int getMinor() {
return minor;
}
/**
* Add an attribute to this class.
* @param a attribute to add
*/
public void addAttribute( Attribute a ) {
attribute_vec.add(a);
}
public void addAnnotationEntry(AnnotationEntryGen a) {
annotation_vec.add(a);
}
/**
* Add a method to this class.
* @param m method to add
*/
public void addMethod( Method m ) {
method_vec.add(m);
}
/**
* Convenience method.
*
* Add an empty constructor to this class that does nothing but calling super().
* @param access_flags rights for constructor
*/
public void addEmptyConstructor( int access_flags ) {
InstructionList il = new InstructionList();
il.append(InstructionConstants.THIS); // Push `this'
il.append(new INVOKESPECIAL(cp.addMethodref(super_class_name, "<init>", "()V")));
il.append(InstructionConstants.RETURN);
MethodGen mg = new MethodGen(access_flags, Type.VOID, Type.NO_ARGS, null, "<init>",
class_name, il, cp);
mg.setMaxStack(1);
addMethod(mg.getMethod());
}
/**
* Add a field to this class.
* @param f field to add
*/
public void addField( Field f ) {
field_vec.add(f);
}
public boolean containsField( Field f ) {
return field_vec.contains(f);
}
/** @return field object with given name, or null
*/
public Field containsField( String name ) {
for (Field f : field_vec) {
if (f.getName().equals(name)) {
return f;
}
}
return null;
}
/** @return method object with given name and signature, or null
*/
public Method containsMethod( String name, String signature ) {
for (Method m : method_vec) {
if (m.getName().equals(name) && m.getSignature().equals(signature)) {
return m;
}
}
return null;
}
/**
* Remove an attribute from this class.
* @param a attribute to remove
*/
public void removeAttribute( Attribute a ) {
attribute_vec.remove(a);
}
/**
* Remove a method from this class.
* @param m method to remove
*/
public void removeMethod( Method m ) {
method_vec.remove(m);
}
/** Replace given method with new one. If the old one does not exist
* add the new_ method to the class anyway.
*/
public void replaceMethod( Method old, Method new_ ) {
if (new_ == null) {
throw new ClassGenException("Replacement method must not be null");
}
int i = method_vec.indexOf(old);
if (i < 0) {
method_vec.add(new_);
} else {
method_vec.set(i, new_);
}
}
/** Replace given field with new one. If the old one does not exist
* add the new_ field to the class anyway.
*/
public void replaceField( Field old, Field new_ ) {
if (new_ == null) {
throw new ClassGenException("Replacement method must not be null");
}
int i = field_vec.indexOf(old);
if (i < 0) {
field_vec.add(new_);
} else {
field_vec.set(i, new_);
}
}
/**
* Remove a field to this class.
* @param f field to remove
*/
public void removeField( Field f ) {
field_vec.remove(f);
}
public String getClassName() {
return class_name;
}
public String getSuperclassName() {
return super_class_name;
}
public String getFileName() {
return file_name;
}
public void setClassName( String name ) {
class_name = name.replace('/', '.');
class_name_index = cp.addClass(name);
}
public void setSuperclassName( String name ) {
super_class_name = name.replace('/', '.');
superclass_name_index = cp.addClass(name);
}
public Method[] getMethods() {
return method_vec.toArray(new Method[method_vec.size()]);
}
public void setMethods( Method[] methods ) {
method_vec.clear();
for (Method method : methods) {
addMethod(method);
}
}
public void setMethodAt( Method method, int pos ) {
method_vec.set(pos, method);
}
public Method getMethodAt( int pos ) {
return method_vec.get(pos);
}
public String[] getInterfaceNames() {
int size = interface_vec.size();
String[] interfaces = new String[size];
interface_vec.toArray(interfaces);
return interfaces;
}
public int[] getInterfaces() {
int size = interface_vec.size();
int[] interfaces = new int[size];
for (int i = 0; i < size; i++) {
interfaces[i] = cp.addClass(interface_vec.get(i));
}
return interfaces;
}
public Field[] getFields() {
return field_vec.toArray(new Field[field_vec.size()]);
}
public Attribute[] getAttributes() {
return attribute_vec.toArray(new Attribute[attribute_vec.size()]);
}
// J5TODO: Should we make calling unpackAnnotations() lazy and put it in here?
public AnnotationEntryGen[] getAnnotationEntries() {
return annotation_vec.toArray(new AnnotationEntryGen[annotation_vec.size()]);
}
public ConstantPoolGen getConstantPool() {
return cp;
}
public void setConstantPool( ConstantPoolGen constant_pool ) {
cp = constant_pool;
}
public void setClassNameIndex( int class_name_index ) {
this.class_name_index = class_name_index;
class_name = cp.getConstantPool().getConstantString(class_name_index,
Constants.CONSTANT_Class).replace('/', '.');
}
public void setSuperclassNameIndex( int superclass_name_index ) {
this.superclass_name_index = superclass_name_index;
super_class_name = cp.getConstantPool().getConstantString(superclass_name_index,
Constants.CONSTANT_Class).replace('/', '.');
}
public int getSuperclassNameIndex() {
return superclass_name_index;
}
public int getClassNameIndex() {
return class_name_index;
}
private List<ClassObserver> observers;
/** Add observer for this object.
*/
public void addObserver( ClassObserver o ) {
if (observers == null) {
observers = new ArrayList<ClassObserver>();
}
observers.add(o);
}
/** Remove observer for this object.
*/
public void removeObserver( ClassObserver o ) {
if (observers != null) {
observers.remove(o);
}
}
/** 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 he has finished editing the object.
*/
public void update() {
if (observers != null) {
for (ClassObserver observer : observers) {
observer.notify(this);
}
}
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new Error("Clone Not Supported"); // never happens
}
}
/**
* @return Comparison strategy object
*/
public static BCELComparator getComparator() {
return _cmp;
}
/**
* @param comparator Comparison strategy object
*/
public static void setComparator( BCELComparator comparator ) {
_cmp = comparator;
}
/**
* 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 java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals( Object obj ) {
return _cmp.equals(this, obj);
}
/**
* Return value as defined by given BCELComparator strategy.
* By default return the hashcode of the class name.
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return _cmp.hashCode(this);
}
}