#pike __REAL_VERSION__
inherit @module@;

object machine;

static string make_sig(object o, mapping(string:mixed) info, object|void p)
{
  if(p)
    return "("+Array.map(values(p), make_sig, info)*""+")"+make_sig(o, info);
  else {
    if(info->classisprimitive_method(o))
      return info->primitives[o];
    else if(info->classisarray_method(o))
      return replace((string)info->classgetname_method(o), ".", "/");
    else
      return "L"+replace((string)info->classgetname_method(o), ".", "/")+";";
  }
}

static void check_exception(mapping(string:mixed) info)
{
  object e = info->jvm->exception_occurred();
  if(e) {
    object sw = info->stringwriter_class->alloc();
    info->stringwriter_init(sw);
    object pw = info->printwriter_class->alloc();
    info->printwriter_init(pw, sw);
    info->throwable_printstacktrace(e, pw);
    info->printwriter_flush(pw);
    info->jvm->exception_clear();
    array bt = backtrace();
    throw(({(string)sw, bt[..sizeof(bt)-3]}));
  }
}

static mixed wrap_result(mixed x, mapping(string:mixed) info)
{
  check_exception(info);
  if(objectp(x)) {
    if(x->_values) {
      return jarray(x, info);
    } else {
      object cls = info->getclass_method(x);
      return jobject(info->classes[cls] || jclass(cls, info), x, info);
    }
  } else
    return x;
}

static array unwrap_args(array a)
{
  return Array.map(a, lambda(mixed x) {
			return (objectp(x)? x->_obj||x:x);
		      });
}

static class jmethod {

  static object obj;
  static mapping(string:mixed) info;
  static mapping(string:object) protos;

  static int is_applicable(string sig, array(mixed) args)
  {
    int sp=0, ap=0, na=sizeof(args);
    if(sig[sp++]!='(')
      return 0;
    while(ap<na) {
      switch(sig[sp++]) {
      case 'B':
      case 'C':
      case 'I':
      case 'J':
      case 'S':
      case 'Z':
	if(!intp(args[ap]))
	  return 0;
	break;
      case 'D':
      case 'F':
	if((!intp(args[ap])) && (!floatp(args[ap])))
	  return 0;
	break;
      case '[':
	if((!arrayp(args[ap])) && (!objectp(args[ap])))
	  return 0;
	while(sig[sp]=='[')
	  sp++;
	if(sig[sp++]=='L')
	  while(sig[sp++]!=';')
	    ;
	break;
      case 'L':
	if((!objectp(args[ap])) && (!stringp(args[ap])))
	  return 0;
	while(sig[sp++]!=';')
	  ;
	break;
      default:
	return 0;
	break;
      }
      ap++;
    }
    return sig[sp]==')';
  }

  static object select_proto(array(mixed) args)
  {
    array(string) applicable =
      Array.filter(indices(protos), is_applicable, args);

    if(sizeof(applicable)==1)
      return protos[applicable[0]];

    if(!sizeof(applicable))
      throw(({"No method signatures are applicable.  Resolution incomplete.\n",
	      backtrace()}));

    throw(({"Multiple method signatures apply.  Resolution incomplete.\n",
	    backtrace()}));
  }

  mixed `()(mixed ... args)
  {
    object mm;
    if(sizeof(protos)==1)
      mm = values(protos)[0];
    else
      mm = select_proto(args);
    if(obj)
      return wrap_result(mm(obj, @unwrap_args(args)), info);
    else
      return wrap_result(mm(@unwrap_args(args)), info);
  }

  void create(mapping(string:mixed) i, object o, mapping(string:object) p)
  {
    info = i;
    obj = o;
    protos = p;
  }

  void add_proto(string p, object o)
  {
    protos[p] = o;
  }

  object for_object(object o)
  {
    return jmethod(info, o, protos);
  }

  object for_proto(string p)
  {
    return protos[p];
  }

};

static class jobject {

  static object obj;
  static object cls;
  mapping(string:mixed) info;

  static mixed _wrap_result(mixed x)
  {
    return cls->_wrap_result(x);
  }

  mixed cast(mixed ... args)
  {
    return obj->cast(@unwrap_args(args));
  }

  mixed `[](string n)
  {
    object f = cls->_fields[n];
    if(f)
      return _wrap_result(f->get(obj));
    object m = cls->_methods[n];
    if(m)
      return m->for_object(obj);
    return ([])[0];
  }

