org.aspectj.apache.bcel.generic.ClassGen.java Source code

Java tutorial

Introduction

Here is the source code for org.aspectj.apache.bcel.generic.ClassGen.java

Source

package org.aspectj.apache.bcel.generic;

/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *    "Apache BCEL" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache BCEL", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.lang.reflect.Modifier;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.aspectj.apache.bcel.Constants;
import org.aspectj.apache.bcel.classfile.Attribute;
import org.aspectj.apache.bcel.classfile.ConstantPool;
import org.aspectj.apache.bcel.classfile.Field;
import org.aspectj.apache.bcel.classfile.JavaClass;
import org.aspectj.apache.bcel.classfile.Method;
import org.aspectj.apache.bcel.classfile.Modifiers;
import org.aspectj.apache.bcel.classfile.SourceFile;
import org.aspectj.apache.bcel.classfile.Utility;
import org.aspectj.apache.bcel.classfile.annotation.AnnotationGen;
import org.aspectj.apache.bcel.classfile.annotation.RuntimeInvisAnnos;
import org.aspectj.apache.bcel.classfile.annotation.RuntimeVisAnnos;

/**
 * Template class for building up a java class. May be initialized with an existing java class.
 * 
 * @see JavaClass
 * @version $Id: ClassGen.java,v 1.15 2009/09/15 19:40:14 aclement Exp $
 * @author <A HREF="mailto:markus.dahm@berlin.de">M. Dahm</A>
 * 
 *         Upgraded, Andy Clement 9th Mar 06 - calculates SUID
 */
public class ClassGen extends Modifiers implements Cloneable {

    private String classname;
    private String superclassname;
    private String filename;
    private int classnameIndex = -1;
    private int superclassnameIndex = -1;
    private int major = Constants.MAJOR_1_1;
    private int minor = Constants.MINOR_1_1;
    private ConstantPool cpool;
    private List<Field> fieldsList = new ArrayList<Field>();
    private List<Method> methodsList = new ArrayList<Method>();
    private List<Attribute> attributesList = new ArrayList<Attribute>();
    private List<String> interfaceList = new ArrayList<String>();
    private List<AnnotationGen> annotationsList = new ArrayList<AnnotationGen>();

    public ClassGen(String classname, String superclassname, String filename, int modifiers,
            String[] interfacenames, ConstantPool cpool) {
        this.classname = classname;
        this.superclassname = superclassname;
        this.filename = filename;
        this.modifiers = modifiers;
        this.cpool = cpool;
        if (filename != null) {
            addAttribute(new SourceFile(cpool.addUtf8("SourceFile"), 2, cpool.addUtf8(filename), cpool));
        }
        this.classnameIndex = cpool.addClass(classname);
        this.superclassnameIndex = cpool.addClass(superclassname);
        if (interfacenames != null) {
            for (String interfacename : interfacenames) {
                addInterface(interfacename);
            }
        }
    }

    public ClassGen(String classname, String superclassname, String filename, int modifiers,
            String[] interfacenames) {
        this(classname, superclassname, filename, modifiers, interfacenames, new ConstantPool());
    }

    public ClassGen(JavaClass clazz) {
        classnameIndex = clazz.getClassNameIndex();
        superclassnameIndex = clazz.getSuperclassNameIndex();
        classname = clazz.getClassName();
        superclassname = clazz.getSuperclassName();
        filename = clazz.getSourceFileName();
        modifiers = clazz.getModifiers();
        cpool = clazz.getConstantPool().copy();
        major = clazz.getMajor();
        minor = clazz.getMinor();

        Method[] methods = clazz.getMethods();
        Field[] fields = clazz.getFields();
        String[] interfaces = clazz.getInterfaceNames();

        for (int i = 0; i < interfaces.length; i++) {
            addInterface(interfaces[i]);
        }

        // OPTIMIZE Could make unpacking lazy, done on first reference
        Attribute[] attributes = clazz.getAttributes();
        for (Attribute attr : attributes) {
            if (attr instanceof RuntimeVisAnnos) {
                RuntimeVisAnnos rva = (RuntimeVisAnnos) attr;
                List<AnnotationGen> annos = rva.getAnnotations();
                for (AnnotationGen a : annos) {
                    annotationsList.add(new AnnotationGen(a, cpool, false));
                }
            } else if (attr instanceof RuntimeInvisAnnos) {
                RuntimeInvisAnnos ria = (RuntimeInvisAnnos) attr;
                List<AnnotationGen> annos = ria.getAnnotations();
                for (AnnotationGen anno : annos) {
                    annotationsList.add(new AnnotationGen(anno, cpool, false));
                }
            } else {
                attributesList.add(attr);
            }
        }

        for (int i = 0; i < methods.length; i++) {
            addMethod(methods[i]);
        }

        for (int i = 0; i < fields.length; i++) {
            addField(fields[i]);
        }
    }

