All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.classfilewriter.ClassFile Maven / Gradle / Ivy

There is a newer version: 3.0.0.Alpha1
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 *
 * Copyright 2012 Red Hat, Inc.
 *
 * Licensed 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.jboss.classfilewriter;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.jboss.classfilewriter.annotations.AnnotationBuilder;
import org.jboss.classfilewriter.annotations.AnnotationsAttribute;
import org.jboss.classfilewriter.attributes.Attribute;
import org.jboss.classfilewriter.constpool.ConstPool;
import org.jboss.classfilewriter.util.ByteArrayDataOutputStream;
import org.jboss.classfilewriter.util.DescriptorUtils;

/**
 *
 * @author Stuart Douglas
 *
 */
public class ClassFile implements WritableEntry {

    private final String name;

    private final String superclass;

    private final int accessFlags;

    private final int version;

    private final ConstPool constPool = new ConstPool();

    private final List interfaces = new ArrayList();

    private final Set fields = new HashSet();

    private final Set methods = new HashSet();

    private byte[] bytecode;

    private final List attributes = new ArrayList();

    private final AnnotationsAttribute runtimeVisibleAnnotationsAttribute;

    public ClassFile(String name, String superclass, String... interfaces) {
        this(name, AccessFlag.of(AccessFlag.SUPER, AccessFlag.PUBLIC), superclass, interfaces);
    }

    public ClassFile(String name, int accessFlags, String superclass, String... interfaces) {
        this.version = JavaVersions.JAVA_6;
        this.name = name.replace('/', '.'); // store the name in . form
        this.superclass = superclass;
        this.accessFlags = accessFlags;
        this.interfaces.addAll(Arrays.asList(interfaces));
        runtimeVisibleAnnotationsAttribute = new AnnotationsAttribute(AnnotationsAttribute.Type.RUNTIME_VISIBLE, constPool);
        this.attributes.add(runtimeVisibleAnnotationsAttribute);
    }

    public void addInterface(String iface) {
        this.interfaces.add(iface);
    }

    // fields
    /**
     * Adds a field with the given name and descriptor.
     *
     */
    public ClassField addField(int accessFlags, String name, String descriptor) {
        return addField(accessFlags, name, descriptor, null);
    }

    public ClassField addField(int accessFlags, String name, String descriptor, String signature) {
        ClassField field = new ClassField((short) accessFlags, name, descriptor, this, constPool);
        if (fields.contains(field)) {
            throw new DuplicateMemberException("Field  already exists. Field: " + name + " Descriptor:" + signature);
        }
        fields.add(field);
        field.setSignature(signature);
        return field;
    }

    public ClassField addField(int accessFlags, String name, Class type) {
        return addField(accessFlags, name, DescriptorUtils.makeDescriptor(type));
    }

    public ClassField addField(int accessFlags, String name, Class type, String genericSignature) {
        return addField(accessFlags, name, DescriptorUtils.makeDescriptor(type), genericSignature);
    }

    public ClassField addField(Field field) {
        ClassField classField = addField((short) field.getModifiers(), field.getName(), field.getType(), null);
        for (Annotation annotation : field.getDeclaredAnnotations()) {
            classField.getRuntimeVisibleAnnotationsAttribute().addAnnotation(
                    AnnotationBuilder.createAnnotation(constPool, annotation));
        }
        return classField;

    }

    // methods

    public ClassMethod addMethod(int accessFlags, String name, String returnType, String... parameters) {
        ClassMethod method = new ClassMethod(name, returnType, parameters, accessFlags, this);
        if (methods.contains(method)) {
            throw new DuplicateMemberException("Method  already exists. Method: " + name + " Parameters:"
                    + Arrays.toString(parameters) + " Return Type: " + returnType);
        }
        methods.add(method);
        return method;
    }