  array(string) _indices()
  {
    return indices(cls->_fields)|indices(cls->_methods);
  }

  array(mixed) _values()
  {
    return rows(this_object(), _indices());
  }

  static object method(string n, string sig)
  {
    object m = cls->_methods[n];
    if(m) {
      object m2 = m->for_proto(sig);
      if(m2)
	return jmethod(info, obj, ([sig:m2]));
    }
    return ([])[0];
  }

  mixed `->(string n)
  {
    if(sizeof(n) && n[0]=='_')
      switch(n) {
      case "_method":
	return method;
      case "_obj":
	return obj;
      case "_monitor_enter":
	return obj->monitor_enter;
      default:
	return `[](n);
      } else
	return `[](n);
  }

  void create(object c, object o, mapping(string:mixed) i)
  {
    obj = o;
    cls = c;
    info = i;
  }

};

static class jarray {

  static object obj;
  mapping(string:mixed) info;

  mixed cast(mixed ... args)
  {
    return obj->cast(@unwrap_args(args));
  }

  mixed `[](int ... n)
  {
    return wrap_result(obj->`[](@n), info);
  }

  array(mixed) _indices()
  {
    return indices(obj);
  }

  array(mixed) _values()
  {
    return Array.map(values(obj), wrap_result, info);
  }

  int _sizeof()
  {
    return sizeof(obj);
  }

  mixed `->(string n)
  {
    if(n == "length")
      return sizeof(obj);
    else if(n == "_obj")
      return obj;
    else if(n == "_monitor_enter")
      return obj->monitor_enter;
    else
      return ([])[0];
  }

  void create(object o, mapping(string:mixed) i)
  {
    obj = o;
    info = i;
  }

};

static class jconstructor {

  static object cls, con;
  static mapping(string:mixed) info;
  
  object `()(mixed ... args)
  {
    object o = cls->_alloc();
    if(!o)
      return 0;
    con(o, @unwrap_args(args));
    return jobject(cls, o, info);
  }

  void create(object c, object o, mapping(string:mixed) i)
  {
    cls = c;
    con = o;
    info = i;
  }

}

static class jclass {

  static object obj;
  static mapping(string:object) fields = ([]);
  static mapping(string:object) static_fields = ([]);
  static mapping(string:object) methods = ([]);
  static mapping(string:object) static_methods = ([]);
  static object constructor;
  static mapping(string:mixed) info;

  static mixed _wrap_result(mixed x)
  {
    return wrap_result(x, info);
  }

  void create(object o, mapping(string:mixed) i)
  {
    obj = o;
    info = i;
    info->classes[o] = this_object();
    foreach(values(i->getfields_method(o)), object f)
      if((i->fieldgetmodifiers_method(f)) & i->modifier_static) {
	string name = (string)i->fieldgetname_method(f);
	static_fields[name] =
	  o->get_static_field(name, make_sig(i->fieldgettype_method(f), i));
      } else {
	string name = (string)i->fieldgetname_method(f);
	fields[name] =
	  o->get_field(name, make_sig(i->fieldgettype_method(f), i));
      }
    foreach(values(i->getmethods_method(o)), object m)
      if((i->methodgetmodifiers_method(m)) & i->modifier_static) {
	string name = (string)i->methodgetname_method(m);
	string sig = make_sig(i->methodgetreturntype_method(m), i,
			      i->methodgetparametertypes_method(m));
	object oo = o->get_static_method(name, sig);
	if(static_methods[name])
	  static_methods[name]->add_proto(sig, oo);
	else
	  static_methods[name] = jmethod(info, 0, ([sig:oo]));
      } else {
	string name = (string)i->methodgetname_method(m);
	string sig = make_sig(i->methodgetreturntype_method(m), i,
			      i->methodgetparametertypes_method(m));
	object oo = o->get_method(name, sig);
	if(methods[name])
	  methods[name]->add_proto(sig, oo);
	else
	  methods[name] = jmethod(info, 0, ([sig:oo]));
      }
    foreach(values(i->getconstructors_method(o)), object c) {
      string sig = make_sig(i->voidtype, i,
			    i->constructorgetparametertypes_method(c));
      object oo = o->get_method("<init>", sig);
      if(constructor)
	constructor->add_proto(sig, oo);
      else
	constructor = jmethod(info, 0, ([sig:oo]));
    }
  }

  mixed `[](string n)
  {
    object f = static_fields[n];
    if(f)
      return _wrap_result(f->get());
    return static_methods[n];
  }

  array(string) _indices()
  {
    return indices(static_fields)|indices(static_methods);
  }

  array(mixed) _values()
  {
    return rows(this_object(), _indices());
  }

  static object method(string n, string sig)
  {
    object m = static_methods[n];
    if(m) {
      object m2 = m->for_proto(sig);
      if(m2)
	return jmethod(info, 0, ([sig:m2]));
    }
    return 0;
  }

  static object make_constructor(string sig)
  {
    if(sizeof(sig) && sig[-1]!='V')
      sig += "V";
    if(constructor) {
      object c = constructor->for_proto(sig);
      if(c)
	return jconstructor(this_object(), c, info);
    }
    return 0;
  }

  mixed `->(string n)
  {
    if(sizeof(n) && n[0]=='_')
      switch(n) {
      case "_fields":
	return fields;
      case "_static_fields":
	return static_fields;
      case "_methods":
	return methods;
      case "_static_methods":
	return static_methods;
      case "_wrap_result":
	return _wrap_result;
      case "_method":
	return method;
      case "_constructor":
	return make_constructor;
      case "_alloc":
	return obj->alloc;
      case "_register_natives":
	return obj->register_natives;
      default:
	return `[](n);
      } else
	return `[](n);
  }

  object `()(mixed ... args)
  {
    object o = obj->alloc();
    if(!o)
      return 0;
    if(constructor)
      constructor->for_object(o)(@unwrap_args(args));
    return jobject(this_object(), o, info);
  }

};

