Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change Java fields and methods to type attributes instead of instance attributes #386

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/c/Include/java_access/Class.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
jclass java_lang_Class_getComponentType(JNIEnv*, jclass);
jobjectArray java_lang_Class_getConstructors(JNIEnv*, jclass);
jobjectArray java_lang_Class_getDeclaredClasses(JNIEnv*, jclass);
jobjectArray java_lang_Class_getDeclaredFields(JNIEnv*, jclass);
jobjectArray java_lang_Class_getDeclaredMethods(JNIEnv*, jclass);
jobjectArray java_lang_Class_getFields(JNIEnv*, jclass);
jobjectArray java_lang_Class_getMethods(JNIEnv*, jclass);
jint java_lang_Class_getModifiers(JNIEnv*, jclass);
Expand Down
4 changes: 3 additions & 1 deletion src/main/c/Include/pyjclass.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ typedef struct {
* A python callable, either a PyJConstructor or PyJMultiMethod with many
* PyJConstructors
*/
PyObject *constructor;
PyObject *constructor;
/* A python dict containing fields, methods, and inner classes */
PyObject *attr;
} PyJClassObject;

PyObject* PyJClass_Wrap(JNIEnv*, jobject);
Expand Down
1 change: 0 additions & 1 deletion src/main/c/Include/pyjobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ extern PyTypeObject PyJObject_Type;
#define PyJObject_FIELDS \
jobject object; \
jclass clazz; \
PyObject *attr; \
PyObject *javaClassName;

typedef struct {
Expand Down
26 changes: 26 additions & 0 deletions src/main/c/Jep/java_access/Class.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
static jmethodID getComponentType = 0;
static jmethodID getConstructors = 0;
static jmethodID getDeclaredClasses = 0;
static jmethodID getDeclaredFields = 0;
static jmethodID getDeclaredMethods = 0;
static jmethodID getFields = 0;
static jmethodID getMethods = 0;
static jmethodID getModifiers = 0;
Expand Down Expand Up @@ -77,6 +79,30 @@ jobjectArray java_lang_Class_getDeclaredClasses(JNIEnv* env, jclass this)
return result;
}

jobjectArray java_lang_Class_getDeclaredFields(JNIEnv* env, jclass this)
{
jobjectArray result = NULL;
Py_BEGIN_ALLOW_THREADS
if (JNI_METHOD(getDeclaredFields, env, JCLASS_TYPE, "getDeclaredFields",
"()[Ljava/lang/reflect/Field;")) {
result = (jobjectArray) (*env)->CallObjectMethod(env, this, getDeclaredFields);
}
Py_END_ALLOW_THREADS
return result;
}

jobjectArray java_lang_Class_getDeclaredMethods(JNIEnv* env, jclass this)
{
jobjectArray result = NULL;
Py_BEGIN_ALLOW_THREADS
if (JNI_METHOD(getDeclaredMethods, env, JCLASS_TYPE, "getDeclaredMethods",
"()[Ljava/lang/reflect/Method;")) {
result = (jobjectArray) (*env)->CallObjectMethod(env, this, getDeclaredMethods);
}
Py_END_ALLOW_THREADS
return result;
}

