Package net.sf.rej.java

Source Code of net.sf.rej.java.ClassFile

/* Copyright (C) 2004-2007 Sami Koivu
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
package net.sf.rej.java;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.rej.java.attribute.Attributes;
import net.sf.rej.java.constantpool.ConstantPool;
import net.sf.rej.java.constantpool.ConstantPoolInfo;
import net.sf.rej.util.ByteSerializer;
import net.sf.rej.util.ByteToolkit;
import net.sf.rej.util.Range;

/**
* <code>ClassFile</code> objects represent .class files. They're constructed
* either by parsing a byte array or a stream with <code>net.sf.rej.java.Disassembler
* </code>, using the Constructor or using the <code>net.sf.rej.java.ClassFactory</code>
* class.
*
* @author Sami Koivu
*/
public class ClassFile {
 
  /**
   * The default magic for comparison.
   */
    private static final byte[] magic = { (byte) 0xCa, (byte) 0xfe, (byte) 0xBa, (byte) 0xbe};

    /**
     * Class-file version.
     */
    private ClassVersion version = new ClassVersion();

    /**
     * The constant pool of this class file.
     */
    private ConstantPool pool;

    /**
     * The class-level access-flags of this class (public, protected, etc)
     */
    private int accessFlags;
   
    /**
     * An index to a ClassRef in the constant pool, defining this class.
     */
    private int thisClass;
   
    /**
     * An index to a ClassRef in the constant pool, defining this class's
     * parent.
     */   
    private int superClass;
   
    /**
     * A list of interfaces implemented by this class.
     */
    private List<Interface> interfaces = new ArrayList<Interface>();

    /**
     * A list of the fields of this class.
     */
    private List<Field> fields = new ArrayList<Field>();

    /**
     * A list of methods of this class, including any static code blocks and
     * constructors.
     */
    private List<Method> methods = new ArrayList<Method>();

    /**
     * An <code>Attributes</code> object containing all the class-level
     * attributes of this class (if any), such as SourceFile.
     */
    private Attributes attributes;

    /**
     * Initializes this <code>ClassFile</code> object. Note that
     * this object is not yet valid for deserialization until several
     * attributes of this class have been set.
     */
    public ClassFile() {
        // default constructor
    }

    /**
     * Returns the class-level access flags of this class. The int value is the
     * result of a bit-wise AND operation of all the modifiers of this class.
     * For a more thorough description and programmatically handling this value
     * see <code>net.sf.rej.java.AccessFlags</code>.
     * @return class-level access-flags.
     */
    public int getAccessFlags() {
        return this.accessFlags;
    }

    /**
     * Returns the major version of this class. For a more thorough description
     * of how the version maps into different Java compability settings,
     * see the <code>net.sf.rej.java.ClassVersion</code> class.
     * @return the major version of this class.
     */
    public int getMajorVersion() {
        return this.version.getMajorVersion();
    }

    /**
     * Returns the minor version of this class. For a more thorough description
     * of how the version maps into different Java compability settings,
     * see the <code>net.sf.rej.java.ClassVersion</code> class.
     * @return the minor version of this class.
     */
    public int getMinorVersion() {
        return this.version.getMinorVersion();
    }

    /**
     * Sets the major version of this class. For a more thorough description
     * of how the version maps into different Java compability settings,
     * see the <code>net.sf.rej.java.ClassVersion</code> class.
     * @param majorVersion the new major version for this class.
     */
    public void setMajorVersion(int majorVersion) {
      this.version.setMajorVersion(majorVersion);
    }

    /**
     * Sets the minor version of this class. For a more thorough description
     * of how the version maps into different Java compability settings,
     * see the <code>net.sf.rej.java.ClassVersion</code> class.
     * @param minorVersion the new minor version for this class.
     */
    public void setMinorVersion(int minorVersion) {
      this.version.setMinorVersion(minorVersion);
    }

