239 lines
8.2 KiB
Python
239 lines
8.2 KiB
Python
|
# *****************************************************************************
|
||
|
#
|
||
|
# 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.
|
||
|
#
|
||
|
# See NOTICE file for details.
|
||
|
#
|
||
|
# *****************************************************************************
|
||
|
import _jpype
|
||
|
|
||
|
__all__ = ["JProxy", "JImplements"]
|
||
|
|
||
|
|
||
|
# FIXME the java.lang.method we are overriding should be passes to the lookup function
|
||
|
# so we can properly handle name mangling on the override.
|
||
|
|
||
|
def _checkInterfaceOverrides(interfaces, overrides):
|
||
|
# Verify all methods are overriden
|
||
|
for interface in interfaces:
|
||
|
for method in interface.class_.getMethods():
|
||
|
if method.getModifiers() & 1024 == 0:
|
||
|
continue
|
||
|
if not str(method.getName()) in overrides:
|
||
|
raise NotImplementedError("Interface '%s' requires method '%s' to be implemented." % (
|
||
|
interface.class_.getName(), method.getName()))
|
||
|
|
||
|
|
||
|
def _classOverrides(cls):
|
||
|
# Find all class defined overrides
|
||
|
overrides = {}
|
||
|
for k, v in cls.__dict__.items():
|
||
|
try:
|
||
|
attr = object.__getattribute__(v, "__joverride__")
|
||
|
overrides[k] = (v, attr)
|
||
|
except AttributeError:
|
||
|
pass
|
||
|
return overrides
|
||
|
|
||
|
|
||
|
def _prepareInterfaces(cls, intf):
|
||
|
# Convert the interfaces list
|
||
|
actualIntf = _convertInterfaces(intf)
|
||
|
overrides = _classOverrides(cls)
|
||
|
_checkInterfaceOverrides(actualIntf, overrides)
|
||
|
return actualIntf
|
||
|
|
||
|
|
||
|
def _createJProxyDeferred(cls, *intf):
|
||
|
""" (internal) Create a proxy from a Python class with
|
||
|
@JOverride notation on methods evaluated at first
|
||
|
instantiation.
|
||
|
"""
|
||
|
if not isinstance(cls, type):
|
||
|
raise TypeError("JImplements only applies to types, not %s" % (type(cls)))
|
||
|
|
||
|
def new(tp, *args, **kwargs):
|
||
|
# Attach a __jpype_interfaces__ attribute to this class if
|
||
|
# one doesn't already exist.
|
||
|
actualIntf = getattr(tp, "__jpype_interfaces__", None)
|
||
|
if actualIntf is None:
|
||
|
actualIntf = _prepareInterfaces(cls, intf)
|
||
|
tp.__jpype_interfaces__ = actualIntf
|
||
|
return _jpype._JProxy.__new__(tp, None, actualIntf)
|
||
|
|
||
|
members = {'__new__': new}
|
||
|
# Return the augmented class
|
||
|
return type("proxy.%s" % cls.__name__, (cls, _jpype._JProxy), members)
|
||
|
|
||
|
|
||
|
def _createJProxy(cls, *intf):
|
||
|
""" (internal) Create a proxy from a Python class with
|
||
|
@JOverride notation on methods evaluated at declaration.
|
||
|
"""
|
||
|
if not isinstance(cls, type):
|
||
|
raise TypeError("JImplements only applies to types, not %s" % (type(cls)))
|
||
|
|
||
|
actualIntf = _prepareInterfaces(cls, intf)
|
||
|
|
||
|
def new(tp, *args, **kwargs):
|
||
|
self = _jpype._JProxy.__new__(tp, None, actualIntf)
|
||
|
tp.__init__(self, *args, **kwargs)
|
||
|
return self
|
||
|
|
||
|
members = {'__new__': new}
|
||
|
# Return the augmented class
|
||
|
return type("proxy.%s" % cls.__name__, (cls, _jpype._JProxy), members)
|
||
|
|
||
|
|
||
|
def JImplements(*interfaces, deferred=False, **kwargs):
|
||
|
""" Annotation for creating a new proxy that implements one or more
|
||
|
Java interfaces.
|
||
|
|
||
|
This annotation is placed on an ordinary Python class. The annotation
|
||
|
requires a list of interfaces. It must implement all of the java
|
||
|
methods for each of the interfaces. Each implemented method
|
||
|
should have a @JOverride annotation. The JVM must be running in
|
||
|
order to validate the class.
|
||
|
|
||
|
Args:
|
||
|
interfaces (str*,JClass*): Strings or JClasses for each Java interface
|
||
|
this proxy is to implement.
|
||
|
|
||
|
Kwargs:
|
||
|
deferred (bool):
|
||
|
Whether to defer validation of the interfaces and overrides until
|
||
|
the first instance instantiation (True) or validate at declaration
|
||
|
(False). Deferred validation allows a proxy class to be declared prior
|
||
|
to starting the JVM. Validation only occurs once per proxy class,
|
||
|
thus there is no performance penalty. Default False.
|
||
|
|
||
|
Example:
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
@JImplement("java.lang.Runnable")
|
||
|
class MyImpl(object):
|
||
|
@JOverride
|
||
|
def run(self, arg):
|
||
|
pass
|
||
|
|
||
|
@JImplement("org.my.Interface1", "org.my.Interface2")
|
||
|
class MyImpl(object):
|
||
|
@JOverride
|
||
|
def method(self, arg):
|
||
|
pass
|
||
|
|
||
|
"""
|
||
|
if deferred:
|
||
|
def JProxyCreator(cls):
|
||
|
return _createJProxyDeferred(cls, *interfaces, **kwargs)
|
||
|
else:
|
||
|
def JProxyCreator(cls):
|
||
|
return _createJProxy(cls, *interfaces, **kwargs)
|
||
|
return JProxyCreator
|
||
|
|
||
|
|
||
|
def _convertInterfaces(intf):
|
||
|
""" (internal) Convert a list of interface names into
|
||
|
a list of interfaces suitable for a proxy.
|
||
|
"""
|
||
|
# Flatten the list
|
||
|
intflist = []
|
||
|
for item in intf:
|
||
|
if isinstance(item, str) or not hasattr(item, '__iter__'):
|
||
|
intflist.append(item)
|
||
|
else:
|
||
|
intflist.extend(item)
|
||
|
|
||
|
# Look up the classes if given as a string
|
||
|
actualIntf = set()
|
||
|
for item in intflist:
|
||
|
if isinstance(item, str):
|
||
|
actualIntf.add(_jpype.JClass(item))
|
||
|
else:
|
||
|
actualIntf.add(item)
|
||
|
|
||
|
# Check that all are interfaces
|
||
|
if not actualIntf:
|
||
|
raise TypeError("At least one Java interface must be specified")
|
||
|
|
||
|
for cls in actualIntf:
|
||
|
# If it isn't a JClass, then it cannot be a Java interface
|
||
|
if not isinstance(cls, _jpype.JClass):
|
||
|
raise TypeError("'%s' is not a Java interface" %
|
||
|
type(cls).__name__)
|
||
|
# Java concrete and abstract classes cannot be proxied
|
||
|
if not issubclass(cls, _jpype.JInterface):
|
||
|
raise TypeError("'%s' is not a Java interface" % cls.__name__)
|
||
|
|
||
|
return tuple(actualIntf)
|
||
|
|
||
|
|
||
|
class _JFromDict(object):
|
||
|
def __init__(self, dict):
|
||
|
self.dict = dict
|
||
|
|
||
|
def __getattribute__(self, name):
|
||
|
try:
|
||
|
return object.__getattribute__(self, 'dict')[name]
|
||
|
except KeyError:
|
||
|
pass
|
||
|
raise AttributeError("attribute not found")
|
||
|
|
||
|
|
||
|
class JProxy(_jpype._JProxy):
|
||
|
""" Define a proxy for a Java interface.
|
||
|
|
||
|
This is an older style JPype proxy interface that uses either a
|
||
|
dictionary or an object instance to implement methods defined
|
||
|
in java. The python object can be held by java and its lifespan
|
||
|
will continue as long as java holds a reference to the object
|
||
|
instance. New code should use ``@JImplements`` annotation as
|
||
|
it will support improved type safety and error handling.
|
||
|
|
||
|
Name lookups can either made using a dictionary or an object
|
||
|
instance. One of these two options must be specified.
|
||
|
|
||
|
Args:
|
||
|
intf: either a single interface or a list of java interfaces.
|
||
|
The interfaces can either be defined by strings or
|
||
|
JClass instance. Only interfaces may be used in a
|
||
|
proxy,
|
||
|
dict (dict[string, callable], optional): specifies a dictionary
|
||
|
containing the methods to be called when executing the
|
||
|
java interface methods.
|
||
|
inst (object, optional): specifies an object with methods
|
||
|
whose names matches the java interfaces methods.
|
||
|
"""
|
||
|
def __new__(cls, intf, dict=None, inst=None, convert=False):
|
||
|
# Convert the interfaces
|
||
|
actualIntf = _convertInterfaces([intf])
|
||
|
|
||
|
# Verify that one of the options has been selected
|
||
|
if dict is not None and inst is not None:
|
||
|
raise TypeError("Specify only one of dict and inst")
|
||
|
|
||
|
if dict is not None:
|
||
|
return _jpype._JProxy(_JFromDict(dict), actualIntf, convert)
|
||
|
|
||
|
if inst is not None:
|
||
|
return _jpype._JProxy.__new__(cls, inst, actualIntf, convert)
|
||
|
|
||
|
raise TypeError("a dict or inst must be specified")
|
||
|
|
||
|
@staticmethod
|
||
|
def unwrap(obj):
|
||
|
if not isinstance(obj, _jpype._JProxy):
|
||
|
return obj
|
||
|
return obj.__javainst__
|