[Jython-checkins] jython (merge default -> default): Merge recent import-related fixes to trunk

jeff.allen jython-checkins at python.org
Sun May 17 10:11:29 CEST 2015


https://hg.python.org/jython/rev/bb6cababa5bd
changeset:   7712:bb6cababa5bd
parent:      7706:bfa13b3a5553
parent:      7711:dccf5d04d58e
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Sun May 17 09:10:22 2015 +0100
summary:
  Merge recent import-related fixes to trunk

files:
  Lib/test/test_classpathimporter.py                |  160 ++++++++-
  Lib/test/test_support.py                          |   97 ++---
  NEWS                                              |    2 +
  src/org/python/core/ClasspathPyImporter.java      |  114 +++++++-
  src/org/python/core/util/importer.java            |   19 +-
  src/org/python/modules/zipimport/zipimporter.java |   10 +-
  6 files changed, 309 insertions(+), 93 deletions(-)


diff --git a/Lib/test/test_classpathimporter.py b/Lib/test/test_classpathimporter.py
--- a/Lib/test/test_classpathimporter.py
+++ b/Lib/test/test_classpathimporter.py
@@ -8,15 +8,15 @@
 from test import test_support
 from java.lang import Thread
 
+import pkgutil
+
 class ClasspathImporterTestCase(unittest.TestCase):
 
     def setUp(self):
         self.orig_context = Thread.currentThread().contextClassLoader
-        self.orig_path = sys.path
 
     def tearDown(self):
         Thread.currentThread().contextClassLoader = self.orig_context
-        sys.path = self.orig_path
 
     # I don't like the checked in jar file bug1239.jar.  The *one* thing I
     # liked about the tests in bugtests/ is that you could create a java file,
@@ -26,74 +26,174 @@
     # with sys.path.append where not getting scanned if they start with a top
     # level package we already have, like the "org" in org.python.*
     def test_bug1239(self):
-        sys.path.append("Lib/test/bug1239.jar")
-        import org.test403javapackage.test403
+        with test_support.DirsOnSysPath("Lib/test/bug1239.jar"):
+            import org.test403javapackage.test403
 
     # different from test_bug1239 in that only a Java package is imported, not
     # a Java class.  I'd also like to get rid of this checked in test jar.
     def test_bug1126(self):
-        sys.path.append("Lib/test/bug1126/bug1126.jar")
-        import org.subpackage
+        with test_support.DirsOnSysPath("Lib/test/bug1126/bug1126.jar"):
+            import org.subpackage
 
 
 class PyclasspathImporterTestCase(unittest.TestCase):
 
+    RESOURCE_DATA = "Always look\non the bright side\r\nof life."
+
     def setUp(self):
         self.orig_context = Thread.currentThread().contextClassLoader
-        self.orig_path = sys.path
         self.temp_dir = tempfile.mkdtemp()
         self.modules = sys.modules.keys()
 
     def tearDown(self):
         Thread.currentThread().contextClassLoader = self.orig_context
-        sys.path = self.orig_path
-        shutil.rmtree(self.temp_dir)
         for module in sys.modules.keys():
             if module not in self.modules:
                 del sys.modules[module]
+        try:
+            shutil.rmtree(self.temp_dir)
+        except OSError:
+            # On Windows at least we cannot delete the open JAR
+            pass
 
-    def setClassLoaderAndCheck(self, orig_jar, prefix, compile_path=''):
-        # Create a new jar and compile prefer_compiled into it
+    def prepareJar(self, orig_jar):
+        # Create a new copy of the checked-in test jar
         orig_jar = test_support.findfile(orig_jar)
         jar = os.path.join(self.temp_dir, os.path.basename(orig_jar))
         shutil.copy(orig_jar, jar)
+        return jar
 
-        code = os.path.join(self.temp_dir, 'prefer_compiled.py')
-        fp = open(code, 'w')
-        fp.write('compiled = True')
-        fp.close()
+    def compileToJar(self, jar, compile_path=''):
+        # Add a compiled version of prefer_compiled.py to the jar
+        source = 'prefer_compiled.py'
+        code = os.path.join(self.temp_dir, source)
+        with open(code, 'w') as fp:
+            fp.write('compiled = True')
+        # Compile that file
         py_compile.compile(code)
