| /* |
| * 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); |
| } |
| } |