    /**
     * Returns the major and minor version of this class in a <code>ClassVersion
     * </code> object. For a more thorough description
     * of how the version maps into different Java compability settings,
     * see the <code>net.sf.rej.java.ClassVersion</code> class.
     * @return the major and minor versions of this class.
     */
    public ClassVersion getVersion() {
      return this.version;
    }
   
    /**
     * Sets the major and minor versions of this class.
     * @param version
     */
    public void setVersion(ClassVersion version) {
      this.version = version;
    }

    /**
     * Returns a read-only list of the methods in this class. The methods are
     * returned as self-contained <code>net.sf.rej.java.Method</code> objects.
     * Modifying this list does not affect this class.
     * Modifying the methods contained in the list does affect this class.
     * @return a list of Method objects describing the methods of this class.
     */
    public List<Method> getMethods() {
        List<Method> list = new ArrayList<Method>();
        list.addAll(this.methods);
        return list;
    }

    /**
     * Adds a method to this class. For creating <code>net.sf.rej.java.Method</code>
     * objects, see <code>net.sf.rej.java.MethodFactory</code> class.
     * @param method a method (or static code block, or constructor)
     * to add to this class.
     */
    public void add(Method method) {
        this.methods.add(method);
    }

    /**
     * Adds a field to this class. For creating <code>net.sf.rej.java.Field</code>
     * objects, see <code>net.sf.rej.java.FieldFactory</code> class.
     * @param field a field to add to this class.
     */
    public void add(Field field) {
      this.fields.add(field);
    }
   
    /**
     * Returns a read-only list of the interfaces implemented by this class.
     * The interfaces are returned as self-contained <code>net.sf.rej.java.Interface
     * </code> objects. Modifying this list does not affect this class. Modifying
     * the <code>Interface</code> objects contained in the list does affect this
     * class.
     * @return a list of Interface objects describing the interfaces implemented
     * by this class.
     */
    public List<Interface> getInterfaces() {
        List<Interface> list = new ArrayList<Interface>();
        list.addAll(this.interfaces);
        return list;
    }

    /**
     * Returns the index to the constant pool ClassRef item which describes
     * the class represented by this <code>ClassFile</code> object.
     * @return an int value index to the constant pool.
     */
    public int getThisClass() {
        return this.thisClass;
    }

    /**
     * Returns an <code>Attributes</code> object, containing the class-level attributes of this
     * class - if it has any. The object returned is not a copy and any modifications
     * to it or the attributes contained in it will affect this class.
     * @return an Attributes object with the class-level attributes of this class.
     */
    public Attributes getAttributes() {
        return this.attributes;
    }

    /**
     * Returns the index to the constant pool ClassRef item which describes
     * the parent class of the class represented by this <code>ClassFile</code>
     * object.
     * @return an int value index to the constant pool.
     */
    public int getSuperClass() {
        return this.superClass;
    }

    /**
     * Returns a read-only list of the fields in this class. The fields are
     * returned as self-contained <code>net.sf.rej.java.Field</code> objects.
     * Modifying the list returned will not affect this class.
     * Modifying the fields contained in the list will affect this class.
     * @return a list of Field objects describing the fields of this class.
     */
    public List<Field> getFields() {
        List<Field> list = new ArrayList<Field>();
        list.addAll(this.fields);
        return list;
    }

    /**
     * Returns the ConstantPool object which models the constant pool associated
     * with this <code>ClassFile</code> object. The object returned is not a copy
     * and any modification to it will modify this class file as well.
     * @return the constant pool.
     */
    public ConstantPool getPool() {
        return this.pool;
    }

    /**
     * Returns the default magic for Java class files. In other words,
     * <code>0xcafebabe</code>
     * @return a byte array with the default class file magic.
     */
    public byte[] getMagic() {
        return ClassFile.magic;
    }

    /**
     * Sets the class-level access flags of this class. The int value is the
     * result of a bit-wise AND operation of all the modifiers of this class.
     * For a more thorough description and programmatically handling this value
     * see <code>net.sf.rej.java.AccessFlags</code>.
     * @param accessFlags the class-level access-flags.
     */
    public void setAccessFlags(int accessFlags) {
        this.accessFlags = accessFlags;
    }

