/*
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

#include <jni.h>
#include <stdarg.h>
#ifdef _MSC_VER
#include <Winsock2.h>
#else
#include <arpa/inet.h>
#endif

#include "java/lang/Object.h"
#include "java/lang/Class.h"
#include "java/lang/String.h"
#include "java/lang/Throwable.h"
#include "java/lang/Boolean.h"
#include "java/lang/Byte.h"
#include "java/lang/Character.h"
#include "java/lang/Double.h"
#include "java/lang/Float.h"
#include "java/lang/Integer.h"
#include "java/lang/Long.h"
#include "java/lang/Short.h"
#include "java/util/Iterator.h"
#include "JArray.h"
#include "functions.h"
#include "macros.h"

using namespace java::lang;
using namespace java::util;

PyObject *PyExc_JavaError = PyExc_ValueError;
PyObject *PyExc_InvalidArgsError = PyExc_ValueError;

PyObject *_set_exception_types(PyObject *self, PyObject *args)
{
    if (!PyArg_ParseTuple(args, "OO",
                          &PyExc_JavaError, &PyExc_InvalidArgsError))
        return NULL;

    Py_RETURN_NONE;
}

PyObject *_set_function_self(PyObject *self, PyObject *args)
{
    PyObject *object, *module;

    if (!PyArg_ParseTuple(args, "OO", &object, &module))
        return NULL;

    if (!PyCFunction_Check(object))
    {
        PyErr_SetObject(PyExc_TypeError, object);
        return NULL;
    }

    PyCFunctionObject *cfn = (PyCFunctionObject *) object;

    Py_INCREF(module);
    Py_XDECREF(cfn->m_self);
    cfn->m_self = module;

    Py_RETURN_NONE;
}

PyObject *findClass(PyObject *self, PyObject *args)
{
    char *className;

    if (!PyArg_ParseTuple(args, "s", &className))
        return NULL;

    try {
        jclass cls = env->findClass(className);

        if (cls)
            return t_Class::wrap_Object(Class(cls));
    } catch (int e) {
        switch (e) {
          case _EXC_PYTHON:
            return NULL;
          case _EXC_JAVA:
            return PyErr_SetJavaError();
          default:
            throw;
        }
    }

    Py_RETURN_NONE;
}

static const char interface_bytes[] = {
    '\xca', '\xfe', '\xba', '\xbe',   // magic number: 0xcafebabe
    '\x00', '\x00', '\x00', '\x32',   // version 50.0
    '\x00', '\x07',                   // constant pool max index: 6
    '\x07', '\x00', '\x04',           // 1: class name at 4
    '\x07', '\x00', '\x05',           // 2: class name at 5
    '\x07', '\x00', '\x06',           // 3: class name at 6
    '\x01', '\x00', '\x00',           // 4: empty string
    '\x01', '\x00', '\x10',           // 5: 16-byte string: java/lang/Object
    'j', 'a', 'v', 'a', '/', 'l', 'a', 'n', 'g', '/',
    'O', 'b', 'j', 'e', 'c', 't',
    '\x01', '\x00', '\x00',           // 6: empty string
    '\x06', '\x01',                   // public abstract interface
    '\x00', '\x01',                   // this class at 1
    '\x00', '\x02',                   // superclass at 2
    '\x00', '\x01',                   // 1 interface
    '\x00', '\x03',                   // interface at 3
    '\x00', '\x00',                   // 0 fields
    '\x00', '\x00',                   // 0 methods
    '\x00', '\x00'                    // 0 attributes
};

/* make an empty interface that extends an interface */
PyObject *makeInterface(PyObject *self, PyObject *args)
{
    char *name, *extName;
    int name_len, extName_len;

    if (!PyArg_ParseTuple(args, "s#s#",
                          &name, &name_len, &extName, &extName_len))
        return NULL;

    JNIEnv *vm_env = env->get_vm_env();
    jclass _ucl = (jclass) vm_env->FindClass("java/net/URLClassLoader");
    jmethodID mid = vm_env->GetStaticMethodID(_ucl, "getSystemClassLoader",
                                              "()Ljava/lang/ClassLoader;");
    jobject classLoader = vm_env->CallStaticObjectMethod(_ucl, mid);
    const int bytes_len = sizeof(interface_bytes);
    const int len = bytes_len + name_len + extName_len;
    char *buf = (char *) malloc(len);

    if (buf == NULL)
        return PyErr_NoMemory();

    int name_pos = 22;
    int extName_pos = 44;
    jclass cls;

    memcpy(buf, interface_bytes, name_pos);
    memcpy(buf + name_pos + name_len, interface_bytes + name_pos,
           extName_pos - name_pos);
    memcpy(buf + extName_pos + name_len + extName_len,
           interface_bytes + extName_pos, bytes_len - extName_pos);
    extName_pos += name_len;

    *((unsigned short *) (buf + name_pos - 2)) = htons(name_len);
    memcpy(buf + name_pos, name, name_len);

    *((unsigned short *) (buf + extName_pos - 2)) = htons(extName_len);
    memcpy(buf + extName_pos, extName, extName_len);

    cls = vm_env->DefineClass(name, classLoader, (const jbyte *) buf, len);
    free(buf);

    if (cls)
        return t_Class::wrap_Object(Class(cls));

    return PyErr_SetJavaError();
}

static const char class_bytes[] = {
    '\xca', '\xfe', '\xba', '\xbe',          // magic number: 0xcafebabe
    '\x00', '\x00', '\x00', '\x32',          // version 50.0
    '\x00', '\x0c',                          // constant pool max index: 11
    '\x0a', '\x00', '\x03', '\x00', '\x08',  // 1: method for class 3 at 8
    '\x07', '\x00', '\x09',                  // 2: class name at 9
    '\x07', '\x00', '\x0a',                  // 3: class name at 10
    '\x07', '\x00', '\x0b',                  // 4: class name at 11
    '\x01', '\x00', '\x06',                  // 5: 6-byte string: <init>
    '<', 'i', 'n', 'i', 't', '>',
    '\x01', '\x00', '\x03', '(', ')', 'V',   // 6: 3-byte string: ()V
    '\x01', '\x00', '\x04',                  // 7: 4-byte string: Code
    'C', 'o', 'd', 'e',
    '\x0c', '\x00', '\x05', '\x00', '\x06',  // 8: name at 5, signature at 6
    '\x01', '\x00', '\x00',                  // 9: empty string
    '\x01', '\x00', '\x00',                  // 10: empty string
    '\x01', '\x00', '\x00',                  // 11: empty string
    '\x00', '\x21',                          // super public
    '\x00', '\x02',                          // this class at 2
    '\x00', '\x03',                          // superclass at 3
    '\x00', '\x01',                          // 1 interface
    '\x00', '\x04',                          // interface at 4
    '\x00', '\x00',                          // 0 fields
    '\x00', '\x01',                          // 1 method
    '\x00', '\x01', '\x00', '\x05',          // public, name at 5
    '\x00', '\x06', '\x00', '\x01',          // signature at 6, 1 attribute
    '\x00', '\x07',                          // attribute name at 7: Code
    '\x00', '\x00', '\x00', '\x11',          // 17 bytes past 6 attribute bytes
    '\x00', '\x01',                          // max stack: 1
    '\x00', '\x01',                          // max locals: 1
    '\x00', '\x00', '\x00', '\x05',          // code length: 5
    '\x2a', '\xb7', '\x00', '\x01', '\xb1',  // actual code bytes
    '\x00', '\x00',                          // 0 method exceptions
    '\x00', '\x00',                          // 0 method attributes
    '\x00', '\x00',                          // 0 attributes
};

