ClassGen.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.bcel.generic;

  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collections;
  21. import java.util.List;
  22. import java.util.Objects;

  23. import org.apache.bcel.Const;
  24. import org.apache.bcel.classfile.AccessFlags;
  25. import org.apache.bcel.classfile.Annotations;
  26. import org.apache.bcel.classfile.Attribute;
  27. import org.apache.bcel.classfile.ConstantPool;
  28. import org.apache.bcel.classfile.Field;
  29. import org.apache.bcel.classfile.JavaClass;
  30. import org.apache.bcel.classfile.Method;
  31. import org.apache.bcel.classfile.RuntimeInvisibleAnnotations;
  32. import org.apache.bcel.classfile.RuntimeVisibleAnnotations;
  33. import org.apache.bcel.classfile.SourceFile;
  34. import org.apache.bcel.classfile.Utility;
  35. import org.apache.bcel.util.BCELComparator;
  36. import org.apache.commons.lang3.ArrayUtils;

  37. /**
  38.  * Template class for building up a java class. May be initialized with an existing Java class (file).
  39.  *
  40.  * @see JavaClass
  41.  */
  42. public class ClassGen extends AccessFlags implements Cloneable {

  43.     private static BCELComparator<ClassGen> bcelComparator = new BCELComparator<ClassGen>() {

  44.         @Override
  45.         public boolean equals(final ClassGen a, final ClassGen b) {
  46.             return a == b || a != null && b != null && Objects.equals(a.getClassName(), b.getClassName());
  47.         }

  48.         @Override
  49.         public int hashCode(final ClassGen o) {
  50.             return o != null ? Objects.hashCode(o.getClassName()) : 0;
  51.         }
  52.     };

  53.     /**
  54.      * @return Comparison strategy object
  55.      */
  56.     public static BCELComparator<ClassGen> getComparator() {
  57.         return bcelComparator;
  58.     }

  59.     /**
  60.      * @param comparator Comparison strategy object
  61.      */
  62.     public static void setComparator(final BCELComparator<ClassGen> comparator) {
  63.         bcelComparator = comparator;
  64.     }

  65.     /*
  66.      * Corresponds to the fields found in a JavaClass object.
  67.      */
  68.     private String className;
  69.     private String superClassName;
  70.     private final String fileName;
  71.     private int classNameIndex = -1;
  72.     private int superclassNameIndex = -1;
  73.     private int major = Const.MAJOR_1_1;
  74.     private int minor = Const.MINOR_1_1;
  75.     private ConstantPoolGen cp; // Template for building up constant pool
  76.     // ArrayLists instead of arrays to gather fields, methods, etc.
  77.     private final List<Field> fieldList = new ArrayList<>();
  78.     private final List<Method> methodList = new ArrayList<>();

  79.     private final List<Attribute> attributeList = new ArrayList<>();

  80.     private final List<String> interfaceList = new ArrayList<>();

  81.     private final List<AnnotationEntryGen> annotationList = new ArrayList<>();

  82.     private List<ClassObserver> observers;

  83.     /**
  84.      * Constructs a new instance from an existing class.
  85.      *
  86.      * @param clazz JavaClass object (e.g. read from file)
  87.      */
  88.     public ClassGen(final JavaClass clazz) {
  89.         super(clazz.getAccessFlags());
  90.         classNameIndex = clazz.getClassNameIndex();
  91.         superclassNameIndex = clazz.getSuperclassNameIndex();
  92.         className = clazz.getClassName();
  93.         superClassName = clazz.getSuperclassName();
  94.         fileName = clazz.getSourceFileName();
  95.         cp = new ConstantPoolGen(clazz.getConstantPool());
  96.         major = clazz.getMajor();
  97.         minor = clazz.getMinor();
  98.         final Attribute[] attributes = clazz.getAttributes();
  99.         // J5TODO: Could make unpacking lazy, done on first reference
  100.         final AnnotationEntryGen[] annotations = unpackAnnotations(attributes);
  101.         final String[] interfaceNames = clazz.getInterfaceNames();
  102.         if (interfaceNames != null) {
  103.             Collections.addAll(interfaceList, interfaceNames);
  104.         }
  105.         if (attributes != null) {
  106.             for (final Attribute attribute : attributes) {
  107.                 if (!(attribute instanceof Annotations)) {
  108.                     addAttribute(attribute);
  109.                 }
  110.             }
  111.         }
  112.         Collections.addAll(annotationList, annotations);
  113.         final Method[] methods = clazz.getMethods();
  114.         if (methods != null) {
  115.             Collections.addAll(methodList, methods);
  116.         }
  117.         final Field[] fields = clazz.getFields();
  118.         if (fields != null) {
  119.             Collections.addAll(fieldList, fields);
  120.         }
  121.     }

  122.     /**
  123.      * Convenience constructor to set up some important values initially.
  124.      *
  125.      * @param className fully qualified class name
  126.      * @param superClassName fully qualified superclass name
  127.      * @param fileName source file name
  128.      * @param accessFlags access qualifiers
  129.      * @param interfaces implemented interfaces
  130.      */
  131.     public ClassGen(final String className, final String superClassName, final String fileName, final int accessFlags, final String[] interfaces) {
  132.         this(className, superClassName, fileName, accessFlags, interfaces, new ConstantPoolGen());
  133.     }

  134.     /**
  135.      * Convenience constructor to set up some important values initially.
  136.      *
  137.      * @param className fully qualified class name
  138.      * @param superClassName fully qualified superclass name
  139.      * @param fileName source file name
  140.      * @param accessFlags access qualifiers
  141.      * @param interfaces implemented interfaces
  142.      * @param cp constant pool to use
  143.      */
  144.     public ClassGen(final String className, final String superClassName, final String fileName, final int accessFlags, final String[] interfaces,
  145.         final ConstantPoolGen cp) {
  146.         super(accessFlags);
  147.         this.className = className;
  148.         this.superClassName = superClassName;
  149.         this.fileName = fileName;
  150.         this.cp = cp;
  151.         // Put everything needed by default into the constant pool and the vectors
  152.         if (fileName != null) {
  153.             addAttribute(new SourceFile(cp.addUtf8("SourceFile"), 2, cp.addUtf8(fileName), cp.getConstantPool()));
  154.         }
  155.         classNameIndex = cp.addClass(className);
  156.         superclassNameIndex = cp.addClass(superClassName);
  157.         if (interfaces != null) {
  158.             Collections.addAll(interfaceList, interfaces);
  159.         }
  160.     }

  161.     public void addAnnotationEntry(final AnnotationEntryGen a) {
  162.         annotationList.add(a);
  163.     }

  164.     /**
  165.      * Add an attribute to this class.
  166.      *
  167.      * @param a attribute to add
  168.      */
  169.     public void addAttribute(final Attribute a) {
  170.         attributeList.add(a);
  171.     }

  172.     /**
  173.      * Convenience method.
  174.      *
  175.      * Add an empty constructor to this class that does nothing but calling super().
  176.      *
  177.      * @param accessFlags rights for constructor
  178.      */
  179.     public void addEmptyConstructor(final int accessFlags) {
  180.         final InstructionList il = new InstructionList();
  181.         il.append(InstructionConst.THIS); // Push 'this'
  182.         il.append(new INVOKESPECIAL(cp.addMethodref(superClassName, Const.CONSTRUCTOR_NAME, "()V")));
  183.         il.append(InstructionConst.RETURN);
  184.         final MethodGen mg = new MethodGen(accessFlags, Type.VOID, Type.NO_ARGS, null, Const.CONSTRUCTOR_NAME, className, il, cp);
  185.         mg.setMaxStack(1);
  186.         addMethod(mg.getMethod());
  187.     }

  188.     /**
  189.      * Add a field to this class.
  190.      *
  191.      * @param f field to add
  192.      */
  193.     public void addField(final Field f) {
  194.         fieldList.add(f);
  195.     }

  196.     /**
  197.      * Add an interface to this class, i.e., this class has to implement it.
  198.      *
  199.      * @param name interface to implement (fully qualified class name)
  200.      */
  201.     public void addInterface(final String name) {
  202.         interfaceList.add(name);
  203.     }

  204.     /**
  205.      * Add a method to this class.
  206.      *
  207.      * @param m method to add
  208.      */
  209.     public void addMethod(final Method m) {
  210.         methodList.add(m);
  211.     }

  212.     /**
  213.      * Add observer for this object.
  214.      */
  215.     public void addObserver(final ClassObserver o) {
  216.         if (observers == null) {
  217.             observers = new ArrayList<>();
  218.         }
  219.         observers.add(o);
  220.     }

  221.     @Override
  222.     public Object clone() {
  223.         try {
  224.             return super.clone();
  225.         } catch (final CloneNotSupportedException e) {
  226.             throw new UnsupportedOperationException("Clone Not Supported", e); // never happens
  227.         }
  228.     }

  229.     public boolean containsField(final Field f) {
  230.         return fieldList.contains(f);
  231.     }

  232.     /**
  233.      * @return field object with given name, or null
  234.      */
  235.     public Field containsField(final String name) {
  236.         for (final Field f : fieldList) {
  237.             if (f.getName().equals(name)) {
  238.                 return f;
  239.             }
  240.         }
  241.         return null;
  242.     }

  243.     /**
  244.      * @return method object with given name and signature, or null
  245.      */
  246.     public Method containsMethod(final String name, final String signature) {
  247.         for (final Method m : methodList) {
  248.             if (m.getName().equals(name) && m.getSignature().equals(signature)) {
  249.                 return m;
  250.             }
  251.         }
  252.         return null;
  253.     }

  254.     /**
  255.      * Return value as defined by given BCELComparator strategy. By default two ClassGen objects are said to be equal when
  256.      * their class names are equal.
  257.      *
  258.      * @see Object#equals(Object)
  259.      */
  260.     @Override
  261.     public boolean equals(final Object obj) {
  262.         return obj instanceof ClassGen && bcelComparator.equals(this, (ClassGen) obj);
  263.     }

  264.     // J5TODO: Should we make calling unpackAnnotations() lazy and put it in here?
  265.     public AnnotationEntryGen[] getAnnotationEntries() {
  266.         return annotationList.toArray(AnnotationEntryGen.EMPTY_ARRAY);
  267.     }

  268.     public Attribute[] getAttributes() {
  269.         return attributeList.toArray(Attribute.EMPTY_ARRAY);
  270.     }

  271.     public String getClassName() {
  272.         return className;
  273.     }

  274.     public int getClassNameIndex() {
  275.         return classNameIndex;
  276.     }

  277.     public ConstantPoolGen getConstantPool() {
  278.         return cp;
  279.     }

  280.     public Field[] getFields() {
  281.         return fieldList.toArray(Field.EMPTY_ARRAY);
  282.     }

  283.     public String getFileName() {
  284.         return fileName;
  285.     }

  286.     public String[] getInterfaceNames() {
  287.         return interfaceList.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
  288.     }

  289.     public int[] getInterfaces() {
  290.         final int size = interfaceList.size();
  291.         final int[] interfaces = new int[size];
  292.         Arrays.setAll(interfaces, i -> cp.addClass(interfaceList.get(i)));
  293.         return interfaces;
  294.     }

  295.     /**
  296.      * @return the (finally) built up Java class object.
  297.      */
  298.     public JavaClass getJavaClass() {
  299.         final int[] interfaces = getInterfaces();
  300.         final Field[] fields = getFields();
  301.         final Method[] methods = getMethods();
  302.         Attribute[] attributes = null;
  303.         if (annotationList.isEmpty()) {
  304.             attributes = getAttributes();
  305.         } else {
  306.             // TODO: Sometime later, trash any attributes called 'RuntimeVisibleAnnotations' or 'RuntimeInvisibleAnnotations'
  307.             final Attribute[] annAttributes = AnnotationEntryGen.getAnnotationAttributes(cp, getAnnotationEntries());
  308.             attributes = new Attribute[attributeList.size() + annAttributes.length];
  309.             attributeList.toArray(attributes);
  310.             System.arraycopy(annAttributes, 0, attributes, attributeList.size(), annAttributes.length);
  311.         }
  312.         // Must be last since the above calls may still add something to it
  313.         final ConstantPool cp = this.cp.getFinalConstantPool();
  314.         return new JavaClass(classNameIndex, superclassNameIndex, fileName, major, minor, super.getAccessFlags(), cp, interfaces, fields, methods,
  315.             attributes);
  316.     }

  317.     /**
  318.      * @return major version number of class file
  319.      */
  320.     public int getMajor() {
  321.         return major;
  322.     }

  323.     public Method getMethodAt(final int pos) {
  324.         return methodList.get(pos);
  325.     }

  326.     public Method[] getMethods() {
  327.         return methodList.toArray(Method.EMPTY_ARRAY);
  328.     }

  329.     /**
  330.      * @return minor version number of class file
  331.      */
  332.     public int getMinor() {
  333.         return minor;
  334.     }

  335.     public String getSuperclassName() {
  336.         return superClassName;
  337.     }

  338.     public int getSuperclassNameIndex() {
  339.         return superclassNameIndex;
  340.     }

  341.     /**
  342.      * Return value as defined by given BCELComparator strategy. By default return the hash code of the class name.
  343.      *
  344.      * @see Object#hashCode()
  345.      */
  346.     @Override
  347.     public int hashCode() {
  348.         return bcelComparator.hashCode(this);
  349.     }

  350.     /**
  351.      * Remove an attribute from this class.
  352.      *
  353.      * @param a attribute to remove
  354.      */
  355.     public void removeAttribute(final Attribute a) {
  356.         attributeList.remove(a);
  357.     }

  358.     /**
  359.      * Remove a field to this class.
  360.      *
  361.      * @param f field to remove
  362.      */
  363.     public void removeField(final Field f) {
  364.         fieldList.remove(f);
  365.     }

  366.     /**
  367.      * Remove an interface from this class.
  368.      *
  369.      * @param name interface to remove (fully qualified name)
  370.      */
  371.     public void removeInterface(final String name) {
  372.         interfaceList.remove(name);
  373.     }

  374.     /**
  375.      * Remove a method from this class.
  376.      *
  377.      * @param m method to remove
  378.      */
  379.     public void removeMethod(final Method m) {
  380.         methodList.remove(m);
  381.     }

  382.     /**
  383.      * Remove observer for this object.
  384.      */
  385.     public void removeObserver(final ClassObserver o) {
  386.         if (observers != null) {
  387.             observers.remove(o);
  388.         }
  389.     }

  390.     /**
  391.      * Replace given field with new one. If the old one does not exist add the new_ field to the class anyway.
  392.      */
  393.     public void replaceField(final Field old, final Field newField) {
  394.         if (newField == null) {
  395.             throw new ClassGenException("Replacement method must not be null");
  396.         }
  397.         final int i = fieldList.indexOf(old);
  398.         if (i < 0) {
  399.             fieldList.add(newField);
  400.         } else {
  401.             fieldList.set(i, newField);
  402.         }
  403.     }

  404.     /**
  405.      * Replace given method with new one. If the old one does not exist add the newMethod method to the class anyway.
  406.      */
  407.     public void replaceMethod(final Method old, final Method newMethod) {
  408.         if (newMethod == null) {
  409.             throw new ClassGenException("Replacement method must not be null");
  410.         }
  411.         final int i = methodList.indexOf(old);
  412.         if (i < 0) {
  413.             methodList.add(newMethod);
  414.         } else {
  415.             methodList.set(i, newMethod);
  416.         }
  417.     }

  418.     public void setClassName(final String name) {
  419.         className = Utility.pathToPackage(name);
  420.         classNameIndex = cp.addClass(name);
  421.     }

  422.     public void setClassNameIndex(final int classNameIndex) {
  423.         this.classNameIndex = classNameIndex;
  424.         this.className = Utility.pathToPackage(cp.getConstantPool().getConstantString(classNameIndex, Const.CONSTANT_Class));
  425.     }

  426.     public void setConstantPool(final ConstantPoolGen constantPool) {
  427.         cp = constantPool;
  428.     }

  429.     /**
  430.      * Sets major version number of class file, default value is 45 (JDK 1.1)
  431.      *
  432.      * @param major major version number
  433.      */
  434.     public void setMajor(final int major) { // TODO could be package-protected - only called by test code
  435.         this.major = major;
  436.     }

  437.     public void setMethodAt(final Method method, final int pos) {
  438.         methodList.set(pos, method);
  439.     }

  440.     public void setMethods(final Method[] methods) {
  441.         methodList.clear();
  442.         if (methods != null) {
  443.             Collections.addAll(methodList, methods);
  444.         }
  445.     }

  446.     /**
  447.      * Sets minor version number of class file, default value is 3 (JDK 1.1)
  448.      *
  449.      * @param minor minor version number
  450.      */
  451.     public void setMinor(final int minor) { // TODO could be package-protected - only called by test code
  452.         this.minor = minor;
  453.     }

  454.     public void setSuperclassName(final String name) {
  455.         superClassName = Utility.pathToPackage(name);
  456.         superclassNameIndex = cp.addClass(name);
  457.     }

  458.     public void setSuperclassNameIndex(final int superclassNameIndex) {
  459.         this.superclassNameIndex = superclassNameIndex;
  460.         superClassName = Utility.pathToPackage(cp.getConstantPool().getConstantString(superclassNameIndex, Const.CONSTANT_Class));
  461.     }

  462.     /**
  463.      * Unpacks attributes representing annotations.
  464.      */
  465.     private AnnotationEntryGen[] unpackAnnotations(final Attribute[] attributes) {
  466.         final List<AnnotationEntryGen> annotationGenObjs = new ArrayList<>();
  467.         if (attributes != null) {
  468.             for (final Attribute attr : attributes) {
  469.                 if (attr instanceof RuntimeVisibleAnnotations) {
  470.                     final RuntimeVisibleAnnotations rva = (RuntimeVisibleAnnotations) attr;
  471.                     rva.forEach(a -> annotationGenObjs.add(new AnnotationEntryGen(a, getConstantPool(), false)));
  472.                 } else if (attr instanceof RuntimeInvisibleAnnotations) {
  473.                     final RuntimeInvisibleAnnotations ria = (RuntimeInvisibleAnnotations) attr;
  474.                     ria.forEach(a -> annotationGenObjs.add(new AnnotationEntryGen(a, getConstantPool(), false)));
  475.                 }
  476.             }
  477.         }
  478.         return annotationGenObjs.toArray(AnnotationEntryGen.EMPTY_ARRAY);
  479.     }

  480.     /**
  481.      * Call notify() method on all observers. This method is not called automatically whenever the state has changed, but
  482.      * has to be called by the user after they have finished editing the object.
  483.      */
  484.     public void update() {
  485.         if (observers != null) {
  486.             for (final ClassObserver observer : observers) {
  487.                 observer.notify(this);
  488.             }
  489.         }
  490.     }
  491. }