    /**
     * Sets all the methods for this class. Any methods existing
     * in this class prior to calling this method are discarded (provided,
     * naturally, that they are not in the list and thus added again).
     * @param methods the list of methods (or static code block, or constructor)
     * for this class.
     */
    public void setMethods(List<Method> methods) {
        this.methods.clear();
        this.methods.addAll(methods);
    }

    /**
     * Sets all the interfaces for this class. Any interfaces existing
     * in this class prior to calling this method are discarded (provided,
     * naturally, that they are not in the list and thus added again).
     * @param interfaces the list of interfaces for this class.
     */
    public void setInterfaces(List<Interface> interfaces) {
        this.interfaces.clear();
        this.interfaces.addAll(interfaces);
    }

    /**
     * Sets the index to the constant pool ClassRef item which defines the
     * class modeled by this <code>ClassFile</code> object.
     * @param thisClass index to the constant pool.
     */
    public void setThisClass(int thisClass) {
        this.thisClass = thisClass;
    }

    /**
     * Sets the attributes object for this class, discarding the old one.
     * @param attributes an attributes object containing 0-n attributes.
     */
    public void setAttributes(Attributes attributes) {
        this.attributes = attributes;
    }

    /**
     * Sets the index to the constant pool ClassRef item which defines the
     * parent class of the class modeled by this <code>ClassFile</code> object.
     * @param superClass index to the constant pool.
     */
    public void setSuperClass(int superClass) {
        this.superClass = superClass;
    }

    /**
     * Sets all the fields for this class. Any fields existing
     * in this class prior to calling this method are discarded (provided,
     * naturally, that they are not in the list and thus added again).
     * @param fields the list of fields for this class.
     */
    public void setFields(List<Field> fields) {
        this.fields.clear();
        this.fields.addAll(fields);
    }

    /**
     * Sets the constant pool for this class. The old constant pool is discarded.
     * @param pool new constant pool.
     */
    public void setPool(ConstantPool pool) {
        this.pool = pool;
    }

    /**
     * Validates the magic given as a parameter. If the given magic is not the
     * standard 0xcafebabe a <code>RuntimeException</code> is thrown.
     * @param magic the magic to validate.
     * @throws RuntimeException the validation failed.
     */
    public void validateMagic(byte[] magic) throws RuntimeException {
        if (!Arrays.equals(magic, ClassFile.magic)) {
          throw new RuntimeException("Invalid magic: " + ByteToolkit.getHexString(magic));
        }
    }

    /**
     * Serializes this class. The produced byte array is in the class file format.
     * It may be written to a .class file and it may be loaded by a <code>ClassLoader</code>.
     * @return the class data.
     */
    public byte[] getData() {
        ByteSerializer ser = new ByteSerializer(true);
        // 0:
        ser.addBytes(ClassFile.magic);

        // 4:
        ser.addShort(getMinorVersion());
        ser.addShort(getMajorVersion());
       
        // 8:
        ser.addBytes(this.pool.getData());

        // 8+pooldata:
        ser.addShort(this.accessFlags);
        ser.addShort(this.thisClass);
        ser.addShort(this.superClass);

        // 14+pooldata:
        ser.addShort(this.interfaces.size());
        // 16+pooldata:
        for (Interface anInterface : this.interfaces) {
            ser.addShort(anInterface.getNameIndex());
        }

        // 16+pooldata+interfaces*2
        ser.addShort(this.fields.size());
        // 18+pooldata+interfaces*2
        for (Field field : this.fields) {
            ser.addBytes(field.getData());
        }

        // 18+pooldata+interfaces*2+fielddata
        ser.addShort(this.methods.size());
        // 20+pooldata+interfaces*2+fielddata
        for (Method method : this.methods) {
            ser.addBytes(method.getData());
        }

        // 20+pooldata+interfaces*2+fielddata+methoddata
        ser.addBytes(this.attributes.getData());
        return ser.getBytes();
    }
   
