/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.bytecode;

import com.caucho.vfs.TempBuffer;
import com.caucho.vfs.TempStream;
import com.caucho.vfs.WriteStream;

import java.io.IOException;
import java.util.ArrayList;

/**
 * Represents a generic attribute
 */
public class CodeAttribute extends Attribute {
  private JavaClass _jClass;
  private int _maxStack;
  private int _maxLocals;
  private byte []_code;
  private ArrayList<ExceptionItem> _exceptions =
    new ArrayList<ExceptionItem>();
  
  private ArrayList<Attribute> _attributes = new ArrayList<Attribute>();

  public CodeAttribute()
  {
    super("Code");
  }

  CodeAttribute(String name)
  {
    super(name);
  }

  public void setJavaClass(JavaClass jClass)
  {
    _jClass = jClass;
  }

  public JavaClass getJavaClass()
  {
    return _jClass;
  }

  /**
   * Returns the max locals.
   */
  public int getMaxLocals()
  {
    return _maxLocals;
  }

  /**
   * Sets the max locals.
   */
  public void setMaxLocals(int max)
  {
    _maxLocals = max;
  }

  /**
   * Returns the max stack.
   */
  public int getMaxStack()
  {
    return _maxStack;
  }

  /**
   * Sets the max stack.
   */
  public void setMaxStack(int max)
  {
    _maxStack = max;
  }

  /**
   * Sets the code value.
   */
  public void setCode(byte []code)
  {
    _code = code;
  }

  /**
   * Gets the code value.
   */
  public byte []getCode()
  {
    return _code;
  }

  /**
   * Adds an attribute.
   */
  public void addAttribute(Attribute attr)
  {
    _attributes.add(attr);
  }

  /**
   * Returns the exceptions.
   */
  public ArrayList<Attribute> getAttributes()
  {
    return _attributes;
  }

  /**
   * Returns the exceptions.
   */
  public void setAttributes(ArrayList<Attribute> attributes)
  {
    if (_attributes != attributes) {
      _attributes.clear();
      _attributes.addAll(attributes);
    }
  }

  /**
   * Removes an attribute.
   */
  public Attribute removeAttribute(String name)
  {
    for (int i = _attributes.size() - 1; i >= 0; i--) {
      Attribute attr = _attributes.get(i);

      if (attr.getName().equals(name)) {
        _attributes.remove(i);
        return attr;
      }
    }

    return null;
  }

  /**
   * Returns the exceptions.
   */
  public ArrayList<ExceptionItem> getExceptions()
  {
    return _exceptions;
  }

  /**
   * Returns the exceptions.
   */
  public void addException(ClassConstant type, int start, int end, int handler)
  {
    _exceptions.add(new ExceptionItem(type.getIndex(), start, end, handler));
  }

  /**
   * Writes the field to the output.
   */
  public void read(ByteCodeParser in)
    throws IOException
  {
    int length = in.readInt();
    
    _maxStack = in.readShort();
    _maxLocals = in.readShort();

    int codeLength = in.readInt();

    _code = new byte[codeLength];
    in.read(_code, 0, codeLength);

    int exnCount = in.readShort();

    for (int i = 0; i < exnCount; i++) {
      ExceptionItem exn = new ExceptionItem();

      exn.setStart(in.readShort() & 0xffff);
      exn.setEnd(in.readShort() & 0xffff);
      exn.setHandler(in.readShort() & 0xffff);
      exn.setType(in.readShort() & 0xffff);

      _exceptions.add(exn);
    }

    int attrCount = in.readShort();

    for (int i = 0; i < attrCount; i++) {
      Attribute attr = in.parseAttribute();

      _attributes.add(attr);
    }
  }

  /**
   * Writes the field to the output.
   */
  public void write(ByteCodeWriter out)
    throws IOException
  {
    out.writeUTF8Const(getName());

    TempStream ts = new TempStream();
    ts.openWrite();
    WriteStream ws = new WriteStream(ts);
    ByteCodeWriter o2 = new ByteCodeWriter(ws, out.getJavaClass());
    
    o2.writeShort(_maxStack);
    o2.writeShort(_maxLocals);
    o2.writeInt(_code.length);
    o2.write(_code, 0, _code.length);

    o2.writeShort(_exceptions.size());
    for (int i = 0; i < _exceptions.size(); i++) {
      ExceptionItem exn = _exceptions.get(i);

      o2.writeShort(exn.getStart());
      o2.writeShort(exn.getEnd());
      o2.writeShort(exn.getHandler());
      o2.writeShort(exn.getType());
    }

    o2.writeShort(_attributes.size());
    for (int i = 0; i < _attributes.size(); i++) {
      Attribute attr = _attributes.get(i);

      attr.write(o2);
    }
    
    ws.close();
    
    out.writeInt(ts.getLength());
    TempBuffer ptr = ts.getHead();

    for (; ptr != null; ptr = ptr.getNext())
      out.write(ptr.getBuffer(), 0, ptr.getLength());

    ts.destroy();
  }

