/* -*- Mode: C; c-file-style: "gnu" -*-
   method.c -- spew dealing with methods.
   Created: Chris Toshok <toshok@hungry.com>, 5-Dec-1997.
 */
/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1998 The Hungry Programmers

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 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
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#include "config.h"
#include "japharh.h"
#include "interp.h"
#include <assert.h>
#include <stdlib.h>

extern opcode opcode_table[];
extern JNIEnv *the_env;

static void
spew_exception_lines(FILE *fp,
		     MethodStruct *method,
		     int pc)
{
  int i;
  jboolean printed_number = JNI_FALSE;

  if (method->num_exception_blocks != 0)
    output_begin_exception_style(fp);

  for (i = 0; i < method->num_exception_blocks; i ++)
    {
      ExceptionBlock block = method->exceptions[ i ];

      if (pc == block.start_pc)
	{
	  fprintf (fp, "%2d:", i);
	  printed_number = JNI_TRUE;
	}
    }

  if (!printed_number)
    fprintf (fp, "   ");

  for (i = 0; i < method->num_exception_blocks; i ++)
    {
      ExceptionBlock block = method->exceptions[ i ];

      if (pc == block.start_pc)
	fprintf (fp, "+-");
      else if (pc == block.end_pc
	       && pc != block.handler_pc)
	fprintf (fp, "+-");
      else if (pc == block.handler_pc)
	fprintf (fp, "+>");
      else if ((pc > block.start_pc && pc < block.end_pc)
	       || (pc > block.end_pc && pc < block.handler_pc))
	fprintf (fp, "| ");
      else
	fprintf (fp, "  ");
    }

  if (method->num_exception_blocks != 0)
    output_end_exception_style(fp);
}

static void
spew_exception_info(FILE *fp,
		    MethodStruct *method)
{
  int i;

  output_begin_exception_style(fp);

  output_newline(fp);

  for (i = 0; i < method->num_exception_blocks; i ++)
    {
      ExceptionBlock block = method->exceptions[ i ];
      ClazzFile* handler = ExceptionBlock_getHandlerClazz(the_env,
						          method->clazz,
						          &block);

      fprintf (fp, "     Exception Block %2d (catch type %s): start_pc %5d  end_pc %5d  handler_pc  %5d\n",
	       i,
	       /* XXX What should really be done if handler is null ? [pere] */
	       handler ? getClassName(the_env, handler) : "[unknown]",
	       block.start_pc, block.end_pc, block.handler_pc);
    }

  output_end_exception_style(fp);
}

static void
spew_small_constant_info(FILE *fp,
			 MethodStruct *method,
			 ConstantPoolEntry *entry)
{
  if (entry->generic.tag & CONSTANT_Integer)
    {
      fprintf (fp, "Integer constant (%d)", entry->integer_info.bytes);
    }
  else if (entry->generic.tag & CONSTANT_Float)
    {
      float fvalue;

      fvalue = *(float*)((void*)&entry->float_info.bytes);

      fprintf (fp, "Float constant (%f)", fvalue);
    }
  else if (entry->generic.tag & CONSTANT_String)
    {
      ConstantPoolEntry *utf8_entry = &method->clazz->constants[ entry->string_info.string_index ];

      fprintf (fp, "String constant (%s)", ResolveStringAsCString(THREAD_getEnv(), method->clazz, utf8_entry));
    }
  else
    {
      fprintf (stderr, "Illegal constant target of ldc.\n");
    }
}