    public static enum OffsetTag {MAGIC, VERSION, MINOR_VERSION, MAJOR_VERSION,
      CONSTANT_POOL, ACCESS_FLAGS, THIS_CLASS, SUPER_CLASS,
      INTERFACE_COUNT, INTERFACE_DATA, FIELD_COUNT, FIELD_DATA,
      METHOD_COUNT, METHOD_DATA, ATTRIBUTES}
    
    /**
     * Returns a map of offsets of each significant element of the class file.
     * The offsets returned by this method are only valid until this
     * <code>ClassFile</code> object is modified. The keys in the map are
     * of type <code>OffsetTag</code>, <code>Interface</code>,
     * <code>Field</code> and <code>Method</code>.
     *
     * @return a map of element offsets in class file data.
     */
    public Map<Object, Range> getOffsetMap() {
      Map<Object, Range> map = new HashMap<Object, Range>();
      int offset = 0;
      map.put(OffsetTag.MAGIC, new Range(offset, 4));
      offset += 4;
      map.put(OffsetTag.VERSION, new Range(offset, 4));
      map.put(OffsetTag.MINOR_VERSION, new Range(offset, 2));
      offset += 2;
      map.put(OffsetTag.MAJOR_VERSION, new Range(offset, 2));
      offset += 2;
      int poolSize = this.pool.getData().length;
      map.put(OffsetTag.CONSTANT_POOL, new Range(offset, poolSize));
      // TODO: offsets for each pool item?
      offset += poolSize;
      map.put(OffsetTag.ACCESS_FLAGS, new Range(offset, 2));
      offset += 2;
      map.put(OffsetTag.THIS_CLASS, new Range(offset, 2));
      offset += 2;
      map.put(OffsetTag.SUPER_CLASS, new Range(offset, 2));
      offset += 2;
      map.put(OffsetTag.INTERFACE_COUNT, new Range(offset, 2));
      offset += 2;
      map.put(OffsetTag.INTERFACE_DATA, new Range(offset, this.interfaces.size()*2));
      {
        for (Interface anInterface : this.interfaces) {
          map.put(anInterface, new Range(offset, 2));
          offset += 2;
        }
      }
      map.put(OffsetTag.FIELD_COUNT, new Range(offset, 2));
      offset += 2;
      int fieldDataTotalSize = 0;
      Range fieldDataOffset = new Range(offset, 0);
      map.put(OffsetTag.FIELD_DATA, fieldDataOffset);
        for (Field field : this.fields) {
          int fieldSize = field.getData().length;
          map.put(field, new Range(offset, fieldSize));
          offset += fieldSize;
          fieldDataTotalSize += fieldSize;
        }
        fieldDataOffset.setSize(fieldDataTotalSize);
       
      map.put(OffsetTag.METHOD_COUNT, new Range(offset, 2));
      offset += 2;
      int methodDataTotalSize = 0;
      Range methodDataOffset = new Range(offset, 0);
      map.put(OffsetTag.METHOD_DATA, methodDataOffset);
        for (Method method : this.methods) {
          int methodSize = method.getData().length;
          map.put(method, new Range(offset, methodSize));
          offset += methodSize;
          methodDataTotalSize += methodSize;
        }
        methodDataOffset.setSize(methodDataTotalSize);
       
      map.put(OffsetTag.ATTRIBUTES, new Range(offset, this.attributes.getData().length));
      return map;
    }

    /**
     * Returns the short name of this class, in other words, the name without
     * the package definition part. For example, for the class <code>java.lang.String</code>
     * this method returns "String".
     *
     * @return the short name of the class.
     */
    public String getShortClassName() {
        String cn = getFullClassName();
        return cn.substring(cn.lastIndexOf(".") + 1);
    }

    /**
     * Returns the full class name, including the package definition part. For
     * example <code>"net.sf.rej.java.ClassFile"</code>.
     * @return the full name of this class.
     */
    public String getFullClassName() {
        ConstantPool cp = this.pool;
        ConstantPoolInfo cpi = cp.get(this.getThisClass());
        return cpi.getValue();
    }

