/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */


package com.sun.jdo.api.persistence.enhancer.classfile;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.Enumeration;
import java.io.*;
import java.io.DataOutputStream;


/**
 * ClassFile models the structure of a class as represented within
 * a class file.
 */

final public class ClassFile implements VMConstants, Serializable {

  /* Class file constants */
  public static final int magic = 0xcafebabe;

  //@olsen: added more flexible version checking.
  public static final short[][] jdkMajorMinorVersions = new short[][]{
    new short[]{45, 3}, // jdk 1.1
    new short[]{46, 0}, // jdk 1.2
    new short[]{47, 0}, // jdk 1.3
    new short[]{48, 0}, // jdk 1.4
    new short[]{49, 0}  // jdk 1.5
  };
  public static final List jdkVersions =
    convertMajorMinorVersions(jdkMajorMinorVersions);
  public static final String supportedVersions = printSupportedVersions();

  private int majorVersion = 0;
  private int minorVersion = 0;

  /* The constant pool for the class file */
  private ConstantPool constantPool = new ConstantPool();

  /* access flag bit mask - see VMConstants */
  private int accessFlags = 0;

  /* The name of the class */
  private ConstClass thisClassName;

  /* The name of the super class */
  private ConstClass superClassName;

  /* A list of the interfaces which the class implements
   * The contents are ConstClass objects
   */
  private Vector classInterfaces = new Vector();

  /* A list of the fields which the class contains
   * The contents are ClassField objects
   */
  private Vector classFields = new Vector();

  /* A list of the methods which the class defines
   * The contents are ClassMethod objects
   */
  private Vector classMethods = new Vector();

  /* A list of the attributes associated with the class */
  private AttributeVector classAttributes = new AttributeVector();



  /* public accessors */



  /**
   * Return the constant pool for the class file
   */
  public ConstantPool pool() {
    return constantPool;
  }

  /**
   * Return the access flags for the class - see VMConstants
   */
  public int access() {
    return accessFlags;
  }

  /**
   * Is the class final?
   */
  final public boolean isFinal() {
    return (accessFlags & ACCFinal) != 0;
  }

  /**
   * Is the class an interface?
   */
  final public boolean isInterface() {
    return (accessFlags & ACCInterface) != 0;
  }

  /**
   * Is the class public?
   */
  final public boolean isPublic() {
    return (accessFlags & ACCPublic) != 0;
  }

  /**
   * Is the class abstract?
   */
  final public boolean isAbstract() {
    return (accessFlags & ACCAbstract) != 0;
  }


  /**
   * Set the access flags for the class - see VMConstants
   */
  public void setAccessFlags (int flags) {
    accessFlags = flags;
  }

  /**
   * Return the name of the class
   */
  public ConstClass className() {
    return thisClassName;
  }

  /**
   * Return the name of the super class
   */
  public ConstClass superName() {
    return superClassName;
  }

  /**
   * Return the name of the super class as a string
   */
  public String superNameString() {
    return (superClassName == null) ? null : superClassName.asString();
  }

  /**
   * Set the name of the super class
   */
  public void setSuperName(ConstClass superCl) {
    superClassName = superCl;
  }

  /**
   * Return the list of the interfaces which the class implements
   * The contents are ConstClass objects
   */
  public Vector interfaces() {
    return classInterfaces;
  }

  /**
   * Add an interface to the list of the interfaces which the class implements
   */
  public void addInterface (ConstClass iface) {
    classInterfaces.addElement(iface);
  }

  /**
   * Return the list of the fields which the class contains
   * The contents are ClassField objects
   */
  public Vector fields() {
    return classFields;
  }

  /**
   * Add a field to the list of the fields which the class contains
   */
  public void addField (ClassField field) {
    classFields.addElement(field);
  }

  /**
   * Add a field to the list of the fields which the class contains,
   * at the index'th position.
   */
  public void addField(ClassField field, int index) {
    classFields.insertElementAt(field, index);
  }

  /**
   * Return the list of the methods which the class defines
   * The contents are ClassMethod objects
   */
  public Vector methods() {
    return classMethods;
  }