    /**
     * @return build and return a JavaClass
     */
    public JavaClass getJavaClass() {
        int[] interfaces = getInterfaces();
        Field[] fields = getFields();
        Method[] methods = getMethods();

        Collection<Attribute> attributes = null;
        if (annotationsList.size() == 0) {
            attributes = attributesList;
        } else {
            // TODO: Sometime later, trash any attributes called 'RuntimeVisibleAnnotations' or 'RuntimeInvisibleAnnotations'
            attributes = new ArrayList<Attribute>();
            attributes.addAll(Utility.getAnnotationAttributes(cpool, annotationsList));
            attributes.addAll(attributesList);
        }

        // Must be last since the above calls may still add something to it
        ConstantPool cp = this.cpool.getFinalConstantPool();

        return new JavaClass(classnameIndex, superclassnameIndex, filename, major, minor, modifiers, cp, interfaces,
                fields, methods, attributes.toArray(new Attribute[attributes.size()]));// OPTIMIZE avoid toArray()?
    }

    public void addInterface(String name) {
        interfaceList.add(name);
    }

    public void removeInterface(String name) {
        interfaceList.remove(name);
    }

    public int getMajor() {
        return major;
    }

    public void setMajor(int major) {
        this.major = major;
    }

    public void setMinor(int minor) {
        this.minor = minor;
    }

    public int getMinor() {
        return minor;
    }

    public void addAttribute(Attribute a) {
        attributesList.add(a);
    }

    public void addAnnotation(AnnotationGen a) {
        annotationsList.add(a);
    }

    public void addMethod(Method m) {
        methodsList.add(m);
    }

    /**
     * Convenience method.
     * 
     * Add an empty constructor to this class that does nothing but calling super().
     * 
     * @param access rights for constructor
     */
    public void addEmptyConstructor(int access_flags) {
        InstructionList il = new InstructionList();
        il.append(InstructionConstants.THIS); // Push `this'
        il.append(new InvokeInstruction(Constants.INVOKESPECIAL,
                cpool.addMethodref(superclassname, "<init>", "()V")));
        il.append(InstructionConstants.RETURN);

        MethodGen mg = new MethodGen(access_flags, Type.VOID, Type.NO_ARGS, null, "<init>", classname, il, cpool);
        mg.setMaxStack(1);
        mg.setMaxLocals();
        addMethod(mg.getMethod());
    }

    /**
     * Add a field to this class.
     * 
     * @param f field to add
     */
    public void addField(Field f) {
        fieldsList.add(f);
    }

    public boolean containsField(Field f) {
        return fieldsList.contains(f);
    }

    /**
     * @return field object with given name, or null if not found
     */
    public Field findsField(String name) {
        for (Field field : fieldsList) {
            if (field.getName().equals(name)) {
                return field;
            }
        }
        return null;
    }

    /**
     * @return method object with given name and signature, or null if not found
     */
    public Method containsMethod(String name, String signature) {
        for (Method method : methodsList) {
            if (method.getName().equals(name) && method.getSignature().equals(signature)) {
                return method;
            }
        }
        return null;
    }

    public void removeAttribute(Attribute a) {
        attributesList.remove(a);
    }

    public void removeAnnotation(AnnotationGen a) {
        annotationsList.remove(a);
    }

    public void removeMethod(Method m) {
        methodsList.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 = methodsList.indexOf(old);

        if (i < 0)
            methodsList.add(new_);
        else
            methodsList.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 = fieldsList.indexOf(old);

        if (i < 0)
            fieldsList.add(new_);
        else
            fieldsList.set(i, new_);
    }

    public void removeField(Field f) {
        fieldsList.remove(f);
    }

    public String getClassName() {
        return classname;
    }

    public String getSuperclassName() {
        return superclassname;
    }

    public String getFileName() {
        return filename;
    }

    public void setClassName(String name) {
        classname = name.replace('/', '.');
        classnameIndex = cpool.addClass(name);
    }

    public void setSuperclassName(String name) {
        superclassname = name.replace('/', '.');
        superclassnameIndex = cpool.addClass(name);
    }