    /**
     * Returns the full class name of the parent class of the class modeled by
     * this object. For example, for a <code>ClassFile</code> object modeling
     * the class <code>java.lang.String</code> this method will return
     * <code>"java.lang.Object"</code>.
     *
     * @return the full name of the super class of this class.
     */
    public String getSuperClassName() {
      if (this.getSuperClass() == 0) return null;
     
        ConstantPoolInfo cpi = this.pool.get(this.getSuperClass());
        return cpi.getValue();
    }

    /**
     * Returns the class-level access modifiers of the class modeled by this
     * object as a String. For example "public final".
     *
     * @return the class-level access modifers.
     */
    public String getAccessString() {
        StringBuffer sb = new StringBuffer();
        if (AccessFlags.isPublic(this.accessFlags)) sb.append("public ");
        if (AccessFlags.isPrivate(this.accessFlags)) sb.append("private ");
        if (AccessFlags.isProtected(this.accessFlags)) sb.append("protected ");
        if (AccessFlags.isAbstract(this.accessFlags)) sb.append("abstract ");
        if (AccessFlags.isStatic(this.accessFlags)) sb.append("static ");
        //if(AccessFlags.isSuper(this.accessFlags)) sb.append("super ");
        if (AccessFlags.isFinal(this.accessFlags)) sb.append("final ");
        if (AccessFlags.isNative(this.accessFlags)) sb.append("native ");

        return sb.toString().trim();
    }


    /**
     * Returns the name of the package defined for the class presented by this
     * object. In the case where the class is in the default package, an empty
     * <code>String</code> is returned.
     * @return package.
     */
    public String getPackageName() {
        String cn = getFullClassName();
        int lastDot = cn.lastIndexOf(".");
        if(lastDot == -1) {
            return ""; /* empty string for no package is a bit of a hack */
        } else {
            return cn.substring(0, lastDot);
        }
    } 

    /**
     * Removes the given method from this class. Since no equals method has
     * been defined for the <code>Method</code> class the removal only works
     * if the <code>Method</code> object given as parameter is one of the
     * <code>Method</code> objects stored in this class's list of methods.
     * @param method the method to remove.
     */
    public void remove(Method method) {
        this.methods.remove(method);
    }

    /**
     * Removes the given field from this class. Since no equals method has
     * been defined for the <code>Field</code> class the removal only works
     * if the <code>Field</code> object given as parameter is one of the
     * <code>Field</code> objects stored in this class's list of fields.
     * @param field the field to remove.
     */
    public void remove(Field field) {
        this.fields.remove(field);
    }

    /**
     * Returns a String describing the Java version compability of this class.
     * The value is determined from the major/minor version of the class file
     * using the following specification:
     * <blockquote>
   * The Java virtual machine implementation of Sun�s JDK release 1.0.2 supports
   * class file format versions 45.0 through 45.3 inclusive. Sun�s JDK releases
   * 1.1.X can support class file formats of versions in the range 45.0 through
   * 45.65535 inclusive. For implementations of version 1.k of the Java 2 platform
   * can support class file formats of versions in the range 45.0 through 44+k.0
   * inclusive.
     * </blockquote>
    
     * @return java version compability <code>String</code> such as "1.5".
     */
  public String getJavaVersionCompabilityString() {
    return this.version.getJavaVersionCompabilityString();
  }
 
  /**
   * Checks for equality. At this moment the equality is checked by comparing
   * the fully qualified names of the two <code>ClassFile</code> objects.
   *
   * @param obj the object to compare to this object.
   * @return true if the class files refer to the same FQN.
   */
  @Override
  public boolean equals(Object obj) {
    if (obj == null || !(obj instanceof ClassFile)) {
      return false;
    }
    ClassFile cf = (ClassFile)obj;
   
    /* TODO: Other than just checking the FQN, equals should be called
     * on each of the methods, fields and attributes as well.
     */
    return cf.getFullClassName().equals(getFullClassName());
  }

  @Override
  public int hashCode() {
    return getFullClassName().hashCode();
  }

}
TOP

Related Classes of net.sf.rej.java.ClassFile

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.