jobjectArray java_lang_Class_getFields(JNIEnv* env, jclass this)
{
jobjectArray result = NULL;
Expand Down
10 changes: 0 additions & 10 deletions src/main/c/Jep/pyembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,16 +177,6 @@ static int initjep(JNIEnv *env, jboolean hasSharedModules)
PyModule_AddStringConstant(modjep, "JCHAR_ID", "c");
PyModule_AddStringConstant(modjep, "JBYTE_ID", "b");
PyModule_AddIntConstant(modjep, "JEP_NUMPY_ENABLED", JEP_NUMPY_ENABLED);
PyObject *javaAttrCache = PyDict_New();
if (!javaAttrCache) {
Py_DECREF(modjep);
return -1;
}
if (PyModule_AddObject(modjep, "__javaAttributeCache__", javaAttrCache)) {
Py_DECREF(javaAttrCache);
Py_DECREF(modjep);
return -1;
}
PyObject *javaTypeCache = PyDict_New();
ndjensen marked this conversation as resolved.
Show resolved Hide resolved
if (!javaTypeCache) {
Py_DECREF(modjep);
Expand Down
143 changes: 132 additions & 11 deletions src/main/c/Objects/pyjclass.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@

#include "Jep.h"

/*
* https://bugs.python.org/issue2897
* structmember.h must be included to use PyMemberDef
*/
#include "structmember.h"

/*
* Adds a single inner class as attributes to the pyjclass. This will check if
Expand Down Expand Up @@ -71,10 +76,7 @@ static PyObject* pyjclass_add_inner_class(JNIEnv *env, PyJClassObject *topClz,
charName = jstring2char(env, shortName);

if (PyDict_SetItemString(topClz->attr, charName, attrClz) != 0) {
printf("Error adding inner class %s\n", charName);
} else {
PyObject *pyname = PyUnicode_FromString(charName);
Py_DECREF(pyname);
return NULL;
}
Py_DECREF(attrClz); // parent class will hold the reference
release_utf_char(env, shortName, charName);
Expand Down Expand Up @@ -134,17 +136,67 @@ static PyObject* pyjclass_add_inner_classes(JNIEnv *env,
return (PyObject*) topClz;
}

/*
* Populate an attrs dict with all the Java methods and fields for the given
* class. This is currently the only way static methods and fields are made
* available. There is no known use for non-static fields but for now they are
* left in for backwards compatiblity, consider removing them in the future.
*
* Since the method and fields for a class are added to the type and the type
* is cached, this can just copy the attributes from he type and avoid any
bsteffensmeier marked this conversation as resolved.
Show resolved Hide resolved
* reflection.
*/
static PyObject* pyjclass_init_attr(JNIEnv *env, jclass clazz)
{
PyObject *attr = PyDict_New();
if (!attr) {
return attr;
ndjensen marked this conversation as resolved.
Show resolved Hide resolved
}

if (!(*env)->IsAssignableFrom(env, clazz, JOBJECT_TYPE)) {
/*
* Don't bother with primitive types, they don't work with PyJType and they
* don't have any fields or methods.
*/
return attr;
}
PyTypeObject* type = PyJType_Get(env, clazz);
if (!type) {
Py_DecRef(attr);
return NULL;
}
PyObject *key, *value;
Py_ssize_t pos = 0;
while (PyDict_Next(type->tp_dict, &pos, &key, &value)) {
ndjensen marked this conversation as resolved.
Show resolved Hide resolved
if (PyJMethod_Check(value) || PyJMultiMethod_Check(value)
|| PyJField_Check(value) ) {
if (PyDict_SetItem(attr, key, value) != 0) {
Py_DecRef((PyObject*) type);
Py_DecRef(attr);
return NULL;
}
}
}
Py_DecRef((PyObject*) type);
return attr;
}

static int pyjclass_init(JNIEnv *env, PyObject *pyjob)
static int pyjclass_init(JNIEnv *env, PyObject *pyobj)
{
((PyJClassObject*) pyjob)->constructor = NULL;
PyJClassObject *pyjclass = (PyJClassObject*) pyobj;

pyjclass->constructor = NULL;
pyjclass->attr = pyjclass_init_attr(env, ((PyJObject*) pyobj)->clazz);
if (pyjclass->attr == NULL) {
return 0;
}

/*
* attempt to add public inner classes as attributes since lots of people
* code with public enum. Note this will not allow the inner class to be
* imported separately, it must be accessed through the enclosing class.
*/
if (!pyjclass_add_inner_classes(env, (PyJClassObject*) pyjob)) {
if (!pyjclass_add_inner_classes(env, pyjclass)) {
/*
* let's just print the error to stderr and continue on without
* inner class support, it's not the end of the world
Expand Down Expand Up @@ -173,6 +225,7 @@ static void pyjclass_dealloc(PyJClassObject *self)
{
#if USE_DEALLOC
Py_CLEAR(self->constructor);
Py_CLEAR(self->attr);
PyJClass_Type.tp_base->tp_dealloc((PyObject*) self);
#endif
}
Expand Down Expand Up @@ -276,12 +329,80 @@ static PyObject* pyjclass_call(PyJClassObject *self,
return result;
}

// get attribute 'name' for object.
// uses obj->attr dict for storage.
// returns new reference.
static PyObject* pyjclass_getattro(PyObject *obj, PyObject *name)
{
PyObject *ret = PyObject_GenericGetAttr(obj, name);
if (ret == NULL) {
return NULL;
} else 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.
ndjensen marked this conversation as resolved.
Show resolved Hide resolved
*/
PyObject* wrapper = PyMethod_New(ret, (PyObject*) obj);
Py_DECREF(ret);
return wrapper;
} else if (PyJField_Check(ret)) {
PyObject *resolved = pyjfield_get((PyJFieldObject *) ret, (PyJObject*) obj);
Py_DECREF(ret);
return resolved;
}
return ret;
}


// set attribute v for object.
// uses obj->attr dictionary for storage.
static int pyjclass_setattro(PyObject *obj, PyObject *name, PyObject *v)
{
PyObject *cur = NULL;
if (v == NULL) {
PyErr_Format(PyExc_TypeError,
"Deleting attributes from PyJObjects is not allowed.");
return -1;
}
PyJObject *pyjobj = (PyJObject*) obj;
PyJClassObject *pyjclass = (PyJClassObject*) obj;
cur = PyDict_GetItem(pyjclass->attr, name);
if (PyErr_Occurred()) {
return -1;
}

if (cur == NULL) {
PyErr_Format(PyExc_AttributeError, "'%s' object has no attribute '%s'.",
PyUnicode_AsUTF8(pyjobj->javaClassName), PyUnicode_AsUTF8(name));
return -1;
}

if (!PyJField_Check(cur)) {
if (PyJMethod_Check(cur) || PyJMultiMethod_Check(cur)) {
PyErr_Format(PyExc_AttributeError, "'%s' object cannot assign to method '%s'.",
PyUnicode_AsUTF8(pyjobj->javaClassName), PyUnicode_AsUTF8(name));
} else {
PyErr_Format(PyExc_AttributeError,
"'%s' object cannot assign to attribute '%s'.",
PyUnicode_AsUTF8(pyjobj->javaClassName), PyUnicode_AsUTF8(name));
}
return -1;
}

return pyjfield_set((PyJFieldObject *) cur, pyjobj, v);
}


static PyTypeObject* pyjclass_GetPyType(PyJClassObject* self)
{
JNIEnv* env = pyembed_get_env();
return PyJType_Get(env, self->clazz);
}

static PyMemberDef pyjclass_members[] = {
{"__dict__", T_OBJECT, offsetof(PyJClassObject, attr), READONLY},
{0}
};

static PyGetSetDef pyjclass_getset[] = {
{"__pytype__", (getter) pyjclass_GetPyType, NULL},
Expand All @@ -305,8 +426,8 @@ PyTypeObject PyJClass_Type = {
0, /* tp_hash */
(ternaryfunc) pyjclass_call, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
pyjclass_getattro, /* tp_getattro */
pyjclass_setattro, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"jclass", /* tp_doc */
Expand All @@ -317,13 +438,13 @@ PyTypeObject PyJClass_Type = {
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
pyjclass_members, /* tp_members */
pyjclass_getset, /* tp_getset */
0, // &PyJObject_Type /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
offsetof(PyJClassObject, attr), /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
NULL, /* tp_new */
Expand Down
37 changes: 35 additions & 2 deletions src/main/c/Objects/pyjfield.c
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,39 @@ int pyjfield_set(PyJFieldObject *self, PyJObject* pyjobject, PyObject *value)
return -1;
}

static PyObject* pyjfield_descr_get(PyObject *field, PyObject *obj,
PyObject *type)
{
if (obj == Py_None || obj == NULL) {
Py_INCREF(field);
return field;
} else if (PyJObject_Check(obj)) {
return pyjfield_get((PyJFieldObject*) field, (PyJObject*) obj);
} else {
PyErr_Format(PyExc_RuntimeError,
"PyJField can only access fields on Java objects");
return NULL;
}
}


static int pyjfield_descr_set(PyObject *field, PyObject *obj, PyObject *value)
{
if (value == NULL) {
PyErr_Format(PyExc_TypeError,
"Deleting Java fields is not allowed");
return -1;
}

if (PyJObject_Check(obj)) {
return pyjfield_set((PyJFieldObject*) field, (PyJObject*) obj, value);
} else {
PyErr_Format(PyExc_RuntimeError,
"PyJField can only access fields on Java objects");
return -1;
}
}


PyTypeObject PyJField_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
Expand Down Expand Up @@ -647,8 +680,8 @@ PyTypeObject PyJField_Type = {
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
pyjfield_descr_get, /* tp_descr_get */
pyjfield_descr_set, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
Expand Down
12 changes: 11 additions & 1 deletion src/main/c/Objects/pyjmethod.c
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,16 @@ static PyObject* pyjmethod_call(PyJMethodObject *self,
return NULL;
}

static PyObject* pyjmethod_descr_get(PyObject *func, PyObject *obj,
PyObject *type)
{
if (obj == Py_None || obj == NULL) {
Py_INCREF(func);
return func;
}
return PyMethod_New(func, obj);
}

static PyMemberDef pyjmethod_members[] = {
{
"__name__", T_OBJECT_EX, offsetof(PyJMethodObject, pyMethodName), READONLY,
Expand Down Expand Up @@ -818,7 +828,7 @@ PyTypeObject PyJMethod_Type = {
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
pyjmethod_descr_get, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
Expand Down
12 changes: 11 additions & 1 deletion src/main/c/Objects/pyjmultimethod.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ static void pyjmultimethod_dealloc(PyJMultiMethodObject *self)
PyObject_Del(self);
}

static PyObject* pyjmultimethod_descr_get(PyObject *func, PyObject *obj,
PyObject *type)
{
if (obj == Py_None || obj == NULL) {
Py_INCREF(func);
return func;
}
return PyMethod_New(func, obj);
}

static PyGetSetDef pyjmultimethod_getsetlist[] = {
{"__name__", (getter) PyJMultiMethod_GetName, NULL},
{"__methods__", (getter) pyjmultimethod_getmethods, NULL},
Expand Down Expand Up @@ -236,7 +246,7 @@ PyTypeObject PyJMultiMethod_Type = {
pyjmultimethod_getsetlist, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
pyjmultimethod_descr_get, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
Expand Down
Loading