  /**
   * Look for a method with the specified name and type signature
   */
  public ClassMethod findMethod(String methodName, String methodSig) {
    for (Enumeration e = methods().elements(); e.hasMoreElements();) {
      ClassMethod method = (ClassMethod) e.nextElement();
      if (method.name().asString().equals(methodName) &&
	  method.signature().asString().equals(methodSig))
	return method;
    }
    return null;
  }

  /**
   * Add a method to the list of the methods which the class defines
   */
  public void addMethod(ClassMethod method) {
    classMethods.addElement(method);
  }

  /**
   * Look for a field with the specified name
   */
  public ClassField findField(String fieldName) {
    for (Enumeration e = fields().elements(); e.hasMoreElements();) {
      ClassField field = (ClassField) e.nextElement();
      if (field.name().asString().equals(fieldName))
	return field;
    }
    return null;
  }

  /**
   * Return the list of the attributes associated with the class
   */
  public AttributeVector attributes() {
    return classAttributes;
  }

  /**
   * Construct a ClassFile from an input stream
   */
  public ClassFile(DataInputStream data) throws ClassFormatError {
    try {
      int thisMagic = data.readInt();
      if (thisMagic != magic)
        throw new ClassFormatError("Bad magic value for input");//NOI18N

      short thisMinorVersion = data.readShort();
      short thisMajorVersion = data.readShort();
      //@olsen: changed checking only target 1.1 and 1.2 to more
      //general check for a list of versions.

      if (isSupportedVersion(thisMajorVersion, thisMinorVersion)) {
         minorVersion = thisMinorVersion;
         majorVersion = thisMajorVersion;
      } else {
        throw new ClassFormatError("Bad version number: {" + //NOI18N
                  thisMajorVersion + "," + //NOI18N
                  thisMinorVersion +
                  "} expected one of: " + //NOI18N
                  supportedVersions);
      }
      readConstants(data);
      accessFlags = data.readUnsignedShort();
      thisClassName = (ConstClass)
      constantPool.constantAt(data.readUnsignedShort());
      superClassName = (ConstClass)
      constantPool.constantAt(data.readUnsignedShort());
      readInterfaces(data);
      readFields(data);
      readMethods(data);
      classAttributes = AttributeVector.readAttributes(data, constantPool);
    } catch (IOException e) {
      ClassFormatError cfe = new ClassFormatError("IOException during reading");//NOI18N
      cfe.initCause(e);
      throw cfe;
    }
    //@olsen: added println() for debugging
    //System.out.println("ClassFile(): new class = " + thisClassName.asString());
  }

  /**
   * Construct a bare bones class, ready for additions
   */
  public ClassFile(String cname, String supername) {
    thisClassName = constantPool.addClass(cname);
    superClassName = constantPool.addClass(supername);
    //@olsen: added println() for debugging
    //System.out.println("ClassFile(): new bare class file = " + thisClassName);
  }

  /**
   * Write the Class file to the data output stream
   */
  public
  void write (DataOutputStream buff) throws IOException {
    buff.writeInt(magic);
    buff.writeShort(minorVersion);
    buff.writeShort(majorVersion);
    constantPool.write(buff);
    buff.writeShort(accessFlags);
    buff.writeShort(thisClassName.getIndex());
    //@lars: superclass may be null (java.lang.Object); VMSpec 2nd ed., section 4.1
    buff.writeShort(superClassName == null ? 0 : superClassName.getIndex());
//    buff.writeShort(superClassName.getIndex());
    writeInterfaces(buff);
    writeFields(buff);
    writeMethods(buff);
    classAttributes.write(buff);
  }

  /**
   * Returns a byte array representation of this class.
   */
  public byte[] getBytes() throws java.io.IOException {
    /* Write the class bytes to a file, for debugging. */

    String writeClassToDirectory =
      System.getProperty("filter.writeClassToDirectory");
    if (writeClassToDirectory != null) {
      String filename = writeClassToDirectory + java.io.File.separator +
          thisClassName.asString() + ".class";//NOI18N
      System.err.println("Writing class to file " + filename);
      DataOutputStream stream = new DataOutputStream(
	  new java.io.FileOutputStream(filename));
      write(stream);
      stream.close();
    }

    /* Get the class bytes and return them. */

    ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    write(new DataOutputStream(byteStream));

    return byteStream.toByteArray();
  }