/* make an empty class that extends a class and implements an interface */
PyObject *makeClass(PyObject *self, PyObject *args)
{
    char *name, *extName, *implName;
    int name_len, extName_len, implName_len;

    if (!PyArg_ParseTuple(args, "s#s#s#",
                          &name, &name_len, &extName, &extName_len,
                          &implName, &implName_len))
        return NULL;

    JNIEnv *vm_env = env->get_vm_env();
    jclass _ucl = (jclass) vm_env->FindClass("java/net/URLClassLoader");
    jmethodID mid = vm_env->GetStaticMethodID(_ucl, "getSystemClassLoader",
                                              "()Ljava/lang/ClassLoader;");
    jobject classLoader = vm_env->CallStaticObjectMethod(_ucl, mid);
    const int bytes_len = sizeof(class_bytes);
    const int len = bytes_len + name_len + extName_len + implName_len;
    char *buf = (char *) malloc(len);

    if (buf == NULL)
        return PyErr_NoMemory();

    int name_pos = 54;
    int extName_pos = 57;
    int implName_pos = 60;
    jclass cls;

    memcpy(buf, class_bytes, name_pos);
    memcpy(buf + name_pos + name_len, class_bytes + name_pos,
           extName_pos - name_pos);
    memcpy(buf + extName_pos + name_len + extName_len,
           class_bytes + extName_pos, bytes_len - extName_pos);
    memcpy(buf + implName_pos + name_len + extName_len + implName_len,
           class_bytes + implName_pos, bytes_len - implName_pos);

    extName_pos += name_len;
    implName_pos += name_len + extName_len;

    *((unsigned short *) (buf + name_pos - 2)) = htons(name_len);
    memcpy(buf + name_pos, name, name_len);

    *((unsigned short *) (buf + extName_pos - 2)) = htons(extName_len);
    memcpy(buf + extName_pos, extName, extName_len);

    *((unsigned short *) (buf + implName_pos - 2)) = htons(implName_len);
    memcpy(buf + implName_pos, implName, implName_len);

    cls = vm_env->DefineClass(name, classLoader, (const jbyte *) buf, len);
    free(buf);

    if (cls)
        return t_Class::wrap_Object(Class(cls));

    return PyErr_SetJavaError();
}

static boxfn get_boxfn(PyTypeObject *type)
{
    static PyObject *boxfn_ = PyUnicode_FromString("boxfn_");
    PyObject *cobj = PyObject_GetAttr((PyObject *) type, boxfn_);
    boxfn fn;

    if (cobj == NULL)
        return NULL;

    fn = (boxfn) PyCapsule_GetPointer(cobj, "boxfn");
    Py_DECREF(cobj);

    return fn;
}

static int is_instance_of(PyObject *arg, PyTypeObject *type)
{
    static PyObject *class_ = PyUnicode_FromString("class_");
    PyObject *clsObj = PyObject_GetAttr((PyObject *) type, class_);
    int result;

    if (clsObj == NULL)
        return -1;

    result = env->get_vm_env()->
        IsInstanceOf(((t_Object *) arg)->object.this$,
                     (jclass) ((t_Object *) clsObj)->object.this$);
    Py_DECREF(clsObj);

    return result;
}


#if defined(_MSC_VER) || defined(__SUNPRO_CC)
int __parseArgs(PyObject *args, char *types, ...)
{
    int count = Py_SIZE((PyTupleObject *) args);
    va_list list, check;

    va_start(list, types);
    va_start(check, types);

    return _parseArgs(((PyTupleObject *) args)->ob_item, count, types,
		      list, check);
}

int __parseArg(PyObject *arg, char *types, ...)
{
    va_list list, check;

    va_start(list, types);
    va_start(check, types);

    return _parseArgs(&arg, 1, types, list, check);
}

