| /* |
| * 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 |
| * |
| * https://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; |
| } |
| }; |
| |
| /** |
| * Gets the comparison strategy object. |
| * |
| * @return Comparison strategy object. |
| */ |
| public static BCELComparator<ClassGen> getComparator() { |
| return bcelComparator; |
| } |
| |
| /** |
| * Sets the comparison strategy object. |
| * |
| * @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 (for example 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); |
| } |
| } |
| |
| /** |
| * Adds an annotation entry to this class. |
| * |
| * @param a the annotation entry to add. |
| */ |
| 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, that is, 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. |
| * |
| * @param o the observer. |
| */ |
| 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 |
| } |
| } |
| |
| /** |
| * Checks if this class contains the given field. |
| * |
| * @param f the field to check. |
| * @return true if this class contains the field. |
| */ |
| public boolean containsField(final Field f) { |
| return fieldList.contains(f); |
| } |
| |
| /** |
| * Gets the field object with given name, or null. |
| * |
| * @param name the field name. |
| * @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; |
| } |
| |
| /** |
| * Gets the method object with given name and signature, or null. |
| * |
| * @param name the method name. |
| * @param signature the method signature. |
| * @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); |
| } |
| |
| /** |
| * Gets the annotation entries. |
| * |
| * @return the annotation entries. |
| */ |
| // J5TODO: Should we make calling unpackAnnotations() lazy and put it in here? |
| public AnnotationEntryGen[] getAnnotationEntries() { |
| return annotationList.toArray(AnnotationEntryGen.EMPTY_ARRAY); |
| } |
| |
| /** |
| * Gets the attributes. |
| * |
| * @return the attributes. |
| */ |
| public Attribute[] getAttributes() { |
| return attributeList.toArray(Attribute.EMPTY_ARRAY); |
| } |
| |
| /** |
| * Gets the class name. |
| * |
| * @return the class name. |
| */ |
| public String getClassName() { |
| return className; |
| } |
| |
| /** |
| * Gets the class name index. |
| * |
| * @return the class name index. |
| */ |
| public int getClassNameIndex() { |
| return classNameIndex; |
| } |
| |
| /** |
| * Gets the constant pool. |
| * |
| * @return the constant pool. |
| */ |
| public ConstantPoolGen getConstantPool() { |
| return cp; |
| } |
| |
| /** |
| * Gets the fields. |
| * |
| * @return the fields. |
| */ |
| public Field[] getFields() { |
| return fieldList.toArray(Field.EMPTY_ARRAY); |
| } |
| |
| /** |
| * Gets the file name. |
| * |
| * @return the file name. |
| */ |
| public String getFileName() { |
| return fileName; |
| } |
| |
| /** |
| * Gets the interface names. |
| * |
| * @return the interface names. |
| */ |
| public String[] getInterfaceNames() { |
| return interfaceList.toArray(ArrayUtils.EMPTY_STRING_ARRAY); |
| } |
| |
| /** |
| * Gets the interfaces. |
| * |
| * @return the interfaces. |
| */ |
| 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; |
| } |
| |
| /** |
| * Gets the (finally) built up Java class object. |
| * |
| * @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); |
| } |
| |
| /** |
| * Gets major version number of class file. |
| * |
| * @return major version number of class file. |
| */ |
| public int getMajor() { |
| return major; |
| } |
| |
| /** |
| * Gets the method at the given position. |
| * |
| * @param pos the position. |
| * @return the method at the given position. |
| */ |
| public Method getMethodAt(final int pos) { |
| return methodList.get(pos); |
| } |
| |
| /** |
| * Gets the methods. |
| * |
| * @return the methods. |
| */ |
| public Method[] getMethods() { |
| return methodList.toArray(Method.EMPTY_ARRAY); |
| } |
| |
| /** |
| * Gets minor version number of class file. |
| * |
| * @return minor version number of class file. |
| */ |
| public int getMinor() { |
| return minor; |
| } |
| |
| /** |
| * Gets the superclass name. |
| * |
| * @return the superclass name. |
| */ |
| public String getSuperclassName() { |
| return superClassName; |
| } |
| |
| /** |
| * Gets the superclass name index. |
| * |
| * @return the superclass name index. |
| */ |
| 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. |
| * |
| * @param o the observer to remove. |
| */ |
| 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. |
| * |
| * @param old the old field. |
| * @param newField the new field. |
| */ |
| 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. |
| * |
| * @param old the old method. |
| * @param newMethod the new method. |
| */ |
| 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); |
| } |
| } |
| |
| /** |
| * Sets the class name. |
| * |
| * @param name the class name. |
| */ |
| public void setClassName(final String name) { |
| className = Utility.pathToPackage(name); |
| classNameIndex = cp.addClass(name); |
| } |
| |
| /** |
| * Sets the class name index. |
| * |
| * @param classNameIndex the class name index. |
| */ |
| public void setClassNameIndex(final int classNameIndex) { |
| this.classNameIndex = classNameIndex; |
| this.className = Utility.pathToPackage(cp.getConstantPool().getConstantString(classNameIndex, Const.CONSTANT_Class)); |
| } |
| |
| /** |
| * Sets the constant pool. |
| * |
| * @param constantPool the constant pool. |
| */ |
| 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; |
| } |
| |
| /** |
| * Sets the method at the given position. |
| * |
| * @param method the method. |
| * @param pos the position. |
| */ |
| public void setMethodAt(final Method method, final int pos) { |
| methodList.set(pos, method); |
| } |
| |
| /** |
| * Sets the methods. |
| * |
| * @param methods the methods. |
| */ |
| 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; |
| } |
| |
| /** |
| * Sets the superclass name. |
| * |
| * @param name the superclass name. |
| */ |
| public void setSuperclassName(final String name) { |
| superClassName = Utility.pathToPackage(name); |
| superclassNameIndex = cp.addClass(name); |
| } |
| |
| /** |
| * Sets the superclass name index. |
| * |
| * @param superclassNameIndex the superclass name index. |
| */ |
| 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); |
| } |
| } |
| } |
| } |