static class package {

  static mapping(string:mixed) info;
  static mapping(string:object) subpackages;
  static string name;
  static object pkg;

  object `[](string n)
  {
    object p = subpackages[n];
    if(p)
      return p;
    if(zero_type(p)) {
      p = info->jvm->find_class(name==""?n:replace(name, ".", "/")+"/"+n);
      if(info->jvm->exception_check())
	check_exception(info);
      if(p)
	p = info->classes[p] || jclass(p, info);
      return p;
    }
    p = object_program(this_object())((name==""?n:name+"."+n), info);
    if(p)
      subpackages[n] = p;
    return p;
  }

  static array(object|string) find_class(string name, mapping(string:mixed) i)
  {
    object cls = i->jvm->find_class(name);
    object|string e = i->jvm->exception_occurred();
    if(e) {
      e = (string)e;
      i->jvm->exception_clear();
    }
    if(e || !cls) {
      array bt = backtrace();
      throw(({"Java class "+name+" not available!\n"+(stringp(e)? e+"\n":""),
	      bt[..sizeof(bt)-2]}));
    }
    return ({name, cls});
  }

  static object get_static_method(array(object|string) cls, string name,
				  string type, mapping(string:mixed) i)
  {
    object m = cls[1]->get_static_method(name, type);
    object|string e = i->jvm->exception_occurred();
    if(e) {
      e = (string)e;
      i->jvm->exception_clear();
    }
    if(e || !m) {
      array bt = backtrace();
      throw(({"Java method "+name+" "+type+" not available in class "+
	      cls[0]+"!\n"+(stringp(e)? e+"\n":""), bt[..sizeof(bt)-2]}));
    }
    return m;
  }

  static object get_method(array(object|string) cls, string name,
			   string type, mapping(string:mixed) i)
  {
    object m = cls[1]->get_method(name, type);
    object|string e = i->jvm->exception_occurred();
    if(e) {
      e = (string)e;
      i->jvm->exception_clear();
    }
    if(e || !m) {
      array bt = backtrace();
      throw(({"Java method "+name+" "+type+" not available in class "+
	      cls[0]+"!\n"+(stringp(e)? e+"\n":""), bt[..sizeof(bt)-2]}));
    }
    return m;
  }

  static object get_static_field(array(object|string) cls, string name,
				 string type, mapping(string:mixed) i)
  {
    object f = cls[1]->get_static_field(name, type);
    object|string e = i->jvm->exception_occurred();
    if(e) {
      e = (string)e;
      i->jvm->exception_clear();
    }
    if(e || !f) {
      array bt = backtrace();
      throw(({"Java field "+name+" "+type+" not available in class "+
	      cls[0]+"!\n"+(stringp(e)? e+"\n":""), bt[..sizeof(bt)-2]}));
    }
    return f;
  }

  void create(string n, mapping(string:mixed) i)
  {
    name = n;
    info = i;
    if(!i->getpackage_method) {
      array(object|string) cls = find_class("java/lang/Package", i);
      i->getpackage_method =
	get_static_method(cls, "getPackage",
			  "(Ljava/lang/String;)Ljava/lang/Package;", i);
      object gap =
	get_static_method(cls, "getPackages",
			  "()[Ljava/lang/Package;", i);
      i->packages =
	Array.map(values(gap()), lambda(object o, object nm) {
				   return (string)nm(o);
				 },
		  get_method(cls, "getName","()Ljava/lang/String;", i));

      cls = find_class("java/lang/Object", i);
      i->getclass_method = get_method(cls, "getClass",
				      "()Ljava/lang/Class;", i);

      cls = find_class("java/lang/Class", i);
      i->getfields_method = get_method(cls, "getFields",
				       "()[Ljava/lang/reflect/Field;", i);
      i->getmethods_method = get_method(cls, "getMethods",
					"()[Ljava/lang/reflect/Method;", i);
      i->getconstructors_method =
	get_method(cls, "getConstructors",
		   "()[Ljava/lang/reflect/Constructor;", i);

      i->classisprimitive_method = get_method(cls, "isPrimitive", "()Z", i);
      i->classisarray_method = get_method(cls, "isArray", "()Z", i);
      i->classgetname_method = get_method(cls, "getName",
					  "()Ljava/lang/String;", i);

      cls = find_class("java/lang/reflect/Field", i);
      i->fieldgetname_method = get_method(cls, "getName",
					  "()Ljava/lang/String;", i); 
      i->fieldgettype_method = get_method(cls, "getType",
					  "()Ljava/lang/Class;", i);
      i->fieldgetmodifiers_method = get_method(cls, "getModifiers", "()I", i);

      cls = find_class("java/lang/reflect/Method", i);
      i->methodgetname_method = get_method(cls, "getName",
					   "()Ljava/lang/String;", i); 
      i->methodgetreturntype_method = get_method(cls, "getReturnType",
						 "()Ljava/lang/Class;", i); 
      i->methodgetparametertypes_method =
	get_method(cls, "getParameterTypes", "()[Ljava/lang/Class;", i); 
      i->methodgetmodifiers_method = get_method(cls, "getModifiers", "()I", i);

      cls = find_class("java/lang/reflect/Constructor", i);
      i->constructorgetparametertypes_method =
	get_method(cls, "getParameterTypes", "()[Ljava/lang/Class;", i);

      cls = find_class("java/lang/reflect/Modifier", i);
      object fld = get_static_field(cls, "STATIC", "I", i);
      i->modifier_static = fld->get();

      i->primitives =
	mkmapping(Array.map(({"Byte", "Character", "Double", "Float",
			      "Integer", "Long", "Short", "Boolean", "Void"}),
			    lambda(string n) {
			      return
				get_static_field(find_class("java/lang/"+n, i),
						 "TYPE", "Ljava/lang/Class;",
						 i)->get();
			    }),
		  ({"B", "C", "D", "F", "I", "J", "S", "Z", "V"}));

      i->classes = ([]);

      i->voidtype = search(i->primitives, "V");

      i->stringwriter_class = (cls = find_class("java/io/StringWriter", i))[1];
      i->stringwriter_init = get_method(cls, "<init>", "()V", i);

      i->printwriter_class = (cls = find_class("java/io/PrintWriter", i))[1];
      i->printwriter_init = get_method(cls, "<init>", "(Ljava/io/Writer;)V",i);
      i->printwriter_flush = get_method(cls, "flush", "()V", i);

      cls = find_class("java/lang/Throwable", i);
      i->throwable_printstacktrace =
	get_method(cls, "printStackTrace", "(Ljava/io/PrintWriter;)V", i);

    }
    pkg = i->getpackage_method(name);
    array(string) subs = Array.map(i->packages, lambda(string p, string n){
						  string sn;
						  if(sscanf(p,n+"%[^.]",sn)==1)
						    return sn;
						  else
						    return 0;
						}, (name==""?n:n+"."))-({0});
    subpackages = mkmapping(subs, allocate(sizeof(subs)));
  }

};

object pkg;

static void create()
{
  program jvm = this_object()->jvm;
  if(jvm)
    machine = jvm();
  if(machine)
    pkg = package("", (["jvm":machine]));
}