static void
spew_opcode_specific_stuff(FILE *fp, MethodStruct *method,
			   int opcode_pc)
{
  char *ename;
  assert(NULL != method);

  if (method->code[ opcode_pc ] == 178 /* getstatic */
      || method->code[ opcode_pc ] == 179 /* putstatic */
      || method->code[ opcode_pc ] == 180 /* getfield */
      || method->code[ opcode_pc ] == 181 /* putfield */)
    {
      ConstantPoolEntry *c = &method->clazz->constants[method->code[opcode_pc + 1] * 256
						      + method->code[opcode_pc + 2]];
      FieldStruct *field = ResolveFieldRef(THREAD_getEnv(),
					   method->clazz,
					   c);

      output_begin_comment(fp);
      fprintf (fp, "/* ");

      output_begin_link_to_class(fp, field->clazz);
      ename = output_escape_name(field->clazz->class_name);
      fprintf (fp, "%s",
	       ename);
      free(ename);
      output_end_link(fp);
      fprintf (fp, ".");
      output_begin_link_to_field(fp, field);
      ename = output_escape_name(field->name);
      fprintf (fp, "%s",
	       ename);
      free(ename);
      output_end_link(fp);
      fprintf (fp, "*/");

      output_end_comment(fp);
    }
  else if (method->code[ opcode_pc ] == 182 /* invokevirtual */
	   || method->code[ opcode_pc ] == 183 /* invokenonvirtual */)
    {
      ConstantPoolEntry *c = &method->clazz->constants[method->code[opcode_pc + 1] * 256
						      + method->code[opcode_pc + 2]];
      MethodStruct *smethod = ResolveNonVirtualMethodRef(the_env,
							 method->clazz,
							 c);
      Signature *sig;

      assert(NULL != smethod);

      sig = SIG_parseFromJavaSig(the_env, smethod->sig_str);

      output_begin_comment(fp);
      fprintf (fp, "/* ");
      output_begin_link_to_class(fp, smethod->clazz);
      ename = output_escape_name(smethod->clazz->class_name);
      fprintf (fp, "%s", 
	       ename);
      free(ename);
      output_end_link(fp);
      fprintf (fp, ".");
      output_begin_link_to_method(fp, smethod);
      ename = output_escape_name(smethod->name);
      fprintf (fp, "%s",
	       ename);
      free(ename);
      output_end_link(fp);
      fprintf (fp, "%s", SIG_formatToJavaSig(the_env, sig));
      fprintf (fp, " */");
      output_end_comment(fp);
      SIG_free(the_env, sig);
    }
  else if (method->code[ opcode_pc ] == 184 /* invokestatic */)
    {
      ConstantPoolEntry *c = &method->clazz->constants[method->code[opcode_pc + 1] * 256
						      + method->code[opcode_pc + 2]];
      MethodStruct *smethod = ResolveStaticMethodRef(the_env,
						     method->clazz,
						     c);
      Signature *sig = SIG_parseFromJavaSig(the_env, smethod->sig_str);

      output_begin_comment(fp);
      fprintf (fp, "/* ");
      output_begin_link_to_class(fp, smethod->clazz);
      ename = output_escape_name(smethod->clazz->class_name);
      fprintf (fp, "%s", 
	       ename);
      free(ename);
      output_end_link(fp);
      fprintf (fp, ".");
      output_begin_link_to_method(fp, smethod);
      ename = output_escape_name(smethod->name);
      fprintf (fp, "%s",
	       ename);
      free(ename);
      output_end_link(fp);
      fprintf (fp, "%s", SIG_formatToJavaSig(the_env, sig));
      fprintf (fp, " */");
      output_end_comment(fp);
      SIG_free(the_env, sig);
    }
  else if (method->code[ opcode_pc ] == 185 /* invokeinterface */)
    {
    }
  else if (method->code[ opcode_pc ] == 187 /* new */
	   || method->code[ opcode_pc ] == 192 /* checkcast */
	   || method->code[ opcode_pc ] == 193 /* instanceof */)
    {
      ConstantPoolEntry *c = &method->clazz->constants[method->code[opcode_pc + 1] * 256
						      + method->code[opcode_pc + 2]];
      ClazzFile *clazz = ResolveClass(the_env,
				      method->clazz,
				      c);

      output_begin_comment(fp);

      fprintf (fp, "/* ");
      output_begin_link_to_class(fp, clazz);
      ename = output_escape_name(clazz->class_name);
      fprintf (fp, "%s", ename);
      free(ename);
      output_end_link(fp);
      fprintf (fp, " */");

      output_end_comment(fp);
    }
  else if (method->code[ opcode_pc ] == 18 /* ldc */)
    {
      ConstantPoolEntry *c = &method->clazz->constants[method->code[ opcode_pc + 1 ]];

      output_begin_comment(fp);
      fprintf (fp, "/* ");

      spew_small_constant_info(fp, method, c);

      fprintf (fp, " */");

      output_end_comment(fp);
    }
  else if (method->code[ opcode_pc ] == 167 /* goto */
	   || (method->code[ opcode_pc ] >= 153 /* ifeq */
	       && method->code[ opcode_pc ] <= 166 /* ifacmpne */)
	   || (method->code[ opcode_pc ] == 198 /* ifnull */
	       || method->code[ opcode_pc ] == 199 /* ifnonnull */))
    {
      jshort offset = method->code[ opcode_pc + 1 ] << 8 | method->code[ opcode_pc + 2 ];

      output_begin_comment(fp);

      fprintf (fp, "/* PC = %d */", opcode_pc + offset);

      output_end_comment(fp);
    }
}