    public Method[] getMethods() {
        Method[] methods = new Method[methodsList.size()];
        methodsList.toArray(methods);
        return methods;
    }

    public void setMethods(Method[] methods) {
        methodsList.clear();
        for (int m = 0; m < methods.length; m++)
            addMethod(methods[m]);
    }

    public void setFields(Field[] fs) {
        fieldsList.clear();
        for (int m = 0; m < fs.length; m++)
            addField(fs[m]);
    }

    public void setMethodAt(Method method, int pos) {
        methodsList.set(pos, method);
    }

    public Method getMethodAt(int pos) {
        return methodsList.get(pos);
    }

    public String[] getInterfaceNames() {
        int size = interfaceList.size();
        String[] interfaces = new String[size];

        interfaceList.toArray(interfaces);
        return interfaces;
    }

    public int[] getInterfaces() {
        int size = interfaceList.size();
        int[] interfaces = new int[size];

        for (int i = 0; i < size; i++)
            interfaces[i] = cpool.addClass(interfaceList.get(i));

        return interfaces;
    }

    public Field[] getFields() {
        Field[] fields = new Field[fieldsList.size()];
        fieldsList.toArray(fields);
        return fields;
    }

    public Collection<Attribute> getAttributes() {
        return attributesList;
    }

    // J5TODO: Should we make calling unpackAnnotations() lazy and put it in here?
    public AnnotationGen[] getAnnotations() {
        AnnotationGen[] annotations = new AnnotationGen[annotationsList.size()];
        annotationsList.toArray(annotations);
        return annotations;
    }

    public ConstantPool getConstantPool() {
        return cpool;
    }

    public void setConstantPool(ConstantPool constant_pool) {
        cpool = constant_pool;
    }

    public void setClassNameIndex(int class_name_index) {
        this.classnameIndex = class_name_index;
        classname = cpool.getConstantString(class_name_index, Constants.CONSTANT_Class).replace('/', '.');
    }

    public void setSuperclassNameIndex(int superclass_name_index) {
        this.superclassnameIndex = superclass_name_index;
        superclassname = cpool.getConstantString(superclass_name_index, Constants.CONSTANT_Class).replace('/', '.');
    }

    public int getSuperclassNameIndex() {
        return superclassnameIndex;
    }