  /* package local methods */

  public void print (PrintStream out) {
    constantPool.print(out);
    out.println();

    out.println("majorVersion = " + Integer.toString(majorVersion));//NOI18N
    out.println("minorVersion = " + Integer.toString(minorVersion));//NOI18N
    out.println("accessFlags = " + Integer.toString(accessFlags));//NOI18N
    out.println("className = " + thisClassName.asString());//NOI18N
    out.println("superClassName = " + superClassName.asString());//NOI18N
    out.print("Interfaces =");//NOI18N
    for (int i=0; i<classInterfaces.size(); i++) {
        out.print(" " + ((ConstClass) classInterfaces.elementAt(i)).asString());//NOI18N
    }
    out.println();

    out.println("fields =");//NOI18N
    for (int i=0; i<classFields.size(); i++) {
      ((ClassField) classFields.elementAt(i)).print(out, 3);
    }

    out.println("methods =");//NOI18N
    for (int i=0; i<classMethods.size(); i++) {
      ((ClassMethod) classMethods.elementAt(i)).print(out, 3);
    }

    out.println("attributes =");//NOI18N
    classAttributes.print(out, 3);

  }

  public void summarize () {
    PrintStream os = System.out;

    constantPool.summarize();
    int codeSize = 0;
    for (int i=0; i<classMethods.size(); i++) {
      codeSize += ((ClassMethod) classMethods.elementAt(i)).codeSize();
    }
    System.out.println(classMethods.size() + " methods in " + codeSize + " bytes");//NOI18N
  }

  /*
   * class file reading helpers
   */
  private void readConstants (DataInputStream data) throws IOException {
    constantPool = new ConstantPool(data);
  }

  private void readInterfaces(DataInputStream data) throws IOException {
    int nInterfaces = data.readUnsignedShort();
    while (nInterfaces-- > 0) {
      int interfaceIndex = data.readUnsignedShort();
      ConstClass ci = null;
      if (interfaceIndex != 0)
	ci = (ConstClass) constantPool.constantAt(interfaceIndex);
      classInterfaces.addElement(ci);
    }
  }

  private void writeInterfaces(DataOutputStream data) throws IOException {
    data.writeShort(classInterfaces.size());
    for (int i=0; i<classInterfaces.size(); i++) {
      ConstClass ci = (ConstClass) classInterfaces.elementAt(i);
      int interfaceIndex = 0;
      if (ci != null)
	interfaceIndex = ci.getIndex();
      data.writeShort(interfaceIndex);
    }
  }

  private void readFields(DataInputStream data) throws IOException {
    int nFields = data.readUnsignedShort();
    while (nFields-- > 0) {
      classFields.addElement (ClassField.read(data, constantPool));
    }
  }

  private void writeFields (DataOutputStream data) throws IOException {
    data.writeShort(classFields.size());
    for (int i=0; i<classFields.size(); i++)
      ((ClassField)classFields.elementAt(i)).write(data);
  }

  private void readMethods (DataInputStream data) throws IOException {
    int nMethods = data.readUnsignedShort();
    while (nMethods-- > 0) {
      classMethods.addElement (ClassMethod.read(data, constantPool));
    }
  }

  private void writeMethods (DataOutputStream data) throws IOException {
    data.writeShort(classMethods.size());
    for (int i=0; i<classMethods.size(); i++)
      ((ClassMethod)classMethods.elementAt(i)).write(data);
  }


    //@olsen: Static methods added for major.minor compatibility checking
    private static List convertMajorMinorVersions(short[][] majorMinor) {
      int length = majorMinor.length;
      List result = new ArrayList(length);
      for (int i = 0; i < length; i++) {
        result.add(getVersionInt(majorMinor[i][0], majorMinor[i][1]));
      }
      return result;
    }