static int
spew_lookupswitch(FILE *fp, MethodStruct *method,
		  int opcode_pc)
{
  int padding = 3 - (opcode_pc % 4);
  int i, pair;
  int pc = opcode_pc;
  jint num_pairs, default_offset;
  opcode op;

  op = opcode_table[method->code[opcode_pc]];

  spew_exception_lines(fp, method, pc);

  fprintf (fp, "%5d:  %02X ", pc, method->code[pc]);
  
  pc++;
  
  for (i = 0; i < 5; i ++)
    if (i + 1> padding)
      fprintf (fp, "   ");
    else
      fprintf (fp, "%02X ", method->code[pc++]);
  
  fprintf (fp, "%-20s ", op.name);

  fprintf (fp, "\n"); /* don't make this 'output_newline()' */

  spew_exception_lines(fp, method, pc);

  fprintf (fp, "%5d:  ", pc);

  default_offset = 0;
  for (i = 0; i < 5; i ++)
    {
      if (i == 4)
	fprintf (fp, "      ");
      else
	{
	  fprintf (fp, "%02X ", method->code[pc]);
	  default_offset <<= 8;
	  default_offset |= method->code[pc];
	  pc++;
	}
    }

  fprintf (fp, "%-20s ", "");

  output_begin_comment(fp);
  fprintf (fp, "/* default -> PC = %d */", opcode_pc + default_offset);
  output_end_comment(fp);

  fprintf (fp, "\n"); /* don't make this 'output_newline()' */

  spew_exception_lines(fp, method, pc);
  fprintf (fp, "%5d:  ", pc);

  num_pairs = 0;
  for (i = 0; i < 5; i ++)
    {
      if (i == 4)
	fprintf (fp, "      ");
      else
	{
	  fprintf (fp, "%02X ", method->code[pc]);
	  num_pairs <<= 8;
	  num_pairs |= method->code[pc];
	  pc++;
	}
    }

  fprintf (fp, "%-20s ", "");

  output_begin_comment(fp);
  fprintf (fp, "/* num pairs = %d */", num_pairs);
  output_end_comment(fp);

  fprintf (fp, "\n"); /* don't make this 'output_newline()' */

  for (pair = 0; pair < num_pairs; pair ++)
    {
      juint key;
      jint offset;

      spew_exception_lines(fp, method, pc);
      fprintf (fp, "%5d:  ", pc);
	  
      key = 0;
      for (i = 0; i < 5; i ++)
	{
	  if (i == 4)
	    fprintf (fp, "      ");
	  else
	    {
	      fprintf (fp, "%02X ", method->code[pc]);
	      key <<= 8;
	      key |= method->code[pc];
	      pc++;
	    }
	}

      fprintf (fp, "%-20s ", "");
      output_begin_comment(fp);
      fprintf (fp, "/* [ key = %4u      */", key);
      output_end_comment(fp);

      fprintf (fp, "\n"); /* don't make this 'output_newline()' */

      spew_exception_lines(fp, method, pc);
      fprintf (fp, "%5d:  ", pc);
	  
      offset = 0;
      for (i = 0; i < 5; i ++)
	{
	  if (i == 4)
	    fprintf (fp, "      ");
	  else
	    {
	      fprintf (fp, "%02X ", method->code[pc]);
	      offset <<= 8;
	      offset |= method->code[pc];
	      pc++;
	    }
	}

      fprintf (fp, "%-20s ", "");
      output_begin_comment(fp);
      fprintf (fp, "/*   PC =  %4d    ] */", opcode_pc + offset);
      output_end_comment(fp);

      fprintf (fp, "\n"); /* don't make this 'output_newline()' */
    }

  return pc;
}