-        zip = zipfile.ZipFile(jar, 'a')
-        zip.write(os.path.join(self.temp_dir, 'prefer_compiled$py.class'),
-                  os.path.join(compile_path, 'jar_pkg',
-                               'prefer_compiled$py.class'))
-        zip.close()
+        # Now add the compiled file to the jar
+        compiled = source.replace('.py', '$py.class')
+        with zipfile.ZipFile(jar, 'a') as zip:
+            zip.write(os.path.join(self.temp_dir, compiled),
+                      os.path.join(compile_path, 'jar_pkg', compiled))
+        return compiled
 
-        Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
+    def addResourceToJar(self, jar, package='jar_pkg'):
+        name = 'testdata.dat'
+        with zipfile.ZipFile(jar, 'a') as zip:
+            zip.writestr(package + '/' + name, self.RESOURCE_DATA)
+        return name
+
+    def checkImports(self, prefix, compiled):
         import flat_in_jar
         self.assertEquals(flat_in_jar.value, 7)
         import jar_pkg
         self.assertEquals(prefix + '/jar_pkg/__init__.py', jar_pkg.__file__)
         from jar_pkg import prefer_compiled
-        self.assertEquals(prefix + '/jar_pkg/prefer_compiled$py.class', prefer_compiled.__file__)
+        self.assertEquals(prefix + '/jar_pkg/' + compiled, prefer_compiled.__file__)
         self.assert_(prefer_compiled.compiled)
         self.assertRaises(NameError, __import__, 'flat_bad')
         self.assertRaises(NameError, __import__, 'jar_pkg.bad')
 
-    @unittest.skip("FIXME: worked in Jython 2.5")
     def test_default_pyclasspath(self):
-        self.setClassLoaderAndCheck('classimport.jar', '__pyclasspath__')
+        jar = self.prepareJar('classimport.jar')
+        compiled = self.compileToJar(jar)
+        Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
+        self.checkImports('__pyclasspath__', compiled)
 
-    @unittest.skip("FIXME: worked in Jython 2.5")
     def test_path_in_pyclasspath(self):
-        sys.path = ['__pyclasspath__/Lib']
-        self.setClassLoaderAndCheck('classimport_Lib.jar',
-                                    '__pyclasspath__/Lib', 'Lib')
+        jar = self.prepareJar('classimport_Lib.jar')
+        compiled = self.compileToJar(jar, 'Lib')
+        Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
+        with test_support.DirsOnSysPath():
+            sys.path = ['__pyclasspath__/Lib']
+            self.checkImports('__pyclasspath__/Lib', compiled)
+
+    def test_loader_is_package(self):
+        jar = self.prepareJar('classimport.jar')
+        Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
+        mod_name = 'flat_in_jar'
+        loader = pkgutil.get_loader(mod_name)
+        self.assertFalse(loader.is_package(mod_name))
+        self.assertTrue(loader.is_package('jar_pkg'))
+        self.assertFalse(loader.is_package('jar_pkg.prefer_compiled'))
+
+    def test_loader_get_code(self):
+        # Execute Python code out of the JAR
+        jar = self.prepareJar('classimport.jar')
+        Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
+        loader = pkgutil.get_loader('jar_pkg')
+        space = { 'value':None, 'compiled':None}
+
+        # flat_in_jar contains the assignment value = 7
+        code = loader.get_code('flat_in_jar')
+        exec code in space
+        self.assertEquals(space['value'], 7)
+
+        # jar_pkg.prefer_compiled contains the assignment compiled = False
+        code = loader.get_code('jar_pkg.prefer_compiled')
+        exec code in space
+        self.assertEquals(space['compiled'], False)
+
+        # Compile a new one containing the assignment compiled = True
+        self.compileToJar(jar)
+        code = loader.get_code('jar_pkg.prefer_compiled')
+        exec code in space
+        self.assertEquals(space['compiled'], True)
+
+    def test_pkgutil_get_data(self):
+        # Test loader.get_data used via pkgutil
+        jar = self.prepareJar('classimport.jar')
+        name = self.addResourceToJar(jar)
+        Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
+        data = pkgutil.get_data('jar_pkg', name)
+        self.assertIsInstance(data, bytes)
+        self.assertEqual(data, self.RESOURCE_DATA)
+
+    def test_loader_get_data(self):
+        # Test loader.get_data used via pkgutil.get_loader
+        jar = self.prepareJar('classimport.jar')
+        name = self.addResourceToJar(jar)
+        Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
+        loader = pkgutil.get_loader('jar_pkg')
+        # path is a resource path (not file system path using os.path.sep)
+        path = 'jar_pkg/' + name
+        data = loader.get_data(path)
+        self.assertIsInstance(data, bytes)
+        self.assertEqual(data, self.RESOURCE_DATA)
+
+    def test_importer_get_data(self):
+        # Test loader.get_data used via pkgutil.get_importer
+        jar = self.prepareJar('classimport.jar')
+        name = self.addResourceToJar(jar)
+        Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
+        importer = pkgutil.get_importer('__pyclasspath__/')
+        # path is a resource path (may be file system path using os.path.sep)
+        path = os.path.join('jar_pkg', name)
+        data = importer.get_data(path)
+        self.assertIsInstance(data, bytes)
+        self.assertEqual(data, self.RESOURCE_DATA)
+        # Check works a second time (stream use internal to implementation)
+        data = importer.get_data(path)
+        self.assertEqual(data, self.RESOURCE_DATA)
+
+    def test_importer_get_source(self):
+        # Test loader.get_source used via pkgutil.get_importer
+        jar = self.prepareJar('classimport.jar')
+        Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
+        importer = pkgutil.get_importer('__pyclasspath__/')
+        # In package
+        mod = 'jar_pkg.prefer_compiled'
+        source = importer.get_source(mod)
+        self.assertIsInstance(source, bytes)
+        self.assertEqual(source, 'compiled = False\n')
 
 
 def test_main():
