issue 465 - revised reflect

This commit is contained in:
Craig Macdonald 2020-04-07 16:04:32 +01:00
parent 5088487c6e
commit 049c97d55e
5 changed files with 148 additions and 45 deletions

View File

@ -12,6 +12,7 @@ from .jnius import (
)
log = logging.getLogger(__name__)
from collections import defaultdict
class Class(with_metaclass(MetaJavaClass, JavaClass)):
@ -195,6 +196,20 @@ def log_method(method, name, signature):
Modifier.isStrict(mods)
)
def identifyHierarchy(cls, level, classList, concrete=True):
classList.append((cls,level))
supercls = cls.getSuperclass()
if supercls is not None:
identifyHierarchy(supercls, level+1, classList, concrete)
intfcs = cls.getInterfaces()
if intfcs is not None:
for intf in intfcs:
identifyHierarchy(intf, level+1, classList, concrete)
if not concrete and cls.isInterface() and len(intfcs) ==0:
classList.append((find_javaclass('java.lang.Object'),level+1))
#return classList
def autoclass(clsname):
jniname = clsname.replace('.', '/')
@ -217,58 +232,82 @@ def autoclass(clsname):
constructors.append((sig, constructor.isVarArgs()))
classDict['__javaconstructor__'] = constructors
cls = c
level = -1
while cls is not None:
level += 1
if cls is c:
methods = cls.getDeclaredMethods()
else:
methods = cls.getMethods()
methods_name = [x.getName() for x in methods]
classHierachy=[]
identifyHierarchy(c, 0, classHierachy, not c.isInterface())
classHierachy.reverse()
print("autoclass(%s) intf %r hierarchy is %s" % (clsname,c.isInterface(),classHierachy))
clsDone=set()
clsMethods=defaultdict(list)
#we now walk the hierarchy, from top of the tree, identifying methods
#hopefully we start at java.lang.Object
for cls,level in classHierachy:
#dont analyse a given class more than once.
#many interfaces can lead to java.lang.Object
if cls in clsDone:
continue
clsDone.add(cls)
#as we are walking the entire hierarchy, we only need getDeclaredMethods()
#to get what is in this class; other parts of the hierarchy will be found
#in those respective classes.
methods = cls.getDeclaredMethods()
methods_name = [x.getName() for x in methods]
for index, method in enumerate(methods):
name = methods_name[index]
if name in classDict:
continue
# only one method available
if methods_name.count(name) == 1:
static = Modifier.isStatic(method.getModifiers())
varargs = method.isVarArgs()
sig = '({0}){1}'.format(
''.join([get_signature(x) for x in method.getParameterTypes()]),
get_signature(method.getReturnType()))
if log.level <= logging.DEBUG:
log_method(method, name, sig)
classDict[name] = (JavaStaticMethod if static else JavaMethod)(sig, varargs=varargs)
if name != 'getClass' and bean_getter(name) and len(method.getParameterTypes()) == 0:
lowername = lower_name(name[2 if name.startswith('is') else 3:])
classDict[lowername] = (lambda n: property(lambda self: getattr(self, n)()))(name)
continue
#print("%s %s" % (str(cls), name))
clsMethods[name].append((cls, method, level))
#having collated the mthods, identify if there are any with the same name
for name in clsMethods:
if len(clsMethods[name]) == 1:
#uniquely named method
owningCls, method, level = clsMethods[name][0]
static = Modifier.isStatic(method.getModifiers())
varargs = method.isVarArgs()
sig = '({0}){1}'.format(
''.join([get_signature(x) for x in method.getParameterTypes()]),
get_signature(method.getReturnType()))
if log.level <= logging.DEBUG:
log_method(method, name, sig)
classDict[name] = (JavaStaticMethod if static else JavaMethod)(sig, varargs=varargs)
if name != 'getClass' and bean_getter(name) and len(method.getParameterTypes()) == 0:
lowername = lower_name(name[2 if name.startswith('is') else 3:])
classDict[lowername] = (lambda n: property(lambda self: getattr(self, n)()))(name)
else:
# multiple signatures
signatures = []
for index, subname in enumerate(methods_name):
if subname != name:
continue
method = methods[index]
sig = '({0}){1}'.format(
''.join([get_signature(x) for x in method.getParameterTypes()]),
get_signature(method.getReturnType()))
#print("method with %d multiple signatures is %s in cls %s" % (len(clsMethods[name]), name, c))
#assume there can be no more than 10000 levels of inheritance
paramsig_to_level=defaultdict(lambda: 10000)
#we now identify if any have the same signature, as we will call the _lowest_, ie min level
for owningCls, method, level in clsMethods[name]:
param_sig = ''.join([get_signature(x) for x in method.getParameterTypes()])
#print("\t owner %s level %d param_sig %s" % (str(owningCls), level, param_sig))
if level < paramsig_to_level[param_sig]:
paramsig_to_level[param_sig] = level
for owningCls, method, level in clsMethods[name]:
param_sig = ''.join([get_signature(x) for x in method.getParameterTypes()])
#only accept the parameter signature at the deepest level of hierarchy (i.e. min level)
if level > paramsig_to_level[param_sig]:
#print("discarding %s name from %s at level %d" % (name, str(owningCls), level))
continue
return_sig = get_signature(method.getReturnType())
sig = '({0}){1}'.format(param_sig, return_sig)
if log.level <= logging.DEBUG:
log_method(method, name, sig)
signatures.append((sig, Modifier.isStatic(method.getModifiers()), method.isVarArgs()))
#print("method selected %d multiple signatures of %s" % (len(signatures), str(signatures)))
classDict[name] = JavaMultipleMethod(signatures)
_cls = cls.getSuperclass()
if not _cls and cls.isInterface():
cls = find_javaclass('java.lang.Object')
else:
cls = _cls
def _getitem(self, index):
try:
return self.get(index)
@ -295,12 +334,8 @@ def autoclass(clsname):
classDict[field.getName()] = cls(sig)
classDict['__javaclass__'] = clsname.replace('.', '/')
#print(classDict)
#if "newInstance" in classDict:
# print("newInstncae returns " + str(classDict["newInstance"].signatures()))
#print(clsname)
return MetaJavaClass.__new__(
MetaJavaClass,
clsname, # .replace('.', '_'),
clsname,
(JavaClass, ),
classDict)