    /**
     * Adds a method with the same signiture as the given method, including exception types
     * 

* The new method will have the same modifier as the original method, except that the abstract and native flags will be * stripped. *

* TODO: annotations and signiture attribute */ public ClassMethod addMethod(Method method) { ClassMethod classMethod = addMethod(method.getModifiers() & (~AccessFlag.ABSTRACT) & (~AccessFlag.NATIVE), method .getName(), DescriptorUtils.makeDescriptor(method.getReturnType()), DescriptorUtils.parameterDescriptors(method .getParameterTypes())); for (Class e : method.getExceptionTypes()) { classMethod.addCheckedExceptions((Class) e); } for (Annotation annotation : method.getDeclaredAnnotations()) { classMethod.getRuntimeVisibleAnnotationsAttribute().addAnnotation( AnnotationBuilder.createAnnotation(constPool, annotation)); } int count = 0; for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) { for (Annotation annotation : parameterAnnotations) { classMethod.getRuntimeVisibleParameterAnnotationsAttribute().addAnnotation(count, AnnotationBuilder.createAnnotation(constPool, annotation)); } count++; } return classMethod; } /** * Adds a constructor with the same signiture as the given constrcutor, including exception types *

* TODO: annotations and signiture attribute */ public ClassMethod addConstructor(Constructor method) { ClassMethod classMethod = addMethod(method.getModifiers(), "", "V", DescriptorUtils.parameterDescriptors(method .getParameterTypes())); for (Class e : method.getExceptionTypes()) { classMethod.addCheckedExceptions((Class) e); } for (Annotation annotation : method.getDeclaredAnnotations()) { classMethod.getRuntimeVisibleAnnotationsAttribute().addAnnotation( AnnotationBuilder.createAnnotation(constPool, annotation)); } int count = 0; for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) { for (Annotation annotation : parameterAnnotations) { classMethod.getRuntimeVisibleParameterAnnotationsAttribute().addAnnotation(count, AnnotationBuilder.createAnnotation(constPool, annotation)); } count++; } return classMethod; } public void write(ByteArrayDataOutputStream stream) throws IOException { // first make sure everything we need is in the const pool short nameIndex = constPool.addClassEntry(name); short superClassIndex = constPool.addClassEntry(superclass); List interfaceIndexes = new ArrayList(interfaces.size()); for (String i : interfaces) { interfaceIndexes.add(constPool.addClassEntry(i)); } stream.writeInt(0xCAFEBABE);// magic stream.writeInt(version); constPool.write(stream); stream.writeShort(accessFlags); stream.writeShort(nameIndex); stream.writeShort(superClassIndex); stream.writeShort(interfaceIndexes.size()); // interface count for (short i : interfaceIndexes) { stream.writeShort(i); } stream.writeShort(fields.size()); // field count for (ClassField field : fields) { field.write(stream); } stream.writeShort(methods.size()); // method count for (ClassMethod method : methods) { method.write(stream); } stream.writeShort(attributes.size()); // attribute count for (Attribute attribute : attributes) { attribute.write(stream); } } private static java.lang.reflect.Method defineClass1, defineClass2; static { try { AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { Class cl = Class.forName("java.lang.ClassLoader", false, null); defineClass1 = cl.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class }); defineClass1.setAccessible(true); defineClass2 = cl.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class, ProtectionDomain.class }); defineClass2.setAccessible(true); return null; } }); } catch (PrivilegedActionException pae) { throw new RuntimeException("cannot initialize ClassFile", pae.getException()); } } public Class define(ClassLoader loader) { return define(loader, null); } /** * Definines the class using the given ClassLoader and ProtectionDomain */ public Class define(ClassLoader loader, ProtectionDomain domain) { try { SecurityManager sm = System.getSecurityManager(); if (sm != null) { final int index = name.lastIndexOf('.'); final String packageName; if(index == -1 ) { packageName = ""; } else { packageName = name.substring(0, index); } RuntimePermission permission = new RuntimePermission("defineClassInPackage." + packageName); sm.checkPermission(permission); } byte[] b = toBytecode(); java.lang.reflect.Method method; Object[] args; if (domain == null) { method = defineClass1; args = new Object[] { name.replace('/', '.'), b, new Integer(0), new Integer(b.length) }; } else { method = defineClass2; args = new Object[] { name.replace('/', '.'), b, new Integer(0), new Integer(b.length), domain }; } Class clazz = (Class) method.invoke(loader, args); return clazz; } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } public byte[] toBytecode() { // TODO: throw illegal state exception if the class file is modified after writing if (bytecode == null) { try { ByteArrayDataOutputStream out = new ByteArrayDataOutputStream(); write(out); bytecode = out.getBytes(); } catch (IOException e) { throw new RuntimeException(e); } } return bytecode; } public ConstPool getConstPool() { return constPool; } /** * returns the type descriptor for the class * * @return */ public String getDescriptor() { return 'L' + name + ';'; } public AnnotationsAttribute getRuntimeVisibleAnnotationsAttribute() { return runtimeVisibleAnnotationsAttribute; } /** * Returns the generated class name * * @return The generated class name */ public String getName() { return name; } /** * * @return The generated superclass name */ public String getSuperclass() { return superclass; } /** * * @return The interfaces implemented by this class */ public List getInterfaces() { return Collections.unmodifiableList(interfaces); } /** * * @return This class's fields */ public Set getFields() { return Collections.unmodifiableSet(fields); } /** * * @return This classes methods */ public Set getMethods() { return Collections.unmodifiableSet(methods); } }