int _parseArgs(PyObject **args, unsigned int count, char *types,
	       va_list list, va_list check)
{
    unsigned int typeCount = strlen(types);

    if (count > typeCount)
        return -1;
#else

int _parseArgs(PyObject **args, unsigned int count, char *types, ...)
{
    unsigned int typeCount = strlen(types);
    va_list list, check;

    va_start(list, types);
    va_start(check, types);
#endif

    if (!env->vm)
    {
        PyErr_SetString(PyExc_RuntimeError, "initVM() must be called first");
        return -1;
    }

    JNIEnv *vm_env = env->get_vm_env();

    if (!vm_env)
    {
        PyErr_SetString(PyExc_RuntimeError, "attachCurrentThread() must be called first");
        return -1;
    }

    bool last = false;      /* true if last expected parameter */
    bool varargs = false;   /* true if in varargs mode */
    bool empty = false;     /* true if in varargs mode and no params passed */
    int array = 0;          /* > 0 if expecting an array, its nesting level */
    unsigned int pos = 0;

    for (unsigned int a = 0; a < count; a++, pos++) {
        PyObject *arg = args[a];
        char tc = types[pos];

        last = last || types[pos + 1] == '\0';

        if (array > 1 && tc != '[')
          tc = 'o';

        switch (tc) {
          case '[':
          {
              if (++array > 1 &&
                  !PyObject_TypeCheck(arg, PY_TYPE(JArrayObject)))
                return -1;

              a -= 1;
              break;
          }

          case 'j':           /* Java object, with class$    */
          case 'k':           /* Java object, with initializeClass */
          case 'K':           /* Java object, with initializeClass and params */
          {
              jclass cls = NULL;

              switch (tc) {
                case 'j':
                  cls = (jclass) va_arg(list, Class *)->this$;
                  break;
                case 'k':
                case 'K':
                  try {
                      getclassfn initializeClass = va_arg(list, getclassfn);
                      cls = env->getClass(initializeClass);
                  } catch (int e) {
                      switch (e) {
                        case _EXC_PYTHON:
                          return -1;
                        case _EXC_JAVA:
                          PyErr_SetJavaError();
                          return -1;
                        default:
                          throw;
                      }
                  }
                  break;
              }

              if (arg == Py_None)
                  break;

              /* ensure that class Class is initialized (which may not be the
               * case because of earlier recursion avoidance (JObject(cls)).
               */
              if (!Class::class$)
                  env->getClass(Class::initializeClass);

              if (array)
              {
                  if (PyObject_TypeCheck(arg, PY_TYPE(JArrayObject)))
                      break;

                  if (PySequence_Check(arg) &&
                      !PyBytes_Check(arg) && !PyUnicode_Check(arg))
                  {
                      if (PySequence_Length(arg) > 0)
                      {
                          PyObject *obj = PySequence_GetItem(arg, 0);
                          int ok = 0;

                          if (obj == Py_None)
                              ok = 1;
                          else if (PyObject_TypeCheck(obj, PY_TYPE(Object)) &&
                                   vm_env->IsInstanceOf(((t_Object *) obj)->object.this$, cls))
                              ok = 1;
                          else if (PyObject_TypeCheck(obj, PY_TYPE(FinalizerProxy)))
                          {
                              PyObject *o = ((t_fp *) obj)->object;

                              if (PyObject_TypeCheck(o, PY_TYPE(Object)) &&
                                  vm_env->IsInstanceOf(((t_Object *) o)->object.this$, cls))
                                  ok = 1;
                          }

                          Py_DECREF(obj);
                          if (ok)
                              break;
                      }
                      else
                          break;
                  }

                  if (last)
                  {
                      int ok = 0;

                      if (arg == Py_None)
                          ok = 1;
                      else if (PyObject_TypeCheck(arg, PY_TYPE(Object)) &&
                               vm_env->IsInstanceOf(((t_Object *) arg)->object.this$, cls))
                          ok = 1;
                      else if (PyObject_TypeCheck(arg, PY_TYPE(FinalizerProxy)))
                      {
                          PyObject *o = ((t_fp *) arg)->object;

                          if (PyObject_TypeCheck(o, PY_TYPE(Object)) &&
                              vm_env->IsInstanceOf(((t_Object *) o)->object.this$, cls))
                              ok = 1;
                      }
                      if (ok)
                      {
                          varargs = true;
                          break;
                      }
                  }
              }
              else if (PyObject_TypeCheck(arg, PY_TYPE(Object)) &&
                       vm_env->IsInstanceOf(((t_Object *) arg)->object.this$, cls))
                  break;
              else if (PyObject_TypeCheck(arg, PY_TYPE(FinalizerProxy)))
              {
                  arg = ((t_fp *) arg)->object;
                  if (PyObject_TypeCheck(arg, PY_TYPE(Object)) &&
                      vm_env->IsInstanceOf(((t_Object *) arg)->object.this$, cls))
                      break;
              }

              return -1;
          }

          case 'Z':           /* boolean, strict */
          {
              if (array)
              {
                  if (arg == Py_None)
                      break;

                  if (PyObject_TypeCheck(arg, PY_TYPE(JArrayBool)))
                      break;

                  if (PySequence_Check(arg))
                  {
                      if (PySequence_Length(arg) > 0)
                      {
                          PyObject *obj = PySequence_GetItem(arg, 0);
                          int ok = obj == Py_True || obj == Py_False;

                          Py_DECREF(obj);
                          if (ok)
                              break;
                      }
                      else
                          break;
                  }

                  if (last && (arg == Py_True || arg == Py_False))
                  {
                      varargs = true;
                      break;
                  }
              }
              else if (arg == Py_True || arg == Py_False)
                  break;

              return -1;
          }

          case 'B':           /* byte */
          {
              if (array)
              {
                  if (arg == Py_None)
                      break;
                  if (PyObject_TypeCheck(arg, PY_TYPE(JArrayByte)))
                      break;

                  if (last)
                  {
                      if ((PyBytes_Check(arg) && (PyBytes_Size(arg) == 1)) ||
                          PyLong_CheckExact(arg))
                      {
                          varargs = true;
                          break;
                      }
                  }
              }
              else if (PyBytes_Check(arg) && (PyBytes_Size(arg) == 1))
                  break;
              else if (PyLong_CheckExact(arg))
                  break;

              return -1;
          }

          case 'C':           /* char */
          {
              if (array)
              {
                  if (arg == Py_None)
                      break;
                  if (PyObject_TypeCheck(arg, PY_TYPE(JArrayChar)))
                      break;

                  if (last)
                  {
                      if ((PyBytes_Check(arg) && PyBytes_Size(arg) == 1) ||
                          (PyUnicode_Check(arg) &&
                           PyUnicode_GetLength(arg) == 1 &&
                           PyUnicode_READ_CHAR(arg, 0) < 0x10000))
                      {
                          varargs = true;
                          break;
                      }
                  }
              }
              else if (PyBytes_Check(arg) && PyBytes_Size(arg) == 1)
                  break;
              else if (PyUnicode_Check(arg) && PyUnicode_GetLength(arg) == 1 &&
                       PyUnicode_READ_CHAR(arg, 0) < 0x10000)
                  break;

              return -1;
          }

          case 'I':           /* int */
          {
              if (array)
              {
                  if (arg == Py_None)
                      break;

                  if (PyObject_TypeCheck(arg, PY_TYPE(JArrayInt)))
                      break;

                  if (PySequence_Check(arg))
                  {
                      if (PySequence_Length(arg) > 0)
                      {
                          PyObject *obj = PySequence_GetItem(arg, 0);
                          int ok = PyLong_CheckExact(obj);

                          Py_DECREF(obj);
                          if (ok)
                              break;
                      }
                      else
                          break;
                  }

                  if (last && PyLong_CheckExact(arg))
                  {
                      varargs = true;
                      break;
                  }
              }
              else if (PyLong_CheckExact(arg))
                  break;

              return -1;
          }

          case 'S':           /* short */
          {
              if (array)
              {
                  if (arg == Py_None)
                      break;

                  if (PyObject_TypeCheck(arg, PY_TYPE(JArrayShort)))
                      break;

                  if (PySequence_Check(arg))
                  {
                      if (PySequence_Length(arg) > 0)
                      {
                          PyObject *obj = PySequence_GetItem(arg, 0);
                          int ok = PyLong_CheckExact(obj);

                          Py_DECREF(obj);
                          if (ok)
                              break;
                      }
                      else
                          break;
                  }

                  if (last && PyLong_CheckExact(arg))
                  {
                      varargs = true;
                      break;
                  }
              }
              else if (PyLong_CheckExact(arg))
                  break;

              return -1;
          }

          case 'D':           /* double */
          {
              if (array)
              {
                  if (arg == Py_None)
                      break;

                  if (PyObject_TypeCheck(arg, PY_TYPE(JArrayDouble)))
                      break;

                  if (PySequence_Check(arg))
                  {
                      if (PySequence_Length(arg) > 0)
                      {
                          PyObject *obj = PySequence_GetItem(arg, 0);
                          int ok = PyFloat_CheckExact(obj);

                          Py_DECREF(obj);
                          if (ok)
                              break;
                      }
                      else
                          break;
                  }

                  if (last && PyFloat_CheckExact(arg))
                  {
                      varargs = true;
                      break;
                  }
              }
              else if (PyFloat_CheckExact(arg))
                  break;

              return -1;
          }

          case 'F':           /* float */
          {
              if (array)
              {
                  if (arg == Py_None)
                      break;

                  if (PyObject_TypeCheck(arg, PY_TYPE(JArrayFloat)))
                      break;

                  if (PySequence_Check(arg))
                  {
                      if (PySequence_Length(arg) > 0)
                      {
                          PyObject *obj = PySequence_GetItem(arg, 0);
                          int ok = PyFloat_CheckExact(obj);

                          Py_DECREF(obj);
                          if (ok)
                              break;
                      }
                      else
                          break;
                  }

                  if (last && PyFloat_CheckExact(arg))
                  {
                      varargs = true;
                      break;
                  }
              }
              else if (PyFloat_CheckExact(arg))
                  break;

              return -1;
          }

          case 'J':           /* long long */
          {
              if (array)
              {
                  if (arg == Py_None)
                      break;

                  if (PyObject_TypeCheck(arg, PY_TYPE(JArrayLong)))
                      break;

                  if (PySequence_Check(arg))
                  {
                      if (PySequence_Length(arg) > 0)
                      {
                          PyObject *obj = PySequence_GetItem(arg, 0);
                          int ok = PyLong_CheckExact(obj);

                          Py_DECREF(obj);
                          if (ok)
                              break;
                      }
                      else
                          break;
                  }

                  if (last && PyLong_CheckExact(arg))
                  {
                      varargs = true;
                      break;
                  }
              }
              else if (PyLong_CheckExact(arg))
                  break;

              return -1;
          }

          case 's':           /* string  */
          {
              if (array)
              {
                  if (arg == Py_None)
                      break;

                  if (PyObject_TypeCheck(arg, PY_TYPE(JArrayString)))
                      break;

                  if (PySequence_Check(arg) &&
                      !PyBytes_Check(arg) && !PyUnicode_Check(arg))
                  {
                      if (PySequence_Length(arg) > 0)
                      {
                          PyObject *obj = PySequence_GetItem(arg, 0);
                          int ok =
                              (obj == Py_None ||
                               PyBytes_Check(obj) || PyUnicode_Check(obj));

                          Py_DECREF(obj);
                          if (ok)
                              break;
                      }
                      else
                          break;
                  }

                  if (last && (arg == Py_None ||
                               PyBytes_Check(arg) || PyUnicode_Check(arg)))
                  {
                      varargs = true;
                      break;
                  }
              }
              else if (arg == Py_None ||
                       PyBytes_Check(arg) || PyUnicode_Check(arg))
                  break;

              return -1;
          }

          case 'o':         /* java.lang.Object */
            break;

          case 'O':         /* java.lang.Object with type param */
          {
              PyTypeObject *type = va_arg(list, PyTypeObject *);

              if (type != NULL)
              {
                  boxfn fn = get_boxfn(type);

                  if (fn == NULL || fn(type, arg, NULL) < 0)
                      return -1;
              }
              break;
          }

          case 'T':         /* tuple of python types with wrapfn_ */
          {
              static PyObject *wrapfn_ = PyUnicode_FromString("wrapfn_");
              int len = va_arg(list, int);

              if (PyTuple_Check(arg))
              {
                  if (PyTuple_GET_SIZE(arg) != len)
                      return -1;

                  for (int i = 0; i < len; i++) {
                      PyObject *type = PyTuple_GET_ITEM(arg, i);

                      if (!(type == Py_None ||
                            (PyType_Check(type) &&
                             PyObject_HasAttr(type, wrapfn_))))
                          return -1;
                  }
                  break;
              }
              return -1;
          }

          default:
            return -1;
        }

        if (tc != '[')
            array = 0;

        if (varargs)
        {
            pos = typeCount;
            break;
        }
    }

    if (array)
        return -1;

    if (pos == typeCount - 2 && types[pos] == '[' && types[pos + 1] != '[')
    {
        varargs = true;
        empty = true;
        pos = typeCount;
    }

    if (pos != typeCount)
      return -1;

    pos = 0;
    last = false;

    for (unsigned int a = 0; a <= count; a++, pos++) {
        char tc = types[pos];
        PyObject *arg;

        if (a == count)
        {
            if (empty)  /* empty varargs */
                arg = NULL;
            else
                break;
        }
        else
            arg = args[a];

        last = last || types[pos + 1] == '\0';

        if (array > 1 && tc != '[')
            tc = 'o';

        switch (tc) {
          case '[':
          {
              array += 1;
              a -= 1;

              break;
          }

          case 'j':           /* Java object except String and Object */
          case 'k':           /* Java object, with initializeClass    */
          case 'K':           /* Java object, with initializeClass and params */
          {
              jclass cls = NULL;

              switch (tc) {
                case 'j':
                  cls = (jclass) va_arg(check, Class *)->this$;
                  break;
                case 'k':
                case 'K':
                  getclassfn initializeClass = va_arg(check, getclassfn);
                  cls = env->getClass(initializeClass);
                  break;
              }

              if (array)
              {
                  JArray<jobject> *array = va_arg(list, JArray<jobject> *);

#ifdef _java_generics
                  if (tc == 'K')
                  {
                      PyTypeObject ***tp = va_arg(list, PyTypeObject ***);

                      va_arg(list, getparametersfn);
                      *tp = NULL;
                  }
#endif
                  if (arg == Py_None)
                      *array = JArray<jobject>((jobject) NULL);
                  else if (last && varargs)
                      *array = fromPySequence(cls, args + a, count - a);
                  else if (PyObject_TypeCheck(arg, PY_TYPE(JArrayObject)))
                      *array = ((t_JArray<jobject> *) arg)->array;
                  else
                      *array = JArray<jobject>(cls, arg);

                  if (PyErr_Occurred())
                      return -1;
              }
              else
              {
                  Object *obj = va_arg(list, Object *);

                  if (PyObject_TypeCheck(arg, PY_TYPE(FinalizerProxy)))
                      arg = ((t_fp *) arg)->object;

#ifdef _java_generics
                  if (tc == 'K')
                  {
                      PyTypeObject ***tp = va_arg(list, PyTypeObject ***);
                      PyTypeObject **(*parameters_)(void *) =
                          va_arg(list, getparametersfn);

                      if (arg == Py_None)
                          *tp = NULL;
                      else
                          *tp = (*parameters_)(arg);
                  }
#endif

                  *obj = arg == Py_None
                      ? Object(NULL)
                      : ((t_Object *) arg)->object;
              }
              break;
          }

          case 'Z':           /* boolean, strict */
          {
              if (array)
              {
                  JArray<jboolean> *array = va_arg(list, JArray<jboolean> *);

                  if (arg == Py_None)
                      *array = JArray<jboolean>((jobject) NULL);
                  else if (last && varargs)
                      *array = JArray<jboolean>(args + a, count - a);
                  else if (PyObject_TypeCheck(arg, PY_TYPE(JArrayBool)))
                      *array = ((t_JArray<jboolean> *) arg)->array;
                  else
                      *array = JArray<jboolean>(arg);

                  if (PyErr_Occurred())
                      return -1;
              }
              else
              {
                  jboolean *b = va_arg(list, jboolean *);
                  *b = arg == Py_True;
              }
              break;
          }

          case 'B':           /* byte */
          {
              if (array)
              {
                  JArray<jbyte> *array = va_arg(list, JArray<jbyte> *);

                  if (arg == Py_None)
                      *array = JArray<jbyte>((jobject) NULL);
                  else if (last && varargs)
                      *array = JArray<jbyte>(args + a, count - a);
                  else if (PyObject_TypeCheck(arg, PY_TYPE(JArrayByte)))
                      *array = ((t_JArray<jbyte> *) arg)->array;
                  else
                      *array = JArray<jbyte>(arg);

                  if (PyErr_Occurred())
                      return -1;
              }
              else if (PyBytes_Check(arg))
              {
                  jbyte *a = va_arg(list, jbyte *);
                  *a = (jbyte) PyBytes_AS_STRING(arg)[0];
              }
              else if (PyUnicode_Check(arg))
              {
                  jbyte *a = va_arg(list, jbyte *);
                  *a = (jbyte) PyUnicode_AsUTF8(arg)[0];
              }
              else
              {
                  jbyte *a = va_arg(list, jbyte *);
                  *a = (jbyte) PyLong_AsLong(arg);
              }
              break;
          }

          case 'C':           /* char */
          {
              if (array)
              {
                  JArray<jchar> *array = va_arg(list, JArray<jchar> *);

                  if (arg == Py_None)
                      *array = JArray<jchar>((jobject) NULL);
                  else if (last && varargs)
                      *array = JArray<jchar>(args + a, count - a);
                  else if (PyObject_TypeCheck(arg, PY_TYPE(JArrayChar)))
                      *array = ((t_JArray<jchar> *) arg)->array;
                  else
                      *array = JArray<jchar>(arg);

                  if (PyErr_Occurred())
                      return -1;
              }
              else
              {
                  jchar *c = va_arg(list, jchar *);
                  *c = (jchar) PyUnicode_READ_CHAR(arg, 0);
              }
              break;
          }

          case 'I':           /* int */
          {
              if (array)
              {
                  JArray<jint> *array = va_arg(list, JArray<jint> *);

                  if (arg == Py_None)
                      *array = JArray<jint>((jobject) NULL);
                  else if (last && varargs)
                      *array = JArray<jint>(args + a, count - a);
                  else if (PyObject_TypeCheck(arg, PY_TYPE(JArrayInt)))
                      *array = ((t_JArray<jint> *) arg)->array;
                  else
                      *array = JArray<jint>(arg);

                  if (PyErr_Occurred())
                      return -1;
              }
              else
              {
                  jint *n = va_arg(list, jint *);
                  *n = (jint) PyLong_AsLong(arg);
              }
              break;
          }

          case 'S':           /* short */
          {
              if (array)
              {
                  JArray<jshort> *array = va_arg(list, JArray<jshort> *);

                  if (arg == Py_None)
                      *array = JArray<jshort>((jobject) NULL);
                  else if (last && varargs)
                      *array = JArray<jshort>(args + a, count - a);
                  else if (PyObject_TypeCheck(arg, PY_TYPE(JArrayShort)))
                      *array = ((t_JArray<jshort> *) arg)->array;
                  else
                      *array = JArray<jshort>(arg);

                  if (PyErr_Occurred())
                      return -1;
              }
              else
              {
                  jshort *n = va_arg(list, jshort *);
                  *n = (jshort) PyLong_AsLong(arg);
              }
              break;
          }

          case 'D':           /* double */
          {
              if (array)
              {
                  JArray<jdouble> *array = va_arg(list, JArray<jdouble> *);

                  if (arg == Py_None)
                      *array = JArray<jdouble>((jobject) NULL);
                  else if (last && varargs)
                      *array = JArray<jdouble>(args + a, count - a);
                  else if (PyObject_TypeCheck(arg, PY_TYPE(JArrayDouble)))
                      *array = ((t_JArray<jdouble> *) arg)->array;
                  else
                      *array = JArray<jdouble>(arg);

                  if (PyErr_Occurred())
                      return -1;
              }
              else
              {
                  jdouble *d = va_arg(list, jdouble *);
                  *d = (jdouble) PyFloat_AsDouble(arg);
              }
              break;
          }

          case 'F':           /* float */
          {
              if (array)
              {
                  JArray<jfloat> *array = va_arg(list, JArray<jfloat> *);

                  if (arg == Py_None)
                      *array = JArray<jfloat>((jobject) NULL);
                  else if (last && varargs)
                      *array = JArray<jfloat>(args + a, count - a);
                  else if (PyObject_TypeCheck(arg, PY_TYPE(JArrayFloat)))
                      *array = ((t_JArray<jfloat> *) arg)->array;
                  else
                      *array = JArray<jfloat>(arg);

                  if (PyErr_Occurred())
                      return -1;
              }
              else
              {
                  jfloat *d = va_arg(list, jfloat *);
                  *d = (jfloat) (float) PyFloat_AsDouble(arg);
              }
              break;
          }

          case 'J':           /* long long */
          {
              if (array)
              {
                  JArray<jlong> *array = va_arg(list, JArray<jlong> *);

                  if (arg == Py_None)
                      *array = JArray<jlong>((jobject) NULL);
                  else if (last && varargs)
                      *array = JArray<jlong>(args + a, count - a);
                  else if (PyObject_TypeCheck(arg, PY_TYPE(JArrayLong)))
                      *array = ((t_JArray<jlong> *) arg)->array;
                  else
                      *array = JArray<jlong>(arg);

                  if (PyErr_Occurred())
                      return -1;
              }
              else
              {
                  jlong *l = va_arg(list, jlong *);
                  *l = (jlong) PyLong_AsLongLong(arg);
              }
              break;
          }

          case 's':           /* string  */
          {
              if (array)
              {
                  JArray<jstring> *array = va_arg(list, JArray<jstring> *);

                  if (arg == Py_None)
                      *array = JArray<jstring>((jobject) NULL);
                  else if (last && varargs)
                      *array = fromPySequence(
                          env->getClass(String::initializeClass),
                          args + a, count - a);
                  else if (PyObject_TypeCheck(arg, PY_TYPE(JArrayString)))
                      *array = ((t_JArray<jstring> *) arg)->array;
                  else
                      *array = JArray<jstring>(arg);

                  if (PyErr_Occurred())
                      return -1;
              }
              else
              {
                  String *str = va_arg(list, String *);

                  if (arg == Py_None)
                      *str = String(NULL);
                  else
                  {
                      *str = p2j(arg);
                      if (PyErr_Occurred())
                          return -1;
                  }
              }
              break;
          }

          case 'o':           /* java.lang.Object  */
          case 'O':           /* java.lang.Object with type param */
          {
              if (array)
              {
                  JArray<Object> *array = va_arg(list, JArray<Object> *);

                  if (arg == Py_None)
                      *array = JArray<Object>((jobject) NULL);
                  else if (last && varargs)
                      *array = JArray<Object>(
                          env->getClass(Object::initializeClass),
                          args + a, count - a);
                  else
                      *array = JArray<Object>(arg);

                  if (PyErr_Occurred())
                      return -1;
              }
              else
              {
                  Object *obj = va_arg(list, Object *);

                  if (tc == 'O')
                  {
                      PyTypeObject *type = va_arg(check, PyTypeObject *);

                      if (type != NULL)
                      {
                          boxfn fn = get_boxfn(type);

                          if (fn == NULL || fn(type, arg, obj) < 0)
                              return -1;

                          break;
                      }
                  }

                  if (boxObject(NULL, arg, obj) < 0)
                      return -1;
              }
              break;
          }

          case 'T':         /* tuple of python types with wrapfn_ */
          {
              int len = va_arg(check, int);
              PyTypeObject **types = va_arg(list, PyTypeObject **);

              for (int i = 0; i < len; i++) {
                  PyObject *type = PyTuple_GET_ITEM(arg, i);

                  if (type == Py_None)
                      types[i] = NULL;
                  else
                      types[i] = (PyTypeObject *) type;
              }
              break;
          }

          default:
            return -1;
        }

        if (tc != '[')
            array = 0;

        if (last && varargs)
        {
            pos = typeCount;
            break;
        }
    }

    if (pos == typeCount)
        return 0;

    return -1;
}


String p2j(PyObject *object)
{
    return String(env->fromPyString(object));
}

PyObject *j2p(const String& js)
{
    return env->fromJString((jstring) js.this$, 0);
}

PyObject *c2p(jchar c)
{
    PyObject *result = PyUnicode_New(1, c);

    PyUnicode_WriteChar(result, 0, c);
    return result;
}

PyObject *PyErr_SetArgsError(char *name, PyObject *args)
{
    if (!PyErr_Occurred())
    {
        PyObject *err = Py_BuildValue("(sO)", name, args);

        PyErr_SetObject(PyExc_InvalidArgsError, err);
        Py_DECREF(err);
    }

    return NULL;
}

PyObject *PyErr_SetArgsError(PyObject *self, char *name, PyObject *args)
{
    if (!PyErr_Occurred())
    {
        PyObject *type = (PyObject *) self->ob_type;
        PyObject *err = Py_BuildValue("(OsO)", type, name, args);

        PyErr_SetObject(PyExc_InvalidArgsError, err);
        Py_DECREF(err);
    }

    return NULL;
}

PyObject *PyErr_SetArgsError(PyTypeObject *type, char *name, PyObject *args)
{
    if (!PyErr_Occurred())
    {
        PyObject *err = Py_BuildValue("(OsO)", type, name, args);

        PyErr_SetObject(PyExc_InvalidArgsError, err);
        Py_DECREF(err);
    }

    return NULL;
}

PyObject *PyErr_SetJavaError()
{
    JNIEnv *vm_env = env->get_vm_env();
    jthrowable throwable = vm_env->ExceptionOccurred();

    vm_env->ExceptionClear();

    if (env->restorePythonException(throwable))
        return NULL;

    PyObject *err = t_Throwable::wrap_Object(Throwable(throwable));

    PyErr_SetObject(PyExc_JavaError, err);
    Py_DECREF(err);

    return NULL;
}

void throwPythonError(void)
{
    PyObject *exc = PyErr_Occurred();

    if (exc && PyErr_GivenExceptionMatches(exc, PyExc_JavaError))
    {
        PyObject *value, *traceback;

        PyErr_Fetch(&exc, &value, &traceback);
        if (value)
        {
            PyObject *je = PyObject_CallMethod(value, "getJavaException", "");

            if (!je)
                PyErr_Restore(exc, value, traceback);
            else
            {
                Py_DECREF(exc);
                Py_DECREF(value);
                Py_XDECREF(traceback);
                exc = je;

                if (exc && PyObject_TypeCheck(exc, PY_TYPE(Throwable)))
                {
                    jobject jobj = ((t_Throwable *) exc)->object.this$;

                    env->get_vm_env()->Throw((jthrowable) jobj);
                    Py_DECREF(exc);

                    return;
                }
            }
        }
        else
        {
            Py_DECREF(exc);
            Py_XDECREF(traceback);
        }
    }
    else if (exc && PyErr_GivenExceptionMatches(exc, PyExc_StopIteration))
    {
        PyErr_Clear();
        return;
    }

    if (exc)
    {
        PyObject *name = PyObject_GetAttrString(exc, "__name__");

        env->get_vm_env()->ThrowNew(env->getPythonExceptionClass(),
                                    PyUnicode_AsUTF8(name));
        Py_DECREF(name);
    }
    else
        env->get_vm_env()->ThrowNew(env->getPythonExceptionClass(),
                                    "python error");
}

void throwTypeError(const char *name, PyObject *object)
{
    PyObject *tuple = Py_BuildValue("(ssO)", "while calling", name, object);

    PyErr_SetObject(PyExc_TypeError, tuple);
    Py_DECREF(tuple);

    env->get_vm_env()->ThrowNew(env->getPythonExceptionClass(), "type error");
}

int abstract_init(PyObject *self, PyObject *args, PyObject *kwds)
{
    PyObject *err =
        Py_BuildValue("(sO)", "instantiating java class", self->ob_type);

    PyErr_SetObject(PyExc_NotImplementedError, err);
    Py_DECREF(err);

    return -1;
}

PyObject *callSuper(PyTypeObject *type, const char *name, PyObject *args,
                    int cardinality)
{
    PyObject *super = (PyObject *) type->tp_base;
    PyObject *method =
        PyObject_GetAttrString(super, (char *) name); // python 2.4 cast
    PyObject *value;

    if (!method)
        return NULL;

    if (cardinality > 1)
        value = PyObject_Call(method, args, NULL);
    else
    {
        PyObject *tuple = PyTuple_Pack(1, args);

        value = PyObject_Call(method, tuple, NULL);
        Py_DECREF(tuple);
    }

    Py_DECREF(method);

    return value;
}

PyObject *callSuper(PyTypeObject *type, PyObject *self,
                    const char *name, PyObject *args, int cardinality)
{
    PyObject *tuple = PyTuple_Pack(2, type, self);
    PyObject *super = PyObject_Call((PyObject *) &PySuper_Type, tuple, NULL);
    PyObject *method, *value;

    Py_DECREF(tuple);
    if (!super)
        return NULL;

    method = PyObject_GetAttrString(super, (char *) name); // python 2.4 cast
    Py_DECREF(super);
    if (!method)
        return NULL;

    if (cardinality > 1)
        value = PyObject_Call(method, args, NULL);
    else
    {
        tuple = PyTuple_Pack(1, args);
        value = PyObject_Call(method, tuple, NULL);
        Py_DECREF(tuple);
    }

    Py_DECREF(method);

    return value;
}

PyObject *castCheck(PyObject *obj, getclassfn initializeClass,
                    int reportError)
{
    if (PyObject_TypeCheck(obj, PY_TYPE(FinalizerProxy)))
        obj = ((t_fp *) obj)->object;

    if (!PyObject_TypeCheck(obj, PY_TYPE(Object)))
    {
        if (reportError)
            PyErr_SetObject(PyExc_TypeError, obj);
        return NULL;
    }

    jobject jobj = ((t_Object *) obj)->object.this$;

    if (jobj && !env->isInstanceOf(jobj, initializeClass))
    {
        if (reportError)
            PyErr_SetObject(PyExc_TypeError, obj);

        return NULL;
    }

    return obj;
}

PyObject *get_extension_iterator(PyObject *self)
{
    return PyObject_CallMethod(self, "iterator", "");
}

PyObject *get_extension_next(PyObject *self)
{
    return PyObject_CallMethod(self, "next", "");
}

PyObject *get_extension_nextElement(PyObject *self)
{
    return PyObject_CallMethod(self, "nextElement", "");
}

static bool setArrayObj(jobjectArray array, int index, PyObject *obj)
{
    bool deleteLocal = false;
    jobject jobj;

    if (obj == Py_None)
      jobj = NULL;
    else if (PyBytes_Check(obj) || PyUnicode_Check(obj))
    {
        jobj = env->fromPyString(obj);
        deleteLocal = true;
    }
    else if (PyObject_TypeCheck(obj, PY_TYPE(JObject)))
        jobj = ((t_JObject *) obj)->object.this$;
    else if (PyObject_TypeCheck(obj, PY_TYPE(FinalizerProxy)))
        jobj = ((t_JObject *) ((t_fp *) obj)->object)->object.this$;
    else if (obj == Py_True || obj == Py_False)
    {
        jobj = env->boxBoolean(obj == Py_True);
        deleteLocal = true;
    }
    else if (PyFloat_Check(obj))
    {
        jobj = env->boxDouble(PyFloat_AS_DOUBLE(obj));
        deleteLocal = true;
    }
    else if (PyLong_Check(obj))
    {
        long long value = PyLong_AsLongLong(obj);

        if (static_cast<long long>(static_cast<int>(value)) == value)
          jobj = env->boxInteger((int) value);
        else
          jobj = env->boxLong(value);
        deleteLocal = true;
    }
    else
    {
        PyErr_SetObject(PyExc_TypeError, obj);
        Py_DECREF(obj);
        return false;
    }

    try {
        env->setObjectArrayElement(array, index, jobj);
        if (deleteLocal)
            env->get_vm_env()->DeleteLocalRef(jobj);
        Py_DECREF(obj);
    } catch (int e) {
        Py_DECREF(obj);
        switch (e) {
          case _EXC_JAVA:
            PyErr_SetJavaError();
            return false;
          default:
            throw;
        }
    }

    return true;
}


jobjectArray fromPySequence(jclass cls, PyObject *sequence)
{
    if (sequence == Py_None)
        return NULL;

    if (!PySequence_Check(sequence))
    {
        PyErr_SetObject(PyExc_TypeError, sequence);
        return NULL;
    }

    int length = PySequence_Length(sequence);
    jobjectArray array;

    try {
        array = env->newObjectArray(cls, length);
    } catch (int e) {
        switch (e) {
          case _EXC_PYTHON:
            return NULL;
          case _EXC_JAVA:
            PyErr_SetJavaError();
            return NULL;
          default:
            throw;
        }
    }

    for (int i = 0; i < length; i++) {
        PyObject *obj = PySequence_GetItem(sequence, i);

        if (!obj)
            break;

        if (!setArrayObj(array, i, obj))
            return NULL;
    }

    return array;
}

jobjectArray fromPySequence(jclass cls, PyObject **args, int length)
{
    jobjectArray array;

    try {
        array = env->newObjectArray(cls, length);
    } catch (int e) {
        switch (e) {
          case _EXC_PYTHON:
            return NULL;
          case _EXC_JAVA:
            PyErr_SetJavaError();
            return NULL;
          default:
            throw;
        }
    }

    for (int i = 0; i < length; i++) {
        PyObject *obj = args[i];

        if (!obj)
            break;

        Py_INCREF(obj);
        if (!setArrayObj(array, i, obj))
            return NULL;
    }

    return array;
}

PyTypeObject *makeType(PyType_Def *def)
{
    if (def->type == NULL)
    {
        int count = 0;

        for (PyType_Def **base = def->bases; *base != NULL; ++base)
            count += 1;

        PyObject *bases = PyTuple_New(count);

        for (int i = 0; i < count; ++i)
            PyTuple_SET_ITEM(bases, i, (PyObject *) makeType(def->bases[i]));

        def->type = (PyTypeObject *)
            PyType_FromSpecWithBases(&def->spec, bases);
        Py_DECREF(bases);
    }

    return def->type;
}

void installType(PyTypeObject **type, PyType_Def *def,
                 PyObject *module, char *name, int isExtension)
{
    if (*type == NULL)
    {
        *type = makeType(def);
        Py_INCREF(*type);

        if (isExtension)
        {
            Py_TYPE(*type) = PY_TYPE(FinalizerClass);
            Py_INCREF(PY_TYPE(FinalizerClass));
        }

        PyModule_AddObject(module, name, (PyObject *) *type);
    }
}

PyObject *wrapType(PyTypeObject *type, const jobject& obj)
{
    static PyObject *wrapfn_ = PyUnicode_FromString("wrapfn_");
    PyObject *cobj = PyObject_GetAttr((PyObject *) type, wrapfn_);
    PyObject *(*wrapfn)(const jobject&);

    if (cobj == NULL)
        return NULL;

    wrapfn = (PyObject *(*)(const jobject &))
        PyCapsule_GetPointer(cobj, "wrapfn");
    Py_DECREF(cobj);

    return wrapfn(obj);
}

PyObject *unboxBoolean(const jobject& obj)
{
    if (obj != NULL)
    {
        if (!env->isInstanceOf(obj, java::lang::Boolean::initializeClass))
        {
            PyErr_SetObject(PyExc_TypeError,
                            (PyObject *) java::lang::PY_TYPE(Boolean));
            return NULL;
        }

        if (env->booleanValue(obj))
            Py_RETURN_TRUE;

        Py_RETURN_FALSE;
    }

    Py_RETURN_NONE;
}

PyObject *unboxByte(const jobject &obj)
{
    if (obj != NULL)
    {
        if (!env->isInstanceOf(obj, java::lang::Byte::initializeClass))
        {
            PyErr_SetObject(PyExc_TypeError,
                            (PyObject *) java::lang::PY_TYPE(Byte));
            return NULL;
        }

        return PyLong_FromLong((long) env->byteValue(obj));
    }

    Py_RETURN_NONE;
}

PyObject *unboxCharacter(const jobject &obj)
{
    if (obj != NULL)
    {
        if (!env->isInstanceOf(obj, java::lang::Character::initializeClass))
        {
            PyErr_SetObject(PyExc_TypeError,
                            (PyObject *) java::lang::PY_TYPE(Character));
            return NULL;
        }

        return c2p(env->charValue(obj));
    }

    Py_RETURN_NONE;
}

PyObject *unboxDouble(const jobject &obj)
{
    if (obj != NULL)
    {
        if (!env->isInstanceOf(obj, java::lang::Double::initializeClass))
        {
            PyErr_SetObject(PyExc_TypeError,
                            (PyObject *) java::lang::PY_TYPE(Double));
            return NULL;
        }

        return PyFloat_FromDouble((double) env->doubleValue(obj));
    }

    Py_RETURN_NONE;
}

PyObject *unboxFloat(const jobject &obj)
{
    if (obj != NULL)
    {
        if (!env->isInstanceOf(obj, java::lang::Float::initializeClass))
        {
            PyErr_SetObject(PyExc_TypeError,
                            (PyObject *) java::lang::PY_TYPE(Float));
            return NULL;
        }

        return PyFloat_FromDouble((double) env->floatValue(obj));
    }

    Py_RETURN_NONE;
}

PyObject *unboxInteger(const jobject &obj)
{
    if (obj != NULL)
    {
        if (!env->isInstanceOf(obj, java::lang::Integer::initializeClass))
        {
            PyErr_SetObject(PyExc_TypeError,
                            (PyObject *) java::lang::PY_TYPE(Integer));
            return NULL;
        }

        return PyLong_FromLong((long) env->intValue(obj));
    }

    Py_RETURN_NONE;
}

PyObject *unboxLong(const jobject &obj)
{
    if (obj != NULL)
    {
        if (!env->isInstanceOf(obj, java::lang::Long::initializeClass))
        {
            PyErr_SetObject(PyExc_TypeError,
                            (PyObject *) java::lang::PY_TYPE(Long));
            return NULL;
        }

        return PyLong_FromLongLong((PY_LONG_LONG) env->longValue(obj));
    }

    Py_RETURN_NONE;
}

PyObject *unboxShort(const jobject &obj)
{
    if (obj != NULL)
    {
        if (!env->isInstanceOf(obj, java::lang::Short::initializeClass))
        {
            PyErr_SetObject(PyExc_TypeError,
                            (PyObject *) java::lang::PY_TYPE(Short));
            return NULL;
        }

        return PyLong_FromLong((long) env->shortValue(obj));
    }

    Py_RETURN_NONE;
}

PyObject *unboxString(const jobject &obj)
{
    if (obj != NULL)
    {
        if (!env->isInstanceOf(obj, java::lang::String::initializeClass))
        {
            PyErr_SetObject(PyExc_TypeError,
                            (PyObject *) java::lang::PY_TYPE(String));
            return NULL;
        }

        return env->fromJString((jstring) obj, 0);
    }

    Py_RETURN_NONE;
}

static int boxJObject(PyTypeObject *type, PyObject *arg,
                      java::lang::Object *obj)
{
    if (arg == Py_None)
    {
        if (obj != NULL)
            *obj = Object(NULL);
    }
    else if (PyObject_TypeCheck(arg, PY_TYPE(Object)))
    {
        if (type != NULL && !is_instance_of(arg, type))
            return -1;

        if (obj != NULL)
            *obj = ((t_Object *) arg)->object;
    }
    else if (PyObject_TypeCheck(arg, PY_TYPE(FinalizerProxy)))
    {
        arg = ((t_fp *) arg)->object;
        if (PyObject_TypeCheck(arg, PY_TYPE(Object)))
        {
            if (type != NULL && !is_instance_of(arg, type))
                return -1;

            if (obj != NULL)
                *obj = ((t_Object *) arg)->object;
        }
        else
            return -1;
    }
    else
        return 1;

    return 0;
}

int boxBoolean(PyTypeObject *type, PyObject *arg, java::lang::Object *obj)
{
    int result = boxJObject(type, arg, obj);

    if (result <= 0)
        return result;

    if (arg == Py_True)
    {
        if (obj != NULL)
            *obj = *Boolean::TRUE;
    }
    else if (arg == Py_False)
    {
        if (obj != NULL)
            *obj = *Boolean::FALSE;
    }
    else
        return -1;

    return 0;
}

int boxByte(PyTypeObject *type, PyObject *arg, java::lang::Object *obj)
{
    int result = boxJObject(type, arg, obj);

    if (result <= 0)
        return result;

    if (PyLong_Check(arg))
    {
        long ln = PyLong_AS_LONG(arg);
        jbyte b = (jbyte) ln;

        if (b == ln)
        {
            if (obj != NULL)
                *obj = Byte(b);
        }
        else
            return -1;
    }
    else if (PyFloat_Check(arg))
    {
        double d = PyFloat_AS_DOUBLE(arg);
        jbyte b = (jbyte) d;

        if (b == d)
        {
            if (obj != NULL)
                *obj = Byte(b);
        }
        else
            return -1;
    }
    else
        return -1;

    return 0;
}

int boxCharacter(PyTypeObject *type, PyObject *arg, java::lang::Object *obj)
{
    int result = boxJObject(type, arg, obj);

    if (result <= 0)
        return result;

    if (PyBytes_Check(arg))
    {
        char *c;
        Py_ssize_t len;

        if (PyBytes_AsStringAndSize(arg, &c, &len) < 0 || len != 1)
            return -1;

        if (obj != NULL)
            *obj = Character((jchar) c[0]);
    }
    else if (PyUnicode_Check(arg))
    {
        Py_ssize_t len = PyUnicode_GetLength(arg);

        if (len != 1)
            return -1;

        if (obj != NULL)
          *obj = Character((jchar) PyUnicode_READ_CHAR(arg, 0));
    }
    else
        return -1;

    return 0;
}

int boxCharSequence(PyTypeObject *type, PyObject *arg, java::lang::Object *obj)
{
    int result = boxJObject(type, arg, obj);

    if (result <= 0)
        return result;

    if (PyBytes_Check(arg) || PyUnicode_Check(arg))
    {
        if (obj != NULL)
        {
            *obj = p2j(arg);
            if (PyErr_Occurred())
                return -1;
        }
    }
    else
        return -1;

    return 0;
}

int boxDouble(PyTypeObject *type, PyObject *arg, java::lang::Object *obj)
{
    int result = boxJObject(type, arg, obj);

    if (result <= 0)
        return result;

    if (PyLong_Check(arg))
    {
        if (obj != NULL)
            *obj = Double((jdouble) PyLong_AsLongLong(arg));
    }
    else if (PyFloat_Check(arg))
    {
        if (obj != NULL)
            *obj = Double(PyFloat_AS_DOUBLE(arg));
    }
    else
        return -1;

    return 0;
}

int boxFloat(PyTypeObject *type, PyObject *arg, java::lang::Object *obj)
{
    int result = boxJObject(type, arg, obj);

    if (result <= 0)
        return result;

    if (PyLong_Check(arg))
    {
        PY_LONG_LONG ln = PyLong_AsLongLong(arg);
        float f = (float) ln;

        if ((PY_LONG_LONG) f == ln)
        {
            if (obj != NULL)
                *obj = Float(f);
        }
        else
            return -1;
    }
    else if (PyFloat_Check(arg))
    {
        double d = PyFloat_AS_DOUBLE(arg);
        float f = (float) d;

        if ((double) f == d)
        {
            if (obj != NULL)
                *obj = Float(f);
        }
        else
            return -1;
    }
    else
        return -1;

    return 0;
}

int boxInteger(PyTypeObject *type, PyObject *arg, java::lang::Object *obj)
{
    int result = boxJObject(type, arg, obj);

    if (result <= 0)
        return result;

    if (PyLong_Check(arg))
    {
        PY_LONG_LONG ln = PyLong_AsLongLong(arg);
        int n = (int) ln;

        if (n == ln)
        {
            if (obj != NULL)
                *obj = Integer(n);
        }
        else
            return -1;
    }
    else if (PyFloat_Check(arg))
    {
        double d = PyFloat_AS_DOUBLE(arg);
        int n = (int) d;

        if ((double) n == d)
        {
            if (obj != NULL)
                *obj = Integer(n);
        }
        else
            return -1;
    }
    else
        return -1;

    return 0;
}

int boxLong(PyTypeObject *type, PyObject *arg, java::lang::Object *obj)
{
    int result = boxJObject(type, arg, obj);

    if (result <= 0)
        return result;

    if (PyLong_Check(arg))
    {
        if (obj != NULL)
            *obj = Long((jlong) PyLong_AsLongLong(arg));
    }
    else if (PyFloat_Check(arg))
    {
        double d = PyFloat_AS_DOUBLE(arg);
        PY_LONG_LONG n = (PY_LONG_LONG) d;

        if ((double) n == d)
        {
            if (obj != NULL)
                *obj = Long((jlong) n);
        }
        else
            return -1;
    }
    else
        return -1;

    return 0;
}

int boxNumber(PyTypeObject *type, PyObject *arg, java::lang::Object *obj)
{
    int result = boxJObject(type, arg, obj);

    if (result <= 0)
        return result;

    if (PyLong_Check(arg))
    {
        if (obj != NULL)
        {
            long long value = PyLong_AsLongLong(arg);

            if (static_cast<long long>(static_cast<int>(value)) == value)
              *obj = Integer((jint) value);
            else
              *obj = Long((jlong) value);
        }
    }
    else if (PyFloat_Check(arg))
    {
        if (obj != NULL)
            *obj = Double((jdouble) PyFloat_AS_DOUBLE(arg));
    }
    else
        return -1;

    return 0;
}

int boxShort(PyTypeObject *type, PyObject *arg, java::lang::Object *obj)
{
    int result = boxJObject(type, arg, obj);

    if (result <= 0)
        return result;

    if (PyLong_Check(arg))
    {
        PY_LONG_LONG ln = PyLong_AsLongLong(arg);
        short sn = (short) ln;

        if (sn == ln)
        {
            if (obj != NULL)
                *obj = Short((jshort) sn);
        }
        else
            return -1;
    }
    else if (PyFloat_Check(arg))
    {
        double d = PyFloat_AS_DOUBLE(arg);
        short sn = (short) (int) d;

        if ((double) sn == d)
        {
            if (obj != NULL)
                *obj = Short((jshort) sn);
        }
        else
            return -1;
    }
    else
        return -1;

    return 0;
}

int boxString(PyTypeObject *type, PyObject *arg, java::lang::Object *obj)
{
    int result = boxJObject(type, arg, obj);

    if (result <= 0)
        return result;

    if (PyBytes_Check(arg) || PyUnicode_Check(arg))
    {
        if (obj != NULL)
        {
            *obj = p2j(arg);
            if (PyErr_Occurred())
                return -1;
        }
    }
    else
        return -1;

    return 0;
}

int boxObject(PyTypeObject *type, PyObject *arg, java::lang::Object *obj)
{
    int result = boxJObject(type, arg, obj);

    if (result <= 0)
        return result;

    if (obj != NULL)
    {
        if (PyBytes_Check(arg) || PyUnicode_Check(arg))
        {
            *obj = p2j(arg);
            if (PyErr_Occurred())
                return -1;
        }
        else if (arg == Py_True)
            *obj = *Boolean::TRUE;
        else if (arg == Py_False)
            *obj = *Boolean::FALSE;
        else if (PyLong_Check(arg))
        {
            long long value = PyLong_AsLongLong(arg);

            if (static_cast<long long>(static_cast<int>(value)) == value)
              *obj = Integer((jint) value);
            else
              *obj = Long((jlong) value);
        }
        else if (PyFloat_Check(arg))
            *obj = Double((jdouble) PyFloat_AS_DOUBLE(arg));
        else
            return -1;
    }
    else if (!(PyBytes_Check(arg) || PyUnicode_Check(arg) ||
               arg == Py_True || arg == Py_False ||
               PyLong_Check(arg) || PyFloat_Check(arg)))
        return -1;

    return 0;
}


#ifdef _java_generics
PyObject *typeParameters(PyTypeObject *types[], size_t size)
{
    size_t count = size / sizeof(PyTypeObject *);
    PyObject *tuple = PyTuple_New(count);

    for (size_t i = 0; i < count; i++) {
        PyObject *type = (PyObject *) types[i];

        if (type == NULL)
            type = Py_None;

        PyTuple_SET_ITEM(tuple, i, type);
        Py_INCREF(type);
    }

    return tuple;
}
#endif