static int
spew_tableswitch(FILE *fp, MethodStruct *method,
		 int opcode_pc)
{
  int padding = 3 - (opcode_pc % 4);
  int i, offset;
  int pc = opcode_pc;
  jint default_offset, high, low;
  opcode op;

  op = opcode_table[method->code[opcode_pc]];

  spew_exception_lines(fp, method, pc);
  fprintf (fp, "%5d:  %02X ", pc, method->code[pc]);
  
  pc++;
  
  for (i = 0; i < 5; i ++)
    if (i + 1> padding)
      fprintf (fp, "   ");
    else
      fprintf (fp, "%02X ", method->code[pc++]);
  
  fprintf (fp, "%-20s ", op.name);

  fprintf (fp, "\n"); /* don't make this 'output_newline()' */

  spew_exception_lines(fp, method, pc);
  fprintf (fp, "%5d:  ", pc);

  default_offset = 0;
  for (i = 0; i < 5; i ++)
    {
      if (i == 4)
	fprintf (fp, "      ");
      else
	{
	  fprintf (fp, "%02X ", method->code[pc]);
	  default_offset <<= 8;
	  default_offset |= method->code[pc];
	  pc++;
	}
    }

  fprintf (fp, "%-20s ", "");

  output_begin_comment(fp);
  fprintf (fp, "/* default -> PC = %d */", opcode_pc + default_offset);
  output_end_comment(fp);

  fprintf (fp, "\n"); /* don't make this 'output_newline()' */

  spew_exception_lines(fp, method, pc);
  fprintf (fp, "%5d:  ", pc);

  low = 0;
  for (i = 0; i < 5; i ++)
    {
      if (i == 4)
	fprintf (fp, "      ");
      else
	{
	  fprintf (fp, "%02X ", method->code[pc]);
	  low <<= 8;
	  low |= method->code[pc];
	  pc++;
	}
    }

  fprintf (fp, "%-20s ", "");

  output_begin_comment(fp);
  fprintf (fp, "/* low = %d */", low);
  output_end_comment(fp);

  fprintf (fp, "\n"); /* don't make this 'output_newline()' */

  spew_exception_lines(fp, method, pc);
  fprintf (fp, "%5d:  ", pc);

  high = 0;
  for (i = 0; i < 5; i ++)
    {
      if (i == 4)
	fprintf (fp, "      ");
      else
	{
	  fprintf (fp, "%02X ", method->code[pc]);
	  high <<= 8;
	  high |= method->code[pc];
	  pc++;
	}
    }

  fprintf (fp, "%-20s ", "");

  output_begin_comment(fp);
  fprintf (fp, "/* high = %d */", high);
  output_end_comment(fp);

  fprintf (fp, "\n"); /* don't make this 'output_newline()' */

  for (offset = 0; offset < high-low + 1; offset ++)
    {
      jint pc_offset;

      spew_exception_lines(fp, method, pc);
      fprintf (fp, "%5d:  ", pc);
	  
      pc_offset = 0;
      for (i = 0; i < 5; i ++)
	{
	  if (i == 4)
	    fprintf (fp, "      ");
	  else
	    {
	      fprintf (fp, "%02X ", method->code[pc]);
	      pc_offset <<= 8;
	      pc_offset |= method->code[pc];
	      pc++;
	    }
	}

      fprintf (fp, "%-20s ", "");
      output_begin_comment(fp);
      fprintf (fp, "/* == %d -> PC = %d */", offset + low, opcode_pc + pc_offset);
      output_end_comment(fp);

      fprintf (fp, "\n"); /* don't make this 'output_newline()' */
    }

  return pc;
}

