/*
 * Copyright (c) 1998-2008 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.log.Log;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Represents a java field.
 */
public class JavaMethod extends JMethod {
  static private final Logger log = Log.open(JavaMethod.class);

  private static final JClass []NULL_CLASS = new JClass[0];

  private JavaClassLoader _loader;

  private JavaClass _jClass;

  private int _accessFlags;
  private String _name;
  private String _descriptor;
  private JClass []_exceptions = NULL_CLASS;
  private int _line = -1;

  private ArrayList<Attribute> _attributes = new ArrayList<Attribute>();

  private JavaAnnotation []_annotations;

  private boolean _isWrite;

  public JavaMethod(JavaClassLoader loader)
  {
    _loader = loader;
  }

  public JavaMethod()
  {
  }

  /**
   * Sets the JavaClass.
   */
  public void setJavaClass(JavaClass jClass)
  {
    _jClass = jClass;
  }

  public void setWrite(boolean isWrite)
  {
    _isWrite = isWrite;
  }

  /**
   * Sets the name.
   */
  public void setName(String name)
  {
    _name = name;

    if (_jClass != null)
      _jClass.getConstantPool().addUTF8(name);
  }

  /**
   * Gets the name.
   */
  public String getName()
  {
    return _name;
  }

  /**
   * Returns the line number.
   */
  public int getLine()
  {
    if (_line >= 0)
      return _line;

    Attribute attr = getAttribute("LineNumberTable");

    if (attr == null) {
      _line = 0;
      return _line;
    }

    _line = 0;
    return _line;
  }

  /**
   * Returns the class loader.
   */
  public JavaClassLoader getClassLoader()
  {
    return _loader;
  }

  /**
   * Sets the access flags
   */
  public void setAccessFlags(int flags)
  {
    _accessFlags = flags;
  }

  /**
   * Gets the access flags
   */
  public int getAccessFlags()
  {
    return _accessFlags;
  }

  /**
   * Returns true for a final method
   */
  public boolean isFinal()
  {
    return Modifier.isFinal(getAccessFlags());
  }

  /**
   * Returns true for a public method
   */
  public boolean isPublic()
  {
    return Modifier.isPublic(getAccessFlags());
  }

  /**
   * Returns true for a protected method
   */
  public boolean isProtected()
  {
    return Modifier.isProtected(getAccessFlags());
  }

  /**
   * Returns true for a private method
   */
  public boolean isPrivate()
  {
    return Modifier.isPrivate(getAccessFlags());
  }

  /**
   * Returns true for an abstract method
   */
  public boolean isAbstract()
  {
    return Modifier.isAbstract(getAccessFlags());
  }

  /**
   * Returns true for a static method
   */
  public boolean isStatic()
  {
    return Modifier.isStatic(getAccessFlags());
  }

  /**
   * Sets the descriptor.
   */
  public void setDescriptor(String descriptor)
  {
    _descriptor = descriptor;

    if (_jClass != null)
      _jClass.getConstantPool().addUTF8(descriptor);
  }

  /**
   * Gets the descriptor.
   */
  public String getDescriptor()
  {
    return _descriptor;
  }

  /**
   * Returns the declaring class.
   */
  public JClass getDeclaringClass()
  {
    return _jClass;
  }

  /**
   * Returns the return types.
   */
  public JClass getReturnType()
  {
    String descriptor = getDescriptor();

    int i = descriptor.lastIndexOf(')');

    return getClassLoader().descriptorToClass(descriptor, i + 1);
  }

  /**
   * Returns the return type.
   */
  public JType getGenericReturnType()
  {
    SignatureAttribute sigAttr = (SignatureAttribute) getAttribute("Signature");

    if (sigAttr != null) {
      String sig = sigAttr.getSignature();

      int t = sig.lastIndexOf(')');

      return _loader.parseParameterizedType(sig.substring(t + 1));
    }

    return getReturnType();
  }

  /**
   * Returns the parameter types.
   */
  public JClass []getParameterTypes()
  {
    String descriptor = getDescriptor();

    ArrayList<JClass> typeList = new ArrayList<JClass>();

    int i = 0;
    while ((i = nextDescriptor(descriptor, i)) >= 0) {
      typeList.add(getClassLoader().descriptorToClass(descriptor, i));
    }

    JClass []types = new JClass[typeList.size()];

    typeList.toArray(types);

    return types;
  }

