pyjnius/jnius/reflect.py

441 lines
17 KiB
Python

from __future__ import absolute_import
from __future__ import unicode_literals
from __future__ import division
from collections import defaultdict
import logging
from .jnius import (
JavaClass, MetaJavaClass, JavaMethod, JavaStaticMethod,
JavaField, JavaStaticField, JavaMultipleMethod, find_javaclass,
JavaException, get_signature
)
__all__ = ('autoclass', 'ensureclass', 'protocol_map')
log = logging.getLogger('kivy').getChild(__name__)
class Class(JavaClass, metaclass=MetaJavaClass):
__javaclass__ = 'java/lang/Class'
desiredAssertionStatus = JavaMethod('()Z')
forName = JavaMultipleMethod([
('(Ljava/lang/String,Z,Ljava/lang/ClassLoader;)Ljava/langClass;', True, False),
('(Ljava/lang/String;)Ljava/lang/Class;', True, False), ])
getClassLoader = JavaMethod('()Ljava/lang/ClassLoader;')
getClasses = JavaMethod('()[Ljava/lang/Class;')
getComponentType = JavaMethod('()Ljava/lang/Class;')
getConstructor = JavaMethod('([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;')
getConstructors = JavaMethod('()[Ljava/lang/reflect/Constructor;')
getDeclaredClasses = JavaMethod('()[Ljava/lang/Class;')
getDeclaredConstructor = JavaMethod('([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;')
getDeclaredConstructors = JavaMethod('()[Ljava/lang/reflect/Constructor;')
getDeclaredField = JavaMethod('(Ljava/lang/String;)Ljava/lang/reflect/Field;')
getDeclaredFields = JavaMethod('()[Ljava/lang/reflect/Field;')
getDeclaredMethod = JavaMethod('(Ljava/lang/String,[Ljava/lang/Class;)Ljava/lang/reflect/Method;')
getDeclaredMethods = JavaMethod('()[Ljava/lang/reflect/Method;')
getDeclaringClass = JavaMethod('()Ljava/lang/Class;')
getField = JavaMethod('(Ljava/lang/String;)Ljava/lang/reflect/Field;')
getFields = JavaMethod('()[Ljava/lang/reflect/Field;')
getInterfaces = JavaMethod('()[Ljava/lang/Class;')
getMethod = JavaMethod('(Ljava/lang/String,[Ljava/lang/Class;)Ljava/lang/reflect/Method;')
getMethods = JavaMethod('()[Ljava/lang/reflect/Method;')
getModifiers = JavaMethod('()[I')
getName = JavaMethod('()Ljava/lang/String;')
getPackage = JavaMethod('()Ljava/lang/Package;')
getProtectionDomain = JavaMethod('()Ljava/security/ProtectionDomain;')
getResource = JavaMethod('(Ljava/lang/String;)Ljava/net/URL;')
getResourceAsStream = JavaMethod('(Ljava/lang/String;)Ljava/io/InputStream;')
getSigners = JavaMethod('()[Ljava/lang/Object;')
getSuperclass = JavaMethod('()Ljava/lang/Class;')
isArray = JavaMethod('()Z')
isAssignableFrom = JavaMethod('(Ljava/lang/Class;)Z')
isInstance = JavaMethod('(Ljava/lang/Object;)Z')
isInterface = JavaMethod('()Z')
isPrimitive = JavaMethod('()Z')
newInstance = JavaMethod('()Ljava/lang/Object;')
toString = JavaMethod('()Ljava/lang/String;')
def __str__(self):
return (
'%s: [%s]' if self.isArray() else '%s: %s'
) % (
'Interface' if self.isInterface() else
'Primitive' if self.isPrimitive() else
'Class',
self.getName()
)
def __repr__(self):
return '<%s at 0x%x>' % (self, id(self))
class Object(JavaClass, metaclass=MetaJavaClass):
__javaclass__ = 'java/lang/Object'
getClass = JavaMethod('()Ljava/lang/Class;')
hashCode = JavaMethod('()I')
class Modifier(JavaClass, metaclass=MetaJavaClass):
__javaclass__ = 'java/lang/reflect/Modifier'
isAbstract = JavaStaticMethod('(I)Z')
isFinal = JavaStaticMethod('(I)Z')
isInterface = JavaStaticMethod('(I)Z')
isNative = JavaStaticMethod('(I)Z')
isPrivate = JavaStaticMethod('(I)Z')
isProtected = JavaStaticMethod('(I)Z')
isPublic = JavaStaticMethod('(I)Z')
isStatic = JavaStaticMethod('(I)Z')
isStrict = JavaStaticMethod('(I)Z')
isSynchronized = JavaStaticMethod('(I)Z')
isTransient = JavaStaticMethod('(I)Z')
isVolatile = JavaStaticMethod('(I)Z')
class Method(JavaClass, metaclass=MetaJavaClass):
__javaclass__ = 'java/lang/reflect/Method'
getName = JavaMethod('()Ljava/lang/String;')
toString = JavaMethod('()Ljava/lang/String;')
getParameterTypes = JavaMethod('()[Ljava/lang/Class;')
getReturnType = JavaMethod('()Ljava/lang/Class;')
getModifiers = JavaMethod('()I')
isVarArgs = JavaMethod('()Z')
isDefault = JavaMethod('()Z')
class Field(JavaClass, metaclass=MetaJavaClass):
__javaclass__ = 'java/lang/reflect/Field'
getName = JavaMethod('()Ljava/lang/String;')
toString = JavaMethod('()Ljava/lang/String;')
getType = JavaMethod('()Ljava/lang/Class;')
getModifiers = JavaMethod('()I')
class Constructor(JavaClass, metaclass=MetaJavaClass):
__javaclass__ = 'java/lang/reflect/Constructor'
toString = JavaMethod('()Ljava/lang/String;')
getParameterTypes = JavaMethod('()[Ljava/lang/Class;')
getModifiers = JavaMethod('()I')
isVarArgs = JavaMethod('()Z')
registers = []
def ensureclass(clsname):
if clsname in registers:
return
jniname = clsname.replace('.', '/')
if MetaJavaClass.get_javaclass(jniname):
return
registers.append(clsname)
autoclass(clsname)
def lower_name(s):
return s[:1].lower() + s[1:] if s else ''
def bean_getter(s):
return (s.startswith('get') and len(s) > 3 and s[3].isupper()) or (s.startswith('is') and len(s) > 2 and s[2].isupper())
def log_method(method, name, signature):
mods = method.getModifiers()
log.debug(
'\nmeth: %s\n'
' sig: %s\n'
' Public %s\n'
' Private %s\n'
' Protected %s\n'
' Static %s\n'
' Final %s\n'
' Synchronized %s\n'
' Volatile %s\n'
' Transient %s\n'
' Native %s\n'
' Interface %s\n'
' Abstract %s\n'
' Strict %s\n',
name,
signature,
Modifier.isPublic(mods),
Modifier.isPrivate(mods),
Modifier.isProtected(mods),
Modifier.isStatic(mods),
Modifier.isFinal(mods),
Modifier.isSynchronized(mods),
Modifier.isVolatile(mods),
Modifier.isTransient(mods),
Modifier.isNative(mods),
Modifier.isInterface(mods),
Modifier.isAbstract(mods),
Modifier.isStrict(mods)
)
def identify_hierarchy(cls, level, concrete=True):
supercls = cls.getSuperclass()
if supercls is not None:
for sup, lvl in identify_hierarchy(supercls, level + 1, concrete=concrete):
yield sup, lvl # we could use yield from when we drop python2
interfaces = cls.getInterfaces()
for interface in interfaces or []:
for sup, lvl in identify_hierarchy(interface, level + 1, concrete=concrete):
yield sup, lvl
# all object extends Object, so if this top interface in a hierarchy, yield Object
if not concrete and cls.isInterface() and not interfaces:
yield find_javaclass('java.lang.Object'), level +1
yield cls, level
# NOTE: if you change the include_protected or include_private default values,
# you also must change the classparams default value in MetaJavaClass.__new__
# and MetaJavaClass.get_javaclass.
def autoclass(clsname, include_protected=True, include_private=True):
jniname = clsname.replace('.', '/')
cls = MetaJavaClass.get_javaclass(jniname, classparams=(include_protected, include_private))
if cls:
return cls
classDict = {}
cls_start_packagename = '.'.join(clsname.split('.')[:-1])
# c = Class.forName(clsname)
c = find_javaclass(clsname)
if c is None:
raise Exception('Java class {0} not found'.format(c))
return None
classDict['_class'] = c
constructors = []
for constructor in c.getConstructors():
sig = '({0})V'.format(
''.join([get_signature(x) for x in constructor.getParameterTypes()]))
constructors.append((sig, constructor.isVarArgs()))
classDict['__javaconstructor__'] = constructors
class_hierachy = list(identify_hierarchy(c, 0, not c.isInterface()))
log.debug("autoclass(%s) intf %r hierarchy is %s" % (clsname,c.isInterface(),str(class_hierachy)))
cls_done=set()
cls_methods = defaultdict(list)
cls_fields = {}
# we now walk the hierarchy, from top of the tree, identifying methods
# hopefully we start at java.lang.Object
for cls, level in class_hierachy:
# dont analyse a given class more than once.
# many interfaces can lead to java.lang.Object
if cls in cls_done:
continue
cls_packagename = '.'.join(cls.getName().split('.')[:-1])
cls_done.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]
# collect all methods declared by this class of the hierarchy for later traversal
for index, method in enumerate(methods):
method_modifier = method.getModifiers()
if Modifier.isProtected(method_modifier) and not include_protected:
continue
if Modifier.isPrivate(method_modifier) and not include_private:
continue
if not (Modifier.isPublic(method_modifier) or
Modifier.isProtected(method_modifier) or
Modifier.isPrivate(method_modifier)):
if cls_start_packagename == cls_packagename and not include_protected:
continue
if cls_start_packagename != cls_packagename and not include_private:
continue
name = methods_name[index]
cls_methods[name].append((cls, method, level))
fields = cls.getDeclaredFields()
for field in fields:
field_name = field.getName()
if field_name in cls_fields:
if level < cls_fields[field_name][1]:
cls_fields[field_name] = (field, level, cls_packagename)
else:
cls_fields[field_name] = (field, level, cls_packagename)
# the fields are analyzed before methods so that if a method and a field
# have the same name, the field will take precedence in classDict.
for field_name, (field, _, cls_packagename) in cls_fields.items():
field_modifier = field.getModifiers()
static = Modifier.isStatic(field_modifier)
sig = get_signature(field.getType())
if Modifier.isProtected(field_modifier) and not include_protected:
continue
if Modifier.isPrivate(field_modifier) and not include_private:
continue
if not (Modifier.isPublic(field_modifier) or
Modifier.isProtected(field_modifier) or
Modifier.isPrivate(field_modifier)):
if cls_start_packagename == cls_packagename and not include_protected:
continue
if cls_start_packagename != cls_packagename and not include_private:
continue
cls = JavaStaticField if static else JavaField
classDict[field_name] = cls(sig)
# having collated the methods, identify if there are any with the same name
for name in cls_methods:
if len(cls_methods[name]) == 1:
# uniquely named method
owningCls, method, level = cls_methods[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.isEnabledFor(logging.DEBUG):
log_method(method, name, sig)
classDict[name] = (JavaStaticMethod if static else JavaMethod)(sig, varargs=varargs)
# methods that fit the characteristics of a JavaBean's methods get turned into properties.
# these added properties should not supercede any other methods or fields.
if name != 'getClass' and bean_getter(name) and len(method.getParameterTypes()) == 0:
lowername = lower_name(name[2 if name.startswith('is') else 3:])
if lowername in classDict:
# don't add this to classDict if the property will replace a method or field.
continue
classDict[lowername] = (lambda n: property(lambda self: getattr(self, n)()))(name)
else:
# multiple signatures
signatures = []
log.debug("method %s has %d multiple signatures in hierarchy of cls %s" % (name, len(cls_methods[name]), c))
paramsig_to_level=defaultdict(lambda: float('inf'))
# we now identify if any have the same signature, as we will call the _lowest_ in the hierarchy,
# as reflected in min level
for owningCls, method, level in cls_methods[name]:
param_sig = ''.join([get_signature(x) for x in method.getParameterTypes()])
log.debug("\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 cls_methods[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]:
log.debug("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.isEnabledFor(logging.DEBUG):
log_method(method, name, sig)
signatures.append((sig, Modifier.isStatic(method.getModifiers()), method.isVarArgs()))
log.debug("method selected %d multiple signatures of %s" % (len(signatures), str(signatures)))
classDict[name] = JavaMultipleMethod(signatures)
# check whether any classes in the hierarchy appear in the protocol_map
for cls, _ in class_hierachy:
cls_name = cls.getName()
if cls_name in protocol_map:
for pname, plambda in protocol_map[cls_name].items():
classDict[pname] = plambda
classDict['__javaclass__'] = clsname.replace('.', '/')
return MetaJavaClass.__new__(
MetaJavaClass,
clsname,
(JavaClass, ),
classDict,
classparams=(include_protected, include_private))
def _getitem(self, index):
''' dunder method for List '''
try:
return self.get(index)
except JavaException as e:
# initialize the subclass before getting the Class.forName
# otherwise isInstance does not know of the subclass
mock_exception_object = autoclass(e.classname)()
if find_javaclass("java.lang.IndexOutOfBoundsException").isInstance(mock_exception_object):
# python for...in iteration checks for end of list by waiting for IndexError
raise IndexError()
else:
raise
def _map_getitem(self, k):
''' dunder method for java.util.Map '''
rtr = self.get(k)
if rtr is None:
raise KeyError()
return rtr
def _map_entry_getitem(self, i):
if i == 0:
return self.getKey()
if i == 1:
return self.getValue()
raise IndexError()
def _iterator_next(self):
''' dunder method for java.util.Iterator'''
if not self.hasNext():
raise StopIteration()
return self.next()
# protocol_map is a user-accessible API for patching class instances with additional methods
protocol_map = {
'java.util.Collection' : {
'__len__' : lambda self: self.size(),
'__contains__' : lambda self, item: self.contains(item),
'__delitem__' : lambda self, item: self.remove(item)
},
'java.util.List' : {
'__getitem__' : _getitem
},
'java.util.Map' : {
'__setitem__' : lambda self, k, v : self.put(k,v),
'__getitem__' : _map_getitem,
'__delitem__' : lambda self, item: self.remove(item),
'__len__' : lambda self: self.size(),
'__contains__' : lambda self, item: self.containsKey(item),
'__iter__' : lambda self: self.keySet().iterator()
},
'java.util.Map$Entry' : {
'__getitem__' : _map_entry_getitem,
'__iter__' : lambda self: iter([self.getKey(), self.getValue()]),
'__len__' : lambda self: 2
},
'java.util.Iterator' : {
'__iter__' : lambda self: self,
'__next__' : _iterator_next,
},
'java.lang.Iterable' : {
'__iter__' : lambda self: self.iterator(),
},
# this also addresses java.io.Closeable
'java.lang.AutoCloseable' : {
'__enter__' : lambda self: self,
'__exit__' : lambda self, type, value, traceback: self.close()
},
'java.lang.Comparable' : {
#__cmp__ is for Python 2 support
'__cmp__' : lambda self, other: self.compareTo(other),
'__eq__' : lambda self, other: self.equals(other),
'__ne__' : lambda self, other: not self.equals(other),
'__lt__' : lambda self, other: self.compareTo(other) < 0,
'__gt__' : lambda self, other: self.compareTo(other) > 0,
'__le__' : lambda self, other: self.compareTo(other) <= 0,
'__ge__' : lambda self, other: self.compareTo(other) >= 0,
}
}