-    test_support.run_unittest(ClasspathImporterTestCase,
-                              PyclasspathImporterTestCase)
+    test_support.run_unittest(
+                    ClasspathImporterTestCase,
+                    PyclasspathImporterTestCase
+    )
 
 
 if __name__ == '__main__':
diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py
--- a/Lib/test/test_support.py
+++ b/Lib/test/test_support.py
@@ -31,7 +31,6 @@
 except ImportError:
     thread = None
 
-
 __all__ = ["Error", "TestFailed", "ResourceDenied", "import_module",
            "verbose", "use_resources", "max_memuse", "record_original_stdout",
            "get_original_stdout", "unload", "unlink", "rmtree", "forget",
@@ -58,7 +57,7 @@
     """Test skipped because it requested a disallowed resource.
 
     This is raised when a test calls requires() for a resource that
-    has not be enabled.  It is used to distinguish between expected
+    has not been enabled.  It is used to distinguish between expected
     and unexpected skips.
     """
 
@@ -220,7 +219,6 @@
             # Increase the timeout and try again
             time.sleep(timeout)
             timeout *= 2
-        print "Still cannot delete", pathname
         warnings.warn('tests may fail, delete still pending for ' + pathname,
                       RuntimeWarning, stacklevel=4)
 
@@ -291,7 +289,7 @@
     possibility of False being returned occurs when regrtest.py is executing."""
     # see if the caller's module is __main__ - if so, treat as if
     # the resource was set
-    if sys._getframe().f_back.f_globals.get("__name__") == "__main__":
+    if sys._getframe(1).f_globals.get("__name__") == "__main__":
         return
     if not is_resource_enabled(resource):
         if msg is None:
@@ -469,30 +467,30 @@
             # 2 latin characters.
             TESTFN_UNICODE = unicode("@test-\xe0\xf2", "latin-1")
         TESTFN_ENCODING = sys.getfilesystemencoding()
-        # TESTFN_UNICODE_UNENCODEABLE is a filename that should *not* be
+        # TESTFN_UNENCODABLE is a filename that should *not* be
         # able to be encoded by *either* the default or filesystem encoding.
         # This test really only makes sense on Windows NT platforms
         # which have special Unicode support in posixmodule.
         if (not hasattr(sys, "getwindowsversion") or
                 sys.getwindowsversion()[3] < 2): #  0=win32s or 1=9x/ME
-            TESTFN_UNICODE_UNENCODEABLE = None
+            TESTFN_UNENCODABLE = None
         else:
             # Japanese characters (I think - from bug 846133)
-            TESTFN_UNICODE_UNENCODEABLE = eval('u"@test-\u5171\u6709\u3055\u308c\u308b"')
+            TESTFN_UNENCODABLE = eval('u"@test-\u5171\u6709\u3055\u308c\u308b"')
             try:
                 # XXX - Note - should be using TESTFN_ENCODING here - but for
                 # Windows, "mbcs" currently always operates as if in
                 # errors=ignore' mode - hence we get '?' characters rather than
                 # the exception.  'Latin1' operates as we expect - ie, fails.
                 # See [ 850997 ] mbcs encoding ignores errors
-                TESTFN_UNICODE_UNENCODEABLE.encode("Latin1")
+                TESTFN_UNENCODABLE.encode("Latin1")
             except UnicodeEncodeError:
                 pass
             else:
                 print \
                 'WARNING: The filename %r CAN be encoded by the filesystem.  ' \
                 'Unicode filename tests may not be effective' \
-                % TESTFN_UNICODE_UNENCODEABLE
+                % TESTFN_UNENCODABLE
 
 # Make sure we can write to TESTFN, try in /tmp if we can't
 fp = None
@@ -556,7 +554,6 @@
             rmtree(name)
 
 
-
 def findfile(file, here=__file__, subdir=None):
     """Try to find a file on sys.path and the working directory.  If it is not
     found the argument passed to the function is returned (this does not
@@ -572,30 +569,6 @@
         if os.path.exists(fn): return fn
     return file
 
-def verify(condition, reason='test failed'):
-    """Verify that condition is true. If not, raise TestFailed.
-
-       The optional argument reason can be given to provide
-       a better error text.
-    """
-
-    if not condition:
-        raise TestFailed(reason)
-
-def vereq(a, b):
-    """Raise TestFailed if a == b is false.
-
-    This is better than verify(a == b) because, in case of failure, the
-    error message incorporates repr(a) and repr(b) so you can see the
-    inputs.
-
-    Note that "not (a == b)" isn't necessarily the same as "a != b"; the
-    former is tested.
-    """
-
-    if not (a == b):
-        raise TestFailed("%r == %r" % (a, b))
-
 def sortdict(dict):
     "Like repr(dict), but in sorted order."
     items = dict.items()
@@ -617,12 +590,8 @@
         unlink(TESTFN)
 
 def check_syntax_error(testcase, statement):
-    try:
-        compile(statement, '<test string>', 'exec')
-    except SyntaxError:
-        pass
-    else:
-        testcase.fail('Missing SyntaxError: "%s"' % statement)
+    testcase.assertRaises(SyntaxError, compile, statement,
+                          '<test string>', 'exec')
 
 def open_urlresource(url, check=None):
     import urlparse, urllib2
@@ -784,7 +753,7 @@
     Use like this:
 
         with CleanImport("foo"):
-            __import__("foo") # new reference
+            importlib.import_module("foo") # new reference
     """
 
     def __init__(self, *module_names):
@@ -854,6 +823,31 @@
         os.environ = self._environ
 
 
+class DirsOnSysPath(object):
+    """Context manager to temporarily add directories to sys.path.
+
+    This makes a copy of sys.path, appends any directories given
+    as positional arguments, then reverts sys.path to the copied
+    settings when the context ends.
+
+    Note that *all* sys.path modifications in the body of the
+    context manager, including replacement of the object,
+    will be reverted at the end of the block.
+    """
+
+    def __init__(self, *paths):
+        self.original_value = sys.path[:]
+        self.original_object = sys.path
+        sys.path.extend(paths)
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *ignore_exc):
+        sys.path = self.original_object
+        sys.path[:] = self.original_value
+
+
 class TransientResource(object):
 
     """Raise ResourceDenied if an exception is raised while the context manager
@@ -946,17 +940,10 @@
         socket.setdefaulttimeout(old_timeout)
 
 
-
 @contextlib.contextmanager
 def captured_output(stream_name):
-    """Run the 'with' statement body using a StringIO object in place of a
-    specific attribute on the sys module.
-    Example use (with 'stream_name=stdout')::
-
-       with captured_stdout() as s:
-           print "hello"
-       assert s.getvalue() == "hello"
-    """
+    """Return a context manager used by captured_stdout and captured_stdin
+    that temporarily replaces the sys stream *stream_name* with a StringIO."""
     import StringIO
     orig_stdout = getattr(sys, stream_name)
     setattr(sys, stream_name, StringIO.StringIO())
@@ -966,6 +953,12 @@
         setattr(sys, stream_name, orig_stdout)
 
 def captured_stdout():
+    """Capture the output of sys.stdout:
+
+       with captured_stdout() as s:
+           print "hello"
+       self.assertEqual(s.getvalue(), "hello")
+    """
     return captured_output("stdout")
 
 def captured_stderr():
@@ -1108,7 +1101,7 @@
                 # to make sure they work. We still want to avoid using
                 # too much memory, though, but we do that noisily.
                 maxsize = 5147
-                self.failIf(maxsize * memuse + overhead > 20 * _1M)
+                self.assertFalse(maxsize * memuse + overhead > 20 * _1M)
             else:
                 maxsize = int((max_memuse - overhead) / memuse)
                 if maxsize < minsize:
@@ -1168,7 +1161,6 @@
         test(result)
         return result
 
-
 def _id(obj):
     return obj
 
@@ -1219,7 +1211,6 @@
 
 
 
-
 def _run_suite(suite):
     """Run tests from a unittest.TestSuite-derived class."""
     if not junit_xml_dir:
diff --git a/NEWS b/NEWS
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,8 @@
    - [ 2310 ] test_import runs on Windows (and passes with 4 skips).
    - [ 2347 ] failures in test_import_pep328 when run with -m
    - [ 2158, 2259 ] Fixed behaviour of relative from ... import *
+   - [ 1879 ] -m command now executes scripts from inside a jar file 
+   - [ 2058 ] ClasspathPyImporter implements PEP 302 get_data (and others)
 
 Jython 2.7rc3
   Bugs fixed
diff --git a/src/org/python/core/ClasspathPyImporter.java b/src/org/python/core/ClasspathPyImporter.java
--- a/src/org/python/core/ClasspathPyImporter.java
+++ b/src/org/python/core/ClasspathPyImporter.java
@@ -1,10 +1,13 @@
 /* Copyright (c) Jython Developers */
 package org.python.core;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Map;
 
+import org.python.core.util.FileUtil;
+import org.python.core.util.StringUtil;
 import org.python.core.util.importer;
 import org.python.expose.ExposedMethod;
 import org.python.expose.ExposedNew;
@@ -41,6 +44,83 @@
     }
 
     /**
+     * Return the contents of the jarred file at the specified path
+     * as bytes.
+     *
+     * @param path a String path name within the archive
+     * @return a String of data in binary mode (no CRLF)
+     */
+    @Override
+    public String get_data(String path) {
+        return ClasspathPyImporter_get_data(path);
+    }
+
+    @ExposedMethod
+    final String ClasspathPyImporter_get_data(String path) {
+        // Strip any leading occurrence of the hook string
+        int len = PYCLASSPATH_PREFIX.length();
+        if (len < path.length() && path.startsWith(PYCLASSPATH_PREFIX)) {
+            path = path.substring(len);
+        }
+
+        // Bundle wraps the stream together with a close operation
+        try (Bundle bundle = makeBundle(path, makeEntry(path))) {
+            byte[] data = FileUtil.readBytes(bundle.inputStream);
+            return StringUtil.fromBytes(data);
+        } catch (IOException ioe) {
+            throw Py.IOError(ioe);
+        }
+    }
+
+    /**
+     * Return the source code for the module as a string (using
+     * newline characters for line endings)
+     *
+     * @param fullname the fully qualified name of the module
+     * @return a String of the module's source code or null
+     */
+    public String get_source(String fullname) {
+        return ClasspathPyImporter_get_source(fullname);
+    }
+
+    @ExposedMethod
+    final String ClasspathPyImporter_get_source(String fullname) {
+
+        ModuleInfo moduleInfo = getModuleInfo(fullname);
+
+        if (moduleInfo == ModuleInfo.ERROR) {
+            return null;
+
+        } else if (moduleInfo == ModuleInfo.NOT_FOUND) {
+            throw Py.ImportError(String.format("can't find module '%s'", fullname));
+
+        } else {
+            // Turn the module name into a source file name
+            String path = makeFilename(fullname);
+            if (moduleInfo == ModuleInfo.PACKAGE) {
+                path += File.separator + "__init__.py";
+            } else {
+                path += ".py";
+            }
+
+            // Bundle wraps the stream together with a close operation
+            try (Bundle bundle = makeBundle(path, makeEntry(path))) {
+                InputStream is = bundle.inputStream;
+                if (is != null) {
+                    byte[] data = FileUtil.readBytes(is);
+                    return StringUtil.fromBytes(data);
+                } else {
+                    // we have the module, but no source
+                    return null;
+                }
+            } catch (IOException ioe) {
+                throw Py.IOError(ioe);
+            }
+        }
+
+    }
+
+    /**
      * Find the module for the fully qualified name.
      *
      * @param fullname the fully qualified name of the module
@@ -54,6 +134,32 @@
     }
 
     /**
+     * Determine whether a module is a package.
+     *
+     * @param fullname the fully qualified name of the module
+     * @return whether the module is a package
+     */
+    @ExposedMethod
+    final boolean ClasspathPyImporter_is_package(String fullname) {
+        return importer_is_package(fullname);
+    }
+
+    /**
+     * Return the code object associated with the module.
+     *
+     * @param fullname the fully qualified name of the module
+     * @return the module's PyCode object or None
+     */
+    @ExposedMethod
+    final PyObject ClasspathPyImporter_get_code(String fullname) {
+        ModuleCodeData moduleCodeData = getModuleCode(fullname);
+        if (moduleCodeData != null) {
+            return moduleCodeData.code;
+        }
+        return Py.None;
+    }
+
+    /**
      * Load a module for the fully qualified name.
      *
      * @param fullname the fully qualified name of the module
@@ -87,14 +193,18 @@
 
     @Override
     protected String makeEntry(String filename) {
+        // In some contexts, the resource string arrives as from os.path.join(*parts)
+        if (!getSeparator().equals(File.separator)) {
+            filename = filename.replace(File.separator, getSeparator());
+        }
         if (entries.containsKey(filename)) {
             return filename;
         }
         InputStream is;
         if (Py.getSystemState().getClassLoader() != null) {
-        	is = tryClassLoader(filename, Py.getSystemState().getClassLoader(), "sys");
+            is = tryClassLoader(filename, Py.getSystemState().getClassLoader(), "sys");
         } else {
-        	is = tryClassLoader(filename, imp.getParentClassLoader(), "parent");
+            is = tryClassLoader(filename, imp.getParentClassLoader(), "parent");
         }
         if (is != null) {
             entries.put(filename, is);
diff --git a/src/org/python/core/util/importer.java b/src/org/python/core/util/importer.java
--- a/src/org/python/core/util/importer.java
+++ b/src/org/python/core/util/importer.java
@@ -4,6 +4,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.EnumSet;
+
 import org.python.core.BytecodeLoader;
 import org.python.core.Py;
 import org.python.core.PyCode;
@@ -40,6 +41,11 @@
     }
 
     /**
+     * Return the bytes for the data located at <code>path</code>.
+     */
+    public abstract String get_data(String path);
+
+    /**
      * Returns the separator between directories and files used by this type of importer.
      */
     protected abstract String getSeparator();
@@ -112,10 +118,20 @@
     }
 
     /**
+     * @param fullname
+     *            the fully qualified name of the module
+     * @return whether the module is a package
+     */
+    protected final boolean importer_is_package(String fullname) {
+        ModuleInfo info = getModuleInfo(fullname);
+        return info == ModuleInfo.PACKAGE;
+    }
+
+    /**
      * Bundle is an InputStream, bundled together with a method that can close the input stream and
      * whatever resources are associated with it when the resource is imported.
      */