  private int nextDescriptor(String name, int i)
  {
    switch (name.charAt(i)) {
    case ')':
      return -1;

    case '(':
    case 'V':
    case 'Z':
    case 'C':
    case 'B':
    case 'S':
    case 'I':
    case 'J':
    case 'F':
    case 'D':
      i += 1;
      break;

    case '[':
      return nextDescriptor(name, i + 1);

    case 'L':
      {
  int tail = name.indexOf( ';', i);

  if (tail < 0)
    throw new IllegalStateException();

  i = tail + 1;
      }
      break;

    default:
      throw new UnsupportedOperationException(name.substring(i));
    }

    if (name.length() <= i)
      return -1;
    else if (name.charAt(i) == ')')
      return -1;
    else
      return i;
  }

  /**
   * Sets the exception types
   */
  public void setExceptionTypes(JClass []exceptions)
  {
    _exceptions = exceptions;
  }

  /**
   * Returns the exception types.
   */
  public JClass []getExceptionTypes()
  {
    return _exceptions;
  }

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

  public CodeWriterAttribute createCodeWriter()
  {
    CodeWriterAttribute code = new CodeWriterAttribute(_jClass);

    _attributes.add(code);

    return code;
  }

  /**
   * 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 attribute.
   */
  public ArrayList<Attribute> getAttributes()
  {
    return _attributes;
  }

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

      if (attr.getName().equals(name))
	return attr;
    }

    return null;
  }

  /**
   * Returns the declared annotations.
   */
  public JAnnotation []getDeclaredAnnotations()
  {
    if (_annotations == null) {
      Attribute attr = getAttribute("RuntimeVisibleAnnotations");

      if (attr instanceof OpaqueAttribute) {
	byte []buffer = ((OpaqueAttribute) attr).getValue();

	try {
	  ByteArrayInputStream is = new ByteArrayInputStream(buffer);

	  ConstantPool cp = _jClass.getConstantPool();

	  _annotations = JavaAnnotation.parseAnnotations(is, cp,
							 getClassLoader());
	} catch (IOException e) {
	  log.log(Level.FINER, e.toString(), e);
	}
      }

      if (_annotations == null) {
	_annotations = new JavaAnnotation[0];
      }
    }

    return _annotations;
  }

  /**
   * Returns the code attribute.
   */
  public CodeAttribute getCode()
  {
    for (int i = 0; i < _attributes.size(); i++) {
      Attribute attr = _attributes.get(i);

      if (attr instanceof CodeAttribute)
	return (CodeAttribute) attr;
    }

    return null;
  }

  /**
   * Create the code attribute.
   */
  public CodeAttribute createCode()
  {
    CodeAttribute code = new CodeAttribute();
    
    for (int i = 0; i < _attributes.size(); i++) {
      Attribute attr = _attributes.get(i);

      if (attr instanceof CodeAttribute)
	return (CodeAttribute) attr;
    }

    return null;
  }


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

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

      attr.write(out);
    }
  }

  /**
   * exports the method.
   */
  public JavaMethod export(JavaClass source, JavaClass target)
  {
    JavaMethod method = new JavaMethod(_loader);
    method.setName(_name);
    method.setDescriptor(_descriptor);
    method.setAccessFlags(_accessFlags);

    target.getConstantPool().addUTF8(_name);
    target.getConstantPool().addUTF8(_descriptor);

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

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

    return method;
  }

  /**
   * Concatenates the method.
   */
  public void concatenate(JavaMethod tail)
  {
    CodeAttribute codeAttr = getCode();
    CodeAttribute tailCodeAttr = tail.getCode();

    byte []code = codeAttr.getCode();
    byte []tailCode = tailCodeAttr.getCode();

    int codeLength = code.length;

    if ((code[codeLength - 1] & 0xff) == CodeVisitor.RETURN)
      codeLength = codeLength - 1;

    byte []newCode = new byte[codeLength + tailCode.length];
    System.arraycopy(code, 0, newCode, 0, codeLength);
    System.arraycopy(tailCode, 0, newCode, codeLength, tailCode.length);

    codeAttr.setCode(newCode);

    if (codeAttr.getMaxStack() < tailCodeAttr.getMaxStack())
      codeAttr.setMaxStack(tailCodeAttr.getMaxStack());

    if (codeAttr.getMaxLocals() < tailCodeAttr.getMaxLocals())
      codeAttr.setMaxLocals(tailCodeAttr.getMaxLocals());

    ArrayList<CodeAttribute.ExceptionItem> exns = tailCodeAttr.getExceptions();
    for (int i = 0; i < exns.size();  i++) {
      CodeAttribute.ExceptionItem exn = exns.get(i);

      CodeAttribute.ExceptionItem newExn = new CodeAttribute.ExceptionItem();

      newExn.setType(exn.getType());
      newExn.setStart(exn.getStart() + codeLength);
      newExn.setEnd(exn.getEnd() + codeLength);
      newExn.setHandler(exn.getHandler() + codeLength);
    }
  }

  public String toString()
  {
    return "JavaMethod[" + _name + "]";
  }
}