    public int getClassNameIndex() {
        return classnameIndex;
    }

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            System.err.println(e);
            return null;
        }
    }

    public final boolean isAnnotation() {
        return (modifiers & Constants.ACC_ANNOTATION) != 0;
    }

    public final boolean isEnum() {
        return (modifiers & Constants.ACC_ENUM) != 0;
    }

    /**
     * Calculate the SerialVersionUID for a class.
     */
    public long getSUID() {
        try {

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(baos);

            // 1. classname
            dos.writeUTF(getClassName());

            // 2. classmodifiers: ACC_PUBLIC, ACC_FINAL, ACC_INTERFACE, and ACC_ABSTRACT
            int classmods = 0;
            classmods |= (isPublic() ? Constants.ACC_PUBLIC : 0);
            classmods |= (isFinal() ? Constants.ACC_FINAL : 0);
            classmods |= (isInterface() ? Constants.ACC_INTERFACE : 0);

            if (isAbstract()) {
                // if an interface then abstract is only set if it has methods
                if (isInterface()) {
                    if (methodsList.size() > 0)
                        classmods |= Constants.ACC_ABSTRACT;
                } else {
                    classmods |= Constants.ACC_ABSTRACT;
                }
            }

            dos.writeInt(classmods);

            // 3. ordered list of interfaces
            String[] names = getInterfaceNames();
            if (names != null) {
                Arrays.sort(names);
                for (int i = 0; i < names.length; i++)
                    dos.writeUTF(names[i]);
            }

            // 4. ordered list of fields (ignoring private static and private transient fields):
            // (relevant modifiers are ACC_PUBLIC, ACC_PRIVATE,
            // ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_VOLATILE,
            // ACC_TRANSIENT)
            List<Field> relevantFields = new ArrayList<Field>();
            for (Field field : fieldsList) {
                if (!(field.isPrivate() && field.isStatic()) && !(field.isPrivate() && field.isTransient())) {
                    relevantFields.add(field);
                }
            }
            Collections.sort(relevantFields, new FieldComparator());
            int relevantFlags = Constants.ACC_PUBLIC | Constants.ACC_PRIVATE | Constants.ACC_PROTECTED
                    | Constants.ACC_STATIC | Constants.ACC_FINAL | Constants.ACC_VOLATILE | Constants.ACC_TRANSIENT;
            for (Field f : relevantFields) {
                dos.writeUTF(f.getName());
                dos.writeInt(relevantFlags & f.getModifiers());
                dos.writeUTF(f.getType().getSignature());
            }

            // some up front method processing: discover clinit, init and ordinary methods of interest:
            List<Method> relevantMethods = new ArrayList<Method>();
            List<Method> relevantCtors = new ArrayList<Method>();
            boolean hasClinit = false;
            for (Method m : methodsList) {
                boolean couldBeInitializer = m.getName().charAt(0) == '<';
                if (couldBeInitializer && m.getName().equals("<clinit>")) {
                    hasClinit = true;
                } else if (couldBeInitializer && m.getName().equals("<init>")) {
                    if (!m.isPrivate())
                        relevantCtors.add(m);
                } else {
                    if (!m.isPrivate())
                        relevantMethods.add(m);
                }
            }
            Collections.sort(relevantCtors, new ConstructorComparator());
            Collections.sort(relevantMethods, new MethodComparator());

            // 5. If a class initializer exists, write out the following:
            // 1. The name of the method, <clinit>.
            // 2. The modifier of the method, java.lang.reflect.Modifier.STATIC, written as a 32-bit integer.
            // 3. The descriptor of the method, ()V.
            if (hasClinit) {
                dos.writeUTF("<clinit>");
                dos.writeInt(Modifier.STATIC);
                dos.writeUTF("()V");
            }

            // for methods and constructors:
            // ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_SYNCHRONIZED,
            // ACC_NATIVE, ACC_ABSTRACT and ACC_STRICT
            relevantFlags = Constants.ACC_PUBLIC | Constants.ACC_PRIVATE | Constants.ACC_PROTECTED
                    | Constants.ACC_STATIC | Constants.ACC_FINAL | Constants.ACC_SYNCHRONIZED | Constants.ACC_NATIVE
                    | Constants.ACC_ABSTRACT | Constants.ACC_STRICT;

            // 6. sorted non-private constructors
            for (Method ctor : relevantCtors) {
                dos.writeUTF(ctor.getName()); // <init>
                dos.writeInt(relevantFlags & ctor.getModifiers());
                dos.writeUTF(ctor.getSignature().replace('/', '.'));
            }

            // 7. sorted non-private methods
            for (Method m : relevantMethods) {
                dos.writeUTF(m.getName());
                dos.writeInt(relevantFlags & m.getModifiers());
                dos.writeUTF(m.getSignature().replace('/', '.'));
            }
            dos.flush();
            dos.close();
            byte[] bs = baos.toByteArray();
            MessageDigest md = MessageDigest.getInstance("SHA");
            byte[] result = md.digest(bs);

            long suid = 0L;
            int pos = result.length > 8 ? 7 : result.length - 1; // use the bytes we have
            while (pos >= 0) {
                suid = suid << 8 | ((long) result[pos--] & 0xff);
            }

            // if it was definetly 8 everytime...
            // long suid = ((long)(sha[0]&0xff) | (long)(sha[1]&0xff) << 8 |
            // (long)(sha[2]&0xff) << 16 | (long)(sha[3]&0xff) << 24 |
            // (long)(sha[4]&0xff) << 32 | (long)(sha[5]&0xff) << 40 |
            // (long)(sha[6]&0xff) << 48 | (long)(sha[7]&0xff) << 56);
            return suid;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Unable to calculate suid for " + getClassName() + ": " + e.toString());
        }
    }

    private static class FieldComparator implements Comparator<Field> {
        public int compare(Field f0, Field f1) {
            return f0.getName().compareTo(f1.getName());
        }
    }

    private static class ConstructorComparator implements Comparator<Method> {
        public int compare(Method m0, Method m1) {
            // can ignore the name...
            return (m0).getSignature().compareTo(m1.getSignature());
        }
    }

    private static class MethodComparator implements Comparator<Method> {
        public int compare(Method m0, Method m1) {
            int result = m0.getName().compareTo(m1.getName());
            if (result == 0) {
                result = m0.getSignature().compareTo(m1.getSignature());
            }
            return result;
        }
    }

    public boolean hasAttribute(String attributeName) {
        for (Attribute attr : attributesList) {
            if (attr.getName().equals(attributeName)) {
                return true;
            }
        }
        return false;
    }

    public Attribute getAttribute(String attributeName) {
        for (Attribute attr : attributesList) {
            if (attr.getName().equals(attributeName)) {
                return attr;
            }
        }
        return null;
    }
}