# ***************************************************************************** # # 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__