    private static boolean isSupportedVersion(short major, short minor) {
      Integer version = getVersionInt(major, minor);
      return jdkVersions.contains(version);
    }

    private static Integer getVersionInt(short major, short minor) {
        return  new Integer(major * 65536 + minor);
    }

    public static final String printSupportedVersions() {
      StringBuffer buf = new StringBuffer("{"); //NOI18N
      int length = jdkMajorMinorVersions.length;
      for (int i = 0; i < length; i++) {
        int major = jdkMajorMinorVersions[i][0];
        int minor = jdkMajorMinorVersions[i][1];
        buf.append("{"); //NOI18N
        buf.append(major);
        buf.append(","); //NOI18N
        buf.append(minor);
        buf.append("}"); //NOI18N
        }
        buf.append("}"); //NOI18N
        return buf.toString();
    }

}

abstract class ArraySorter {
  protected ArraySorter() {}

  /* return the size of the array being sorted */
  abstract int size();

  /* return -1 if o1 < o2, 0 if o1 == o2, 1 if o1 > o2 */
  abstract int compare(int o1Index, int o2Index);

  /* Swap the elements at index o1Index and o2Index */
  abstract void swap(int o1Index, int o2Index);

  void sortArray() {
    sortArray(0, size()-1);
  }

  private void sortArray(int start, int end) {
    if (end > start) {
      swap(start, (start+end)/2);
      int last = start;
      for (int i = start+1; i<=end; i++) {
	if (compare(i, start) < 0)
	  swap (++last, i);
      }
      swap(start, last);
      sortArray(start, last-1);
      sortArray(last+1, end);
    }
  }
}

class InterfaceArraySorter extends ArraySorter {
  private ConstClass theArray[];

  InterfaceArraySorter(ConstClass[] interfaces) {
    theArray = interfaces;
  }

  /* return the size of the array being sorted */
  int size() { return theArray.length; }

  /* return -1 if o1 < o2, 0 if o1 == o2, 1 if o1 > o2 */
  int compare(int o1Index, int o2Index) {
    return theArray[o1Index].asString().compareTo(
	theArray[o2Index].asString());
  }

  /* Swap the elements at index o1Index and o2Index */
  void swap(int o1Index, int o2Index) {
    ConstClass tmp = theArray[o1Index];
    theArray[o1Index] = theArray[o2Index];
    theArray[o2Index] = tmp;
  }
}

class FieldArraySorter extends ArraySorter {
  private ClassField theArray[];

  FieldArraySorter(ClassField[] fields) {
    theArray = fields;
  }

  /* return the size of the array being sorted */
  int size() { return theArray.length; }

  /* return -1 if o1 < o2, 0 if o1 == o2, 1 if o1 > o2 */
  int compare(int o1Index, int o2Index) {
    return theArray[o1Index].name().asString().compareTo(
	theArray[o2Index].name().asString());
  }

  /* Swap the elements at index o1Index and o2Index */
  void swap(int o1Index, int o2Index) {
    ClassField tmp = theArray[o1Index];
    theArray[o1Index] = theArray[o2Index];
    theArray[o2Index] = tmp;
  }
}

class MethodArraySorter extends ArraySorter {
  private ClassMethod theArray[];

  MethodArraySorter(ClassMethod[] methods) {
    theArray = methods;
  }

  /* return the size of the array being sorted */
  int size() { return theArray.length; }

  /* return -1 if o1 < o2, 0 if o1 == o2, 1 if o1 > o2 */
  int compare(int o1Index, int o2Index) {
    int cmp = theArray[o1Index].name().asString().compareTo(
	theArray[o2Index].name().asString());
    if (cmp == 0) {
      cmp = theArray[o1Index].signature().asString().compareTo(
	theArray[o2Index].signature().asString());
    }
    return cmp;
  }

  /* Swap the elements at index o1Index and o2Index */
  void swap(int o1Index, int o2Index) {
    ClassMethod tmp = theArray[o1Index];
    theArray[o1Index] = theArray[o2Index];
    theArray[o2Index] = tmp;
  }


}

