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 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
|
# *****************************************************************************
#
# 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.
#
# *****************************************************************************
"""
JPype Imports Module
--------------------
Once imported this module will place the standard Top Level Domains (TLD) into
the Python scope. These TLDs are ``java``, ``com``, ``org``, ``gov``, ``mil``,
``net`` and ``edu``. Java symbols from these domains can be imported using the
standard Python syntax.
Forms supported:
- **import <java_pkg> [ as <name> ]**
- **import <java_pkg>.<java_class> [ as <name> ]**
- **from <java_pkg> import <java_class>[,<java_class>*]**
- **from <java_pkg> import <java_class> [ as <name> ]**
- **from <java_pkg>.<java_class> import <java_static> [ as <name> ]**
- **from <java_pkg>.<java_class> import <java_inner> [ as <name> ]**
For further information please read the :doc:`imports` guide.
Example:
.. code-block:: python
import jpype
import jpype.imports
jpype.startJVM()
# Import java packages as modules
from java.lang import String
"""
import sys
import _jpype
from importlib.machinery import ModuleSpec as _ModuleSpec
from . import _pykeywords
__all__ = ["registerImportCustomizer", "registerDomain", "JImportCustomizer"]
# %% Utility
def _keywordUnwrap(name):
if not name.endswith('_'):
return name
if name[:-1] in _pykeywords._KEYWORDS:
return name[:-1]
return name
def _keywordWrap(name):
if name in _pykeywords._KEYWORDS:
return name + "_"
return name
# %% Customizer
_CUSTOMIZERS = []
def _JExceptionHandler(pkg, name, ex):
javaname = str(pkg) + "." + name
exname = type(ex).__name__
ex._expandStacktrace()
if exname == "java.lang.ExceptionInInitializerError":
raise ImportError(
"Unable to import '%s' due to initializer error" % javaname) from ex
if exname == "java.lang.UnsupportedClassVersionError":
raise ImportError(
"Unable to import '%s' due to incorrect Java version" % javaname) from ex
if exname == "java.lang.NoClassDefFoundError":
missing = str(ex).replace('/', '.')
raise ImportError("Unable to import '%s' due to missing dependency '%s'" % (
javaname, missing)) from ex
raise ImportError("Unable to import '%s'" % javaname) from ex
def registerImportCustomizer(customizer):
""" Import customizers can be used to import python packages
into java modules automatically.
"""
_CUSTOMIZERS.append(customizer)
# Support hook for placing other things into the java tree
class JImportCustomizer(object):
""" Base class for Import customizer.
Import customizers should implement canCustomize and getSpec.
Example:
.. code-block:: python
# Site packages for each java package are stored under $DEVEL/<java_pkg>/py
class SiteCustomizer(jpype.imports.JImportCustomizer):
def canCustomize(self, name):
if name.startswith('org.mysite') and name.endswith('.py'):
return True
return False
def getSpec(self, name):
pname = name[:-3]
devel = os.environ.get('DEVEL')
path = os.path.join(devel, pname,'py','__init__.py')
return importlib.util.spec_from_file_location(name, path)
"""
def canCustomize(self, name):
""" Determine if this path is to be treated differently
Return:
True if an alternative spec is required.
"""
return False
def getSpec(self, name):
""" Get the module spec for this module.
"""
raise NotImplementedError
# %% Finder
def unwrap(name):
# Deal with Python keywords in the Java path
if not '_' in name:
return name
return ".".join([_keywordUnwrap(i) for i in name.split('.')])
class _JImportLoader:
""" (internal) Finder hook for importlib. """
def find_spec(self, name, path, target=None):
# If jvm is not started then we just check against the TLDs
if not _jpype.isStarted():
base = name.partition('.')[0]
if not base in _JDOMAINS:
return None
raise ImportError(
"Attempt to create Java package '%s' without jvm" % name)
# Check for aliases
if name in _JDOMAINS:
jname = _JDOMAINS[name]
if not _jpype.isPackage(jname):
raise ImportError(
"Java package '%s' not found, requested by alias '%s'" % (jname, name))
ms = _ModuleSpec(name, self)
ms._jname = jname
return ms
# Check if it is a TLD
parts = name.rpartition('.')
# Use the parent module to simplify name mangling
if not parts[1] and _jpype.isPackage(parts[2]):
ms = _ModuleSpec(name, self)
ms._jname = name
return ms
if not parts[1] and not _jpype.isPackage(parts[0]):
return None
base = sys.modules.get(parts[0], None)
if not base or not isinstance(base, _jpype._JPackage):
return None
# Support for external modules in java tree
name = unwrap(name)
for customizer in _CUSTOMIZERS:
if customizer.canCustomize(name):
return customizer.getSpec(name)
# Using isPackage eliminates need for registering tlds
if not hasattr(base, parts[2]):
# If the base is a Java package and it wasn't found in the
# package using getAttr, then we need to emit an error
# so we produce a meaningful diagnositic.
try:
# Use forname because it give better diagnostics
cls = _jpype._java_lang_Class.forName(
name, True, _jpype.JPypeClassLoader)
# This code only is hit if an error was not thrown
if cls.getModifiers() & 1 == 0:
raise ImportError("Class `%s` is not public" % name)
raise ImportError(
"Class `%s` was found but was not expected" % name)
# Not found is acceptable
except Exception as ex:
raise ImportError("Failed to import '%s'" % name) from ex
# Import the java module
return _ModuleSpec(name, self)
""" (internal) Loader hook for importlib. """
def create_module(self, spec):
if spec.parent == "":
return _jpype._JPackage(spec._jname)
parts = spec.name.rsplit('.', 1)
rc = getattr(sys.modules[spec.parent], parts[1])
# Install the handler
rc._handler = _JExceptionHandler
return rc
def exec_module(self, fullname):
pass
# Install hooks into python importlib
sys.meta_path.append(_JImportLoader())
# %% Domains
_JDOMAINS = {}
def registerDomain(mod, alias=None):
""" Add a Java domain to Python as a dynamic module.
This can be used to bind a Java path to a Python path.
Args:
mod(str): Is the Python module to bind to Java.
alias(str, optional): Is the name of the Java path if different
than the Python name.
"""
if not alias:
alias = mod
_JDOMAINS[mod] = alias
# Preregister common top level domains
registerDomain('com')
registerDomain('gov')
registerDomain('java')
registerDomain('org')
registerDomain('mil')
registerDomain('edu')
registerDomain('net')
|