File: _jproxy.py

package info (click to toggle)
python-jpype 1.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,984 kB
  • sloc: python: 18,767; cpp: 17,931; java: 8,448; xml: 1,305; makefile: 154; sh: 35
file content (238 lines) | stat: -rw-r--r-- 8,392 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# *****************************************************************************
#
#   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__