// get attribute 'name' for object. // uses obj->attr list of tuples for storage. // returns new reference. PyObject* pyjobject_getattr(PyJObject *obj, char *name) { PyObject *ret, *pyname, *methods, *members; ret = pyname = methods = members = NULL; if (!name) { Py_RETURN_NONE; } pyname = PyString_FromString(name); methods = PyString_FromString("__methods__"); members = PyString_FromString("__members__"); if (PyObject_RichCompareBool(pyname, methods, Py_EQ)) { Py_DECREF(pyname); Py_DECREF(methods); Py_DECREF(members); Py_INCREF(obj->methods); return obj->methods; } Py_DECREF(methods); if (PyObject_RichCompareBool(pyname, members, Py_EQ)) { Py_DECREF(pyname); Py_DECREF(members); Py_INCREF(obj->fields); return obj->fields; } Py_DECREF(members); if (!PyList_Check(obj->attr)) { Py_DECREF(pyname); PyErr_Format(PyExc_RuntimeError, "Invalid attr list."); return NULL; } // util function fetches from attr list for us. ret = tuplelist_getitem(obj->attr, pyname); /* new reference */ Py_DECREF(pyname); // method optimizations if (pyjmethod_check(ret) || PyJmultiMethod_Check(ret)) { /* * TODO Should not bind non-static methods to pyjclass objects, but not * sure yet how to handle multimethods and static methods. */ #if PY_MAJOR_VERSION >= 3 PyObject* wrapper = PyMethod_New(ret, (PyObject*) obj); #else PyObject* wrapper = PyMethod_New(ret, (PyObject*) obj, (PyObject*) Py_TYPE(obj)); #endif Py_DECREF(ret); ret = wrapper; } if (PyErr_Occurred() || ret == Py_None) { if (ret == Py_None) { Py_DECREF(Py_None); } PyErr_Format(PyExc_AttributeError, "attr not found: %s", name); return NULL; } if (pyjfield_check(ret)) { PyObject *t = pyjfield_get((PyJFieldObject *) ret); Py_DECREF(ret); return t; } return ret; }
// get attribute 'name' for object. // uses obj->attr list of tuples for storage. // returns new reference. static PyObject* pyjobject_getattr(PyJobject_Object *obj, char *name) { PyObject *ret, *pyname, *methods, *members; int listSize, i, found; ret = pyname = methods = members = NULL; listSize = i = found = 0; if(!name) { Py_INCREF(Py_None); return Py_None; } pyname = PyString_FromString(name); methods = PyString_FromString("__methods__"); members = PyString_FromString("__members__"); if(PyObject_Compare(pyname, methods) == 0) { Py_DECREF(pyname); Py_DECREF(methods); Py_DECREF(members); Py_INCREF(obj->methods); return obj->methods; } Py_DECREF(methods); if(PyObject_Compare(pyname, members) == 0) { Py_DECREF(pyname); Py_DECREF(members); Py_INCREF(obj->fields); return obj->fields; } Py_DECREF(members); if(!PyList_Check(obj->attr)) { Py_DECREF(pyname); PyErr_Format(PyExc_RuntimeError, "Invalid attr list."); return NULL; } // util function fetches from attr list for us. ret = tuplelist_getitem(obj->attr, pyname); /* new reference */ Py_DECREF(pyname); if(PyErr_Occurred() || ret == Py_None) { if(ret == Py_None) Py_DECREF(Py_None); PyErr_Format(PyExc_AttributeError, "attr not found: %s", name); return NULL; } if(pyjfield_check(ret)) { PyObject *t = pyjfield_get((PyJfield_Object *) ret); Py_DECREF(ret); return t; } if(pyjmethod_check(ret)) Py_INCREF(obj); return ret; }
static int pyjobject_init(JNIEnv *env, PyJObject *pyjob) { jobjectArray methodArray = NULL; jobjectArray fieldArray = NULL; int i, len = 0; jobject langClass = NULL; jstring className = NULL; const char *cClassName = NULL; PyObject *pyClassName = NULL; PyObject *pyAttrName = NULL; JepThread *jepThread; PyObject *cachedMethodList = NULL; (*env)->PushLocalFrame(env, 20); // ------------------------------ call Class.getMethods() // well, first call getClass() if (objectGetClass == 0) { objectGetClass = (*env)->GetMethodID(env, pyjob->clazz, "getClass", "()Ljava/lang/Class;"); if (process_java_exception(env) || !objectGetClass) { goto EXIT_ERROR; } } langClass = (*env)->CallObjectMethod(env, pyjob->clazz, objectGetClass); if (process_java_exception(env) || !langClass) { goto EXIT_ERROR; } /* * attach attribute java_name to the pyjobject instance to assist with * understanding the type at runtime */ if (classGetName == 0) { classGetName = (*env)->GetMethodID(env, langClass, "getName", "()Ljava/lang/String;"); } className = (*env)->CallObjectMethod(env, pyjob->clazz, classGetName); cClassName = jstring2char(env, className); pyClassName = PyString_FromString(cClassName); release_utf_char(env, className, cClassName); pyAttrName = PyString_FromString("java_name"); if (PyObject_SetAttr((PyObject *) pyjob, pyAttrName, pyClassName) == -1) { PyErr_Format(PyExc_RuntimeError, "Couldn't add java_name as attribute."); } else { pyjobject_addfield(pyjob, pyAttrName); } pyjob->javaClassName = pyClassName; Py_DECREF(pyAttrName); (*env)->DeleteLocalRef(env, className); // then, get methodid for getMethods() if (classGetMethods == 0) { classGetMethods = (*env)->GetMethodID(env, langClass, "getMethods", "()[Ljava/lang/reflect/Method;"); if (process_java_exception(env) || !classGetMethods) { goto EXIT_ERROR; } } /* * Performance improvement. The code below is very similar to previous * versions except methods are now cached in memory. * * Previously every time you instantiate a pyjobject, JEP would get the * complete list of methods, turn them into pyjmethods, and add them as * attributes to the pyjobject. * * Now JEP retains a python dictionary in memory with a key of the fully * qualified Java classname to a list of pyjmethods. Since the * Java methods will never change at runtime for a particular Class, this * is safe and drastically speeds up pyjobject instantiation by reducing * reflection calls. We continue to set and reuse the pyjmethods as * attributes on the pyjobject instance, but if pyjobject_getattr sees a * pyjmethod, it will put it inside a pymethod and return that, enabling * the reuse of the pyjmethod for this particular object instance. * * We have the GIL at this point, so we can safely assume we're * synchronized and multiple threads will not alter the dictionary at the * same time. */ jepThread = pyembed_get_jepthread(); if (jepThread == NULL) { goto EXIT_ERROR; } if (jepThread->fqnToPyJmethods == NULL) { PyObject *methodCache = PyDict_New(); jepThread->fqnToPyJmethods = methodCache; } cachedMethodList = PyDict_GetItem(jepThread->fqnToPyJmethods, pyClassName); if (cachedMethodList == NULL) { PyObject *pyjMethodList = NULL; pyjMethodList = PyList_New(0); // - GetMethodID fails when you pass the clazz object, it expects // a java.lang.Class jobject. // - if you CallObjectMethod with the langClass jclass object, // it'll return an array of methods, but they're methods of the // java.lang.reflect.Method class -- not ->object. // // so what i did here was find the methodid using langClass, // but then i call the method using clazz. methodIds for java // classes are shared.... methodArray = (jobjectArray) (*env)->CallObjectMethod(env, pyjob->clazz, classGetMethods); if (process_java_exception(env) || !methodArray) { goto EXIT_ERROR; } // for each method, create a new pyjmethod object // and add to the internal methods list. len = (*env)->GetArrayLength(env, methodArray); for (i = 0; i < len; i++) { PyJMethodObject *pymethod = NULL; jobject rmethod = NULL; rmethod = (*env)->GetObjectArrayElement(env, methodArray, i); // make new PyJMethodObject, linked to pyjob if (pyjob->object) { pymethod = pyjmethod_new(env, rmethod, pyjob); } else { pymethod = pyjmethod_new_static(env, rmethod, pyjob); } if (!pymethod) { continue; } if (pymethod->pyMethodName && PyString_Check(pymethod->pyMethodName)) { int multi = 0; Py_ssize_t cacheLen = PyList_Size(pyjMethodList); int cacheIndex = 0; for (cacheIndex = 0; cacheIndex < cacheLen; cacheIndex += 1) { PyObject* cached = PyList_GetItem(pyjMethodList, cacheIndex); if (pyjmethod_check(cached)) { PyJMethodObject* cachedMethod = (PyJMethodObject*) cached; if (PyObject_RichCompareBool(pymethod->pyMethodName, cachedMethod->pyMethodName, Py_EQ)) { PyObject* multimethod = PyJmultiMethod_New((PyObject*) pymethod, cached); PyList_SetItem(pyjMethodList, cacheIndex, multimethod); multi = 1; break; } } else if (PyJmultiMethod_Check(cached)) { PyObject* methodName = PyJmultiMethod_GetName(cached); if (PyObject_RichCompareBool(pymethod->pyMethodName, methodName, Py_EQ)) { Py_DECREF(methodName); PyJmultiMethod_Append(cached, (PyObject*) pymethod); multi = 1; break; } else { Py_DECREF(methodName); } } } if (!multi) { if (PyList_Append(pyjMethodList, (PyObject*) pymethod) != 0) { printf("WARNING: couldn't add method"); } } } Py_DECREF(pymethod); (*env)->DeleteLocalRef(env, rmethod); } // end of looping over available methods PyDict_SetItem(jepThread->fqnToPyJmethods, pyClassName, pyjMethodList); cachedMethodList = pyjMethodList; Py_DECREF(pyjMethodList); // fqnToPyJmethods will hold the reference (*env)->DeleteLocalRef(env, methodArray); } // end of setting up cache for this Java Class len = (int) PyList_Size(cachedMethodList); for (i = 0; i < len; i++) { PyObject* name = NULL; PyObject* cached = PyList_GetItem(cachedMethodList, i); if (pyjmethod_check(cached)) { PyJMethodObject* cachedMethod = (PyJMethodObject*) cached; name = cachedMethod->pyMethodName; Py_INCREF(name); } else if (PyJmultiMethod_Check(cached)) { name = PyJmultiMethod_GetName(cached); } if (name) { if (PyObject_SetAttr((PyObject *) pyjob, name, cached) != 0) { PyErr_SetString(PyExc_RuntimeError, "Couldn't add method as attribute."); } else { pyjobject_addmethod(pyjob, name); } Py_DECREF(name); } } // end of cached method optimizations // ------------------------------ process fields if (classGetFields == 0) { classGetFields = (*env)->GetMethodID(env, langClass, "getFields", "()[Ljava/lang/reflect/Field;"); if (process_java_exception(env) || !classGetFields) { goto EXIT_ERROR; } } fieldArray = (jobjectArray) (*env)->CallObjectMethod(env, pyjob->clazz, classGetFields); if (process_java_exception(env) || !fieldArray) { goto EXIT_ERROR; } // for each field, create a pyjfield object and // add to the internal members list. len = (*env)->GetArrayLength(env, fieldArray); for (i = 0; i < len; i++) { jobject rfield = NULL; PyJFieldObject *pyjfield = NULL; rfield = (*env)->GetObjectArrayElement(env, fieldArray, i); pyjfield = pyjfield_new(env, rfield, pyjob); if (!pyjfield) { continue; } if (pyjfield->pyFieldName && PyString_Check(pyjfield->pyFieldName)) { if (PyObject_SetAttr((PyObject *) pyjob, pyjfield->pyFieldName, (PyObject *) pyjfield) != 0) { printf("WARNING: couldn't add field.\n"); } else { pyjobject_addfield(pyjob, pyjfield->pyFieldName); } } Py_DECREF(pyjfield); (*env)->DeleteLocalRef(env, rfield); } (*env)->DeleteLocalRef(env, fieldArray); // we've finished the object. pyjob->finishAttr = 1; (*env)->PopLocalFrame(env, NULL); return 1; EXIT_ERROR: (*env)->PopLocalFrame(env, NULL); if (PyErr_Occurred()) { // java exceptions translated by this time if (pyjob) { pyjobject_dealloc(pyjob); } } return 0; }
// find and call a method on this object that matches the python args. // typically called by way of pyjmethod when python invokes __call__. // // steals reference to self, methodname and args. PyObject* find_method(JNIEnv *env, PyObject *methodName, Py_ssize_t methodCount, PyObject *attr, PyObject *args) { // all possible method candidates PyJmethod_Object **cand = NULL; Py_ssize_t pos, i, listSize, argsSize; pos = i = listSize = argsSize = 0; // not really likely if we were called from pyjmethod, but hey... if(methodCount < 1) { PyErr_Format(PyExc_RuntimeError, "I have no methods."); return NULL; } if(!attr || !PyList_CheckExact(attr)) { PyErr_Format(PyExc_RuntimeError, "Invalid attr list."); return NULL; } cand = (PyJmethod_Object **) PyMem_Malloc(sizeof(PyJmethod_Object*) * methodCount); // just for safety for(i = 0; i < methodCount; i++) cand[i] = NULL; listSize = PyList_GET_SIZE(attr); for(i = 0; i < listSize; i++) { PyObject *tuple = PyList_GetItem(attr, i); /* borrowed */ if(PyErr_Occurred()) break; if(!tuple || tuple == Py_None || !PyTuple_CheckExact(tuple)) continue; if(PyTuple_Size(tuple) == 2) { PyObject *key = PyTuple_GetItem(tuple, 0); /* borrowed */ if(PyErr_Occurred()) break; if(!key || !PyString_Check(key)) continue; if(PyObject_Compare(key, methodName) == 0) { PyObject *method = PyTuple_GetItem(tuple, 1); /* borrowed */ if(pyjmethod_check(method)) cand[pos++] = (PyJmethod_Object *) method; } } } if(PyErr_Occurred()) goto EXIT_ERROR; // makes more sense to work with... pos--; if(pos < 0) { // didn't find a method by that name.... // that shouldn't happen unless the search above is broken. PyErr_Format(PyExc_NameError, "No such method."); goto EXIT_ERROR; } if(pos == 0) { // we're done, call that one PyObject *ret = pyjmethod_call_internal(cand[0], args); PyMem_Free(cand); return ret; } // first, find out if there's only one method that // has the correct number of args argsSize = PyTuple_Size(args); { PyJmethod_Object *matching = NULL; int count = 0; for(i = 0; i <= pos && cand[i]; i++) { // make sure method is fully initialized if(!cand[i]->parameters) { if(!pyjmethod_init(env, cand[i])) { // init failed, that's not good. cand[i] = NULL; PyErr_Warn(PyExc_Warning, "pyjmethod init failed."); continue; } } if(cand[i]->lenParameters == argsSize) { matching = cand[i]; count++; } else cand[i] = NULL; // eliminate non-matching } if(matching && count == 1) { PyMem_Free(cand); return pyjmethod_call_internal(matching, args); } } // local scope for(i = 0; i <= pos; i++) { int parmpos = 0; // already eliminated? if(!cand[i]) continue; // check if argument types match (*env)->PushLocalFrame(env, 20); for(parmpos = 0; parmpos < cand[i]->lenParameters; parmpos++) { PyObject *param = PyTuple_GetItem(args, parmpos); int paramTypeId = -1; jclass pclazz; jclass paramType = (jclass) (*env)->GetObjectArrayElement(env, cand[i]->parameters, parmpos); if(process_java_exception(env) || !paramType) break; pclazz = (*env)->GetObjectClass(env, paramType); if(process_java_exception(env) || !pclazz) break; paramTypeId = get_jtype(env, paramType, pclazz); if(pyarg_matches_jtype(env, param, paramType, paramTypeId)) { if(PyErr_Occurred()) break; continue; } // args don't match break; } (*env)->PopLocalFrame(env, NULL); // this method matches? if(parmpos == cand[i]->lenParameters) { PyObject *ret = pyjmethod_call_internal(cand[i], args); PyMem_Free(cand); return ret; } } EXIT_ERROR: PyMem_Free(cand); if(!PyErr_Occurred()) PyErr_Format(PyExc_NameError, "Matching overloaded method not found."); return NULL; }