-    protected abstract static class Bundle {
+    protected abstract static class Bundle implements AutoCloseable {
         public InputStream inputStream;
 
         public Bundle(InputStream inputStream) {
@@ -125,6 +141,7 @@
         /**
          * Close the underlying resource if necessary. Raises an IOError if a problem occurs.
          */
+        @Override
         public abstract void close();
     }
 
diff --git a/src/org/python/modules/zipimport/zipimporter.java b/src/org/python/modules/zipimport/zipimporter.java
--- a/src/org/python/modules/zipimport/zipimporter.java
+++ b/src/org/python/modules/zipimport/zipimporter.java
@@ -165,11 +165,12 @@
 
     /**
      * Return the uncompressed data for the file at the specified path
-     * as a String.
+     * as bytes.
      *
      * @param path a String path name within the archive
      * @return a String of data in binary mode (no CRLF)
      */
+    @Override
     public String get_data(String path) {
         return zipimporter_get_data(path);
     }
@@ -280,12 +281,7 @@
         }
 
         PyObject tocEntry = files.__finditem__(path);
-        if (tocEntry != null) {
-            return get_data(path);
-        }
-
-        // we have the module, but no source
-        return null;
+        return (tocEntry == null) ? null : get_data(path);
     }
 
     /**

-- 
Repository URL: https://hg.python.org/jython


More information about the Jython-checkins mailing list