  /**
   * Clones the attribute
   */
  public Attribute export(JavaClass source, JavaClass target)
  {
    ConstantPool cp = target.getConstantPool();

    cp.addUTF8(getName());
    
    CodeAttribute attr = new CodeAttribute(getName());

    attr._maxStack = _maxStack;
    attr._maxLocals = _maxLocals;

    byte []code = new byte[_code.length];
    System.arraycopy(_code, 0, code, 0, _code.length);
    attr._code = code;

    for (int i = 0; i < _exceptions.size(); i++) {
      ExceptionItem exn = _exceptions.get(i);

      int type = exn.getType();
      
      if (type != 0)
        type = cp.addClass(source.getConstantPool().getClass(type).getName()).getIndex();

      ExceptionItem newExn = new ExceptionItem(type,
                                               exn.getStart(),
                                               exn.getEnd(),
                                               exn.getHandler());
    
      attr._exceptions.add(newExn);
    }

    for (int i = 0; i < _attributes.size(); i++) {
      Attribute codeAttr = _attributes.get(i);

      attr.addAttribute(codeAttr.export(source, target));
    }

    try {
      attr.exportCode(source, target);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }

    return attr;
  }

  /**
   * Exports code.
   */
  public void exportCode(JavaClass source, JavaClass target)
    throws Exception
  {
    ExportAnalyzer analyzer = new ExportAnalyzer(source, target);

    CodeEnhancer visitor = new CodeEnhancer(source, this);

    visitor.analyze(analyzer, false);

    visitor.update();
  }

  public String toString()
  {
    return "CodeAttribute[" + getName() + "]";
  }

  public static class ExceptionItem {
    private int _type;
    
    private int _start;
    private int _end;
    private int _handler;

    public ExceptionItem()
    {
    }

    public ExceptionItem(int type, int start, int end, int handler)
    {
      _type = type;
      _start = start;
      _end = end;
      _handler = handler;
    }

    /**
     * Sets the exception type.
     */
    public void setType(int type)
    {
      _type = type;
    }

    /**
     * Returns the exception type.
     */
    public int getType()
    {
      return _type;
    }

    /**
     * Sets the start PC
     */
    public void setStart(int pc)
    {
      _start = pc;
    }

    /**
     * Gets the start PC
     */
    public int getStart()
    {
      return _start;
    }

    /**
     * Sets the end PC
     */
    public void setEnd(int pc)
    {
      _end = pc;
    }

    /**
     * Gets the end PC
     */
    public int getEnd()
    {
      return _end;
    }

    /**
     * Sets the handler PC
     */
    public void setHandler(int pc)
    {
      _handler = pc;
    }

    /**
     * Gets the handler PC
     */
    public int getHandler()
    {
      return _handler;
    }
  }

  public static class ExportAnalyzer extends Analyzer {
    private JavaClass _source;
    private JavaClass _target;
    
    public ExportAnalyzer(JavaClass source, JavaClass target)
    {
      _source = source;
      _target = target;
    }

    public void analyze(CodeVisitor visitor)
      throws Exception
    {
      int op = visitor.getOpcode();
      int index;
      ConstantPool sourcePool = _source.getConstantPool();
      ConstantPool targetPool = _target.getConstantPool();
      ConstantPoolEntry entry;

      switch (op) {
      case CodeVisitor.ANEWARRAY:
      case CodeVisitor.CHECKCAST:
      case CodeVisitor.GETFIELD:
      case CodeVisitor.GETSTATIC:
      case CodeVisitor.INSTANCEOF:
      case CodeVisitor.INVOKEINTERFACE:
      case CodeVisitor.INVOKESPECIAL:
      case CodeVisitor.INVOKESTATIC:
      case CodeVisitor.INVOKEVIRTUAL:
      case CodeVisitor.LDC_W:
      case CodeVisitor.LDC2_W:
      case CodeVisitor.MULTIANEWARRAY:
      case CodeVisitor.NEW:
      case CodeVisitor.PUTFIELD:
      case CodeVisitor.PUTSTATIC:
        index = visitor.getShortArg(1);

        entry = sourcePool.getEntry(index);
        int targetIndex = entry.export(targetPool);

        visitor.setShortArg(1, targetIndex);
        break;

      case CodeVisitor.LDC:
        index = visitor.getByteArg(1);
        entry = sourcePool.getEntry(index);
        index = entry.export(targetPool);
        if (index <= 0xff)
          visitor.setByteArg(1, index);
        else {
          CodeEnhancer enhancer = (CodeEnhancer) visitor;
          enhancer.setByteArg(0, CodeVisitor.LDC_W);
          enhancer.addByte(enhancer.getOffset() + 2, 0);
          enhancer.setShortArg(1, index);
        }
        break;

      default:
        break;
      }
    }
  }
}