View File

@ -3,6 +3,13 @@ package org.jnius;
import org.jnius.Parent;
public class Child extends Parent {
public int CHILD_FIELD = 1;
public int doCall(int child) {
return 1;
}
static public Child newInstance(){
return new Child();
}

View File

@ -1,7 +1,14 @@
package org.jnius;
public class Parent {
public int doCall(Object o) {
return 0;
}
static public Parent newInstance(){
return new Parent();
}
public int PARENT_FIELD = 0;
}

View File

@ -1,10 +1,34 @@
from jnius import autoclass
def test_methodcalls():
Parent = autoclass('org.jnius.Parent')
Child = autoclass('org.jnius.Child')
child = Child.newInstance()
parent = Parent.newInstance()
assert parent.doCall(parent) == 0
assert child.doCall(0) == 1
assert child.doCall(child) == 0
def test_fields():
Parent = autoclass('org.jnius.Parent')
Child = autoclass('org.jnius.Child')
child = Child.newInstance()
parent = Parent.newInstance()
assert parent.PARENT_FIELD == 0
assert child.CHILD_FIELD == 1
assert child.PARENT_FIELD == 0
def test_newinstance():
import logging
logging.basicConfig(level=logging.DEBUG)
Parent = autoclass('org.jnius.Parent')
Child = autoclass('org.jnius.Child')
child = Child.newInstance()
assert isinstance(child, Child)
assert isinstance(child, Parent)
if __name__ == "__main__":
test_newinstance()

View File

@ -3,6 +3,7 @@ from __future__ import division
from __future__ import absolute_import
import unittest
from jnius.reflect import autoclass
from jnius import cast
class ReflectTest(unittest.TestCase):
@ -14,6 +15,34 @@ class ReflectTest(unittest.TestCase):
stack.push('world')
self.assertEqual(stack.pop(), 'world')
self.assertEqual(stack.pop(), 'hello')
def test_list_interface(self):
ArrayList = autoclass('java.util.ArrayList')
words = ArrayList()
words.add('hello')
words.add('world')
self.assertIsNotNone(words.stream())
self.assertIsNotNone(words.iterator())
def test_super_interface(self):
LinkedList = autoclass('java.util.LinkedList')
words = LinkedList()
words.add('hello')
words.add('world')
q = cast('java.util.Queue', words)
self.assertEqual(2, q.size())
self.assertIsNotNone(q.iterator())
def test_super_object(self):
LinkedList = autoclass('java.util.LinkedList')
words = LinkedList()
words.hashCode()
def test_super_interface_object(self):
LinkedList = autoclass('java.util.LinkedList')
words = LinkedList()
q = cast('java.util.Queue', words)
q.hashCode()
def test_list_iteration(self):
ArrayList = autoclass('java.util.ArrayList')
@ -21,3 +50,4 @@ class ReflectTest(unittest.TestCase):
words.add('hello')
words.add('world')
self.assertEqual(['hello', 'world'], [word for word in words])