static int
spew_wide(FILE *fp, MethodStruct *method, int opcode_pc)
{
  int pc = opcode_pc;
  opcode op;
  int i;

  op = opcode_table[method->code[opcode_pc]];

  spew_exception_lines(fp, method, pc);
  fprintf (fp, "%5d:  %02X ", pc, method->code[pc]);
  pc++;

  for (i = 0; i < 5; i ++)
    fprintf (fp, "   ");

  fprintf (fp, "%-20s ", op.name);

  fprintf (fp, "\n"); /* don't make this 'output_newline()' */

  if (method->code[pc] == 132 /* iinc */)
    {
      /*
	for this opcode, the wide form is:

	wide
	iinc
	indexbyte1
	indexbyte2
	constbyte1
	constbyte2
      */
      op = opcode_table[method->code[pc]];

      spew_exception_lines(fp, method, pc);
      fprintf (fp, "%5d:  %02X ", pc, method->code[pc]);
      pc++;
      
      for (i = 0; i < 5; i ++)
	if (i + 1> 4)
	  fprintf (fp, "   ");
	else
	  fprintf (fp, "%02X ", method->code[pc++]);

      fprintf (fp, "%-20s ", op.name);

      fprintf (fp, "\n"); /* don't make this 'output_newline()' */
    }
  else
    {
      /*
	for anything else, the wide form is:

	wide
	<opcode>
	indexbyte1
	indexbyte2
      */
      op = opcode_table[method->code[pc]];
      
      spew_exception_lines(fp, method, pc);
      fprintf (fp, "%5d:  %02X ", pc, method->code[pc]);
      pc++;
      
      for (i = 0; i < 5; i ++)
	if (i + 1> 2)
	  fprintf (fp, "   ");
	else
	  fprintf (fp, "%02X ", method->code[pc++]);
      
      fprintf (fp, "%-20s ", op.name);

      fprintf (fp, "\n"); /* don't make this 'output_newline()' */
    }

  return pc;
}

static void
spew_opcodes(FILE *fp, MethodStruct *method)
{
  int pc = 0;
  int opcode_pc;

  while (pc < method->code_length)
    {
      opcode op;

      opcode_pc = pc;

      op = opcode_table[method->code[opcode_pc]];

      if (op.num_bytes_needed == -1)
	{
	  if (method->code[ opcode_pc ] == 171 /* lookupswitch */)
	    {
	      pc = spew_lookupswitch(fp, method, opcode_pc);
	    }
	  else if (method->code[ opcode_pc ] == 170 /* tableswitch */)
	    {
	      pc = spew_tableswitch(fp, method, opcode_pc);
	    }
	  else if (method->code[ opcode_pc ] == 196 /* wide */)
	    {
	      pc = spew_wide(fp, method, opcode_pc);
	    }
	  else
	    /* handle these differently. */
	    assert(0);
	}
      else
	{
	  int i;

	  spew_exception_lines(fp, method, pc);
	  fprintf (fp, "%5d:  %02X ", pc, method->code[pc]);

	  pc++;

	  for (i = 0; i < 5; i ++)
	    if (i + 1> op.num_bytes_needed)
	      fprintf (fp, "   ");
	    else
	      fprintf (fp, "%02X ", method->code[pc++]);
		  
	  fprintf (fp, "%-20s ", op.name);

	  spew_opcode_specific_stuff(fp, method, opcode_pc);

	  fprintf (fp, "\n"); /* don't make this 'output_newline()' */
	}
    }
}

void
spew_method(FILE *fp, MethodStruct *method)
{
  int i;
  char *ename;
  Signature *sig;

  assert(NULL != method);
  
  sig = SIG_parseFromJavaSig(the_env, method->sig_str);

  output_location_for_method(fp, method);

  output_begin_type(fp);
  output_begin_access_flags(fp);
  spew_access_flags_pretty(fp, method->access_flags);
  output_end_access_flags(fp);

  fprintf (fp, " %s ",
	   SIG_formatToJavaSource(THREAD_getEnv(), sig->method.return_type));
  output_end_type(fp);

  output_begin_method_name(fp);
  ename = output_escape_name(method->name);
  fprintf (fp, "%s ",
	   ename);
  free(ename);
  output_end_method_name(fp);

  fprintf (fp, "( ");

  for (i = 0; i < sig->method.num_params; i ++)
    {
      output_begin_type(fp);
      fprintf (fp, "%s", SIG_formatToJavaSource(THREAD_getEnv(),
						sig->method.params[i]));
      output_end_type(fp);
      if (i < sig->method.num_params - 1)
	fprintf (fp, ", ");
    }

  SIG_free(the_env, sig);

  fprintf (fp, ")");

  if (method->access_flags & ACC_NATIVE)
    {
      fprintf (fp, ";");
      output_newline(fp);
      output_newline(fp);
      return;
    }
  else
    {
      output_newline(fp);
    }

  fprintf (fp, "  {");
  output_newline(fp);

  output_begin_opcodes(fp);

  spew_opcodes(fp, method);

  spew_exception_info(fp, method);

  output_end_opcodes(fp);

  fprintf (fp, "  }");

  output_newline(fp);
  output_newline(fp);
}
