From jython-checkins at python.org Sat Nov 3 08:48:41 2018 From: jython-checkins at python.org (jeff.allen) Date: Sat, 03 Nov 2018 12:48:41 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_test=5Fchdir_tolerates_abs?= =?utf-8?q?ent_DOS_8=2E3_filename=2E_Fixes_=232709?= Message-ID: <20181103124841.1.634A3CB989C84345@mg.python.org> https://hg.python.org/jython/rev/3dbf6da78abf changeset: 8193:3dbf6da78abf user: Adam Burke date: Sat Nov 03 11:42:49 2018 +0000 summary: test_chdir tolerates absent DOS 8.3 filename. Fixes #2709 Some Windows filesystems may be configured not to provide a DOS shortname (8dot3name), or it may have been removed from the directory. This change ensures that test_chdir does not fail for this reason. See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file#short-vs-long-names files: Lib/test/shortname.bat | 4 +++ Lib/test/test_chdir.py | 35 +++++++++++++++++++++++++++++- NEWS | 1 + 3 files changed, 39 insertions(+), 1 deletions(-) diff --git a/Lib/test/shortname.bat b/Lib/test/shortname.bat new file mode 100644 --- /dev/null +++ b/Lib/test/shortname.bat @@ -0,0 +1,4 @@ + at ECHO OFF +REM Supports windows-specific shortname tests in test_chdir.py +echo %~s1 + diff --git a/Lib/test/test_chdir.py b/Lib/test/test_chdir.py --- a/Lib/test/test_chdir.py +++ b/Lib/test/test_chdir.py @@ -185,14 +185,47 @@ def setUp(self): super(WindowsChdirTestCase, self).setUp() - self.subdir = os.path.join(self.dir1, 'Program Files') + self.windowsTestDir = 'Program Files' + self.subdir = os.path.join(self.dir1, self.windowsTestDir) os.makedirs(self.subdir) + def shortname(self,path): + # From later versions of Windows (post-Vista), not all files and + # directories have short names + # This is set at the filesystem level and seems intended to phase + # out short (DOS) names + # https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file + # shortname.bat returns the short name if available, else the full name + shortnameLoc = test_support.findfile('shortname.bat') + output = subprocess.check_output(['cmd','/c',shortnameLoc,path]) + return output.strip() + def test_windows_chdir_dos_path(self): + output = self.shortname(self.subdir) + if output.strip().endswith(self.windowsTestDir): + self.skipTest('no dos path to test on this filesystem') dos_name = os.path.join(self.dir1, 'progra~1') os.chdir(dos_name) self.assertEqual(os.getcwd(), os.path.realpath(dos_name)) + def test_windows_chdir_dos_path_program_files(self): + # Prove that we can navigate to a commonly existing system directory + # with a shortname alias. Program Files commonly has 8dot3 (short) alias + # for script back-compatibility. Unlike other tests in the class, don't + # create a temp directory + pfileName = os.environ['PROGRAMFILES'] + shortPfileName = self.shortname( pfileName ) + if not os.path.exists(shortPfileName): + self.skipTest('Windows PROGRAMFILES short directory not found on this system') + if pfileName == shortPfileName: + self.skipTest('Windows system with PROGRAMFILES on non 8dot3 filesystem') + cwd = os.getcwd() + try: + os.chdir(pfileName) + self.assertEqual(os.getcwd(), os.path.realpath(pfileName)) + finally: + os.chdir(cwd) + def test_windows_getcwd_ensures_drive_letter(self): # subdir is in the TEMP directory, usually on C:, while the # current working directory could be (for the sake of comments) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ Development tip Bugs fixed + - [ 2709 ] test_chdir tolerates absent DOS 8.3 filename - [ 2706 ] Use python.path instead of JYTHONPATH - [ 2410 ] Regression in PySystemStateTest (leading slash) - [ 2639 ] Incorrect result when using != comparison against Java {List, Set, Map} -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Sun Nov 4 06:30:16 2018 From: jython-checkins at python.org (jeff.allen) Date: Sun, 04 Nov 2018 11:30:16 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Improve_launcher_resilienc?= =?utf-8?q?e_to_bad_environment_variables=2E_Fixes_=232346=2E?= Message-ID: <20181104113016.1.034E2CF0D991FB66@mg.python.org> https://hg.python.org/jython/rev/6b10ade8331e changeset: 8194:6b10ade8331e user: Jeff Allen date: Sun Nov 04 07:20:18 2018 +0000 summary: Improve launcher resilience to bad environment variables. Fixes #2346. Latest in a series of tweaks to the launcher jython.py and its .exe version, here primarily to improve the error message seen when PATH or JAVA_HOME do not allow it to find the Java command. files: NEWS | 1 + src/shell/jython.exe | Bin src/shell/jython.py | 50 +++++++++++++++++++----------- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ Development tip Bugs fixed + - [ 2346 ] Launcher not resilient to bad environment variable settings - [ 2709 ] test_chdir tolerates absent DOS 8.3 filename - [ 2706 ] Use python.path instead of JYTHONPATH - [ 2410 ] Regression in PySystemStateTest (leading slash) diff --git a/src/shell/jython.exe b/src/shell/jython.exe index 0117670e8db2e1c9b47ca09cbed25dd70f5f1717..56a68742da96300e47785e1316d50b7d2af68ff5 GIT binary patch [stripped] diff --git a/src/shell/jython.py b/src/shell/jython.py --- a/src/shell/jython.py +++ b/src/shell/jython.py @@ -217,7 +217,7 @@ # Frozen. Let it go with the executable path. bytes_path = sys.executable else: - # Not frozen. Use the __file__ of this module.. + # Not frozen. Use the __file__ of this module. bytes_path = __file__ # Python 2 thinks in bytes. Carefully normalise in Unicode. path = os.path.realpath(bytes_path.decode(ENCODING)) @@ -565,24 +565,36 @@ enc = sys.stdout.encoding or 'ascii' sys.stdout.write(command_line.encode(enc, 'replace')) else: - if not (is_windows or not hasattr(os, "execvp") or args.help or - jython_command.uname == u"cygwin"): - # Replace this process with the java process. - # - # NB such replacements actually do not work under Windows, - # but if tried, they also fail very badly by hanging. - # So don't even try! - command = encode_list(command) - os.execvp(command[0], command[1:]) - else: - result = 1 - try: - result = subprocess.call(encode_list(command)) - if args.help: - print_help() - except KeyboardInterrupt: - pass - sys.exit(result) + try: + if not (is_windows or not hasattr(os, "execvp") or args.help or + jython_command.uname == u"cygwin"): + # Replace this process with the java process. + # + # NB such replacements actually do not work under Windows, + # but if tried, they also fail very badly by hanging. + # So don't even try! + command = encode_list(command) + os.execvp(command[0], command[1:]) + else: + result = 1 + try: + result = subprocess.call(encode_list(command)) + if args.help: + print_help() + except KeyboardInterrupt: + pass + sys.exit(result) + except OSError as e: + print >> sys.stderr, "Failed to launch Jython using command:",\ + command[0], "...\n", \ + " Use the --print option to see the command in full." + if jython_command.java_home: + print >> sys.stderr, " Launcher used JAVA_HOME =",\ + jython_command.java_home + else: + print >> sys.stderr, " Check PATH for java/jdb command." + print >> sys.stderr, e + sys.exit(1) if __name__ == "__main__": -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Sun Nov 4 11:25:18 2018 From: jython-checkins at python.org (jeff.allen) Date: Sun, 04 Nov 2018 16:25:18 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Add_newline_to_launcher_--?= =?utf-8?q?print_output=2E?= Message-ID: <20181104162518.1.158683B292E299D7@mg.python.org> https://hg.python.org/jython/rev/5dea28c83799 changeset: 8195:5dea28c83799 user: Jeff Allen date: Sun Nov 04 14:09:59 2018 +0000 summary: Add newline to launcher --print output. This trivial omission showed on Linux, but not on Windows. Given this, we defer rebuilding jython.exe until a further change, or release preparation, makes it necessary. files: src/shell/jython.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/shell/jython.py b/src/shell/jython.py --- a/src/shell/jython.py +++ b/src/shell/jython.py @@ -563,7 +563,7 @@ command_line = u" ".join(command) # It is possible the Unicode cannot be encoded for the console enc = sys.stdout.encoding or 'ascii' - sys.stdout.write(command_line.encode(enc, 'replace')) + sys.stdout.write(command_line.encode(enc, 'replace') + "\n") else: try: if not (is_windows or not hasattr(os, "execvp") or args.help or -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Sun Nov 4 11:59:42 2018 From: jython-checkins at python.org (jeff.allen) Date: Sun, 04 Nov 2018 16:59:42 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Record_many_fixed_bugs_in_?= =?utf-8?q?NEWS=2E?= Message-ID: <20181104165942.1.84CD1FEFB5638640@mg.python.org> https://hg.python.org/jython/rev/584cdc85ab67 changeset: 8196:584cdc85ab67 user: Jeff Allen date: Sun Nov 04 16:58:30 2018 +0000 summary: Record many fixed bugs in NEWS. In some cases, they may have been fixed in an earlier version. files: NEWS | 11 ++++++++++- 1 files changed, 10 insertions(+), 1 deletions(-) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -4,6 +4,15 @@ Development tip Bugs fixed + - [ 2231 ] __doc__ of PyString objects is always "The most base type" + - [ 2230 ] Jython evaluation blocks under heavy load with high multi-core systems + - [ 2506 ] ensurepip is reporting an error + - [ 1609 ] Partial parsing does not work with function decorators + - [ 2494 ] Support for pydoc_data + - [ 2492 ] NPE for PythonInterpreter after new PyInteger + - [ 2707 ] jython.py shebang line invalid on Linux + - [ 1748 ] subprocess and os.system don't show output + - [ 2343 ] PYTHONPATH is overwritten on Windows - [ 2346 ] Launcher not resilient to bad environment variable settings - [ 2709 ] test_chdir tolerates absent DOS 8.3 filename - [ 2706 ] Use python.path instead of JYTHONPATH @@ -30,7 +39,7 @@ users to work around differences from CPython. - python.startup registry property (and JYTHONSTARTUP environment variable) added. - Only the Jython command (class org.python.util.jython) now reads the environment variable - JYTHONPATH, not the core runtime, abd it respects the -E option (ignore environment). This + JYTHONPATH, not the core runtime, and it respects the -E option (ignore environment). This change is for consistency with CPython and with our handling of other environment variables. A pure Java application that creates its own interpreter may use the system or registry key "python.path" to add to sys.path, as documented. -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Sun Nov 25 03:08:34 2018 From: jython-checkins at python.org (jeff.allen) Date: Sun, 25 Nov 2018 08:08:34 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Formatting_and_refactoring?= =?utf-8?q?_of_classes_in_the_package_manager=2E?= Message-ID: <20181125080834.1.CBEB49DF99BC34D2@mg.python.org> https://hg.python.org/jython/rev/0782ca2ab866 changeset: 8197:0782ca2ab866 user: Jeff Allen date: Thu Nov 08 20:19:49 2018 +0000 summary: Formatting and refactoring of classes in the package manager. This ought to have no effect on the meaning. These classes are implicated in our failure to index the Java runtime from Java 9 onwards. Code pre-dates our coding standard, so a good time to apply minor tidying up and some comments. files: registry | 8 +- src/org/python/core/PyJavaPackage.java | 137 ++-- src/org/python/core/packagecache/CachedJarsPackageManager.java | 239 ++++----- src/org/python/core/packagecache/PackageManager.java | 50 +- src/org/python/core/packagecache/PathPackageManager.java | 91 +-- src/org/python/core/packagecache/SysPackageManager.java | 81 ++- 6 files changed, 284 insertions(+), 322 deletions(-) diff --git a/registry b/registry --- a/registry +++ b/registry @@ -21,9 +21,11 @@ #python.cachedir.skip = false # Properties to check for initializing and updating the package cache -# Hopefully you won't have any need to change these -python.packages.paths = java.class.path, sun.boot.class.path -python.packages.directories = java.ext.dirs +# Values shown here are those hard-coded in Jython's cache manager. +# Treat JARs on the classpath and (before Java 9) in the JRE as a source of Python packages. +#python.packages.paths = java.class.path, sun.boot.class.path +# Treat installed optional (Java) packages as source of Python packages (before Java 9) +#python.packages.directories = java.ext.dirs # Set verbosity to error, warning, message, comment, or debug # for varying levels of informative messages from Jython. Normally diff --git a/src/org/python/core/PyJavaPackage.java b/src/org/python/core/PyJavaPackage.java --- a/src/org/python/core/PyJavaPackage.java +++ b/src/org/python/core/PyJavaPackage.java @@ -4,50 +4,41 @@ package org.python.core; import org.python.core.packagecache.PackageManager; + import java.util.StringTokenizer; -/** - * A representation of java package. - */ +/** A representation of java package. */ public class PyJavaPackage extends PyObject implements Traverseproc { + public String __name__; - public PyStringMap __dict__; - /** Its keys are the names of statically known classes. - * E.g. from jars pre-scan. - */ + /** Its keys are the names of statically known classes. E.g. from jars pre-scan. */ public PyStringMap clsSet; public String __file__; - /** (Control) package manager whose hierarchy contains this java pkg. - */ + /** (Control) package manager whose hierarchy contains this java pkg */ public PackageManager __mgr__; public PyJavaPackage(String name) { this(name, null, null); } - public PyJavaPackage(String name,String jarfile) { + public PyJavaPackage(String name, String jarfile) { this(name, null, jarfile); } - public PyJavaPackage(String name,PackageManager mgr) { + public PyJavaPackage(String name, PackageManager mgr) { this(name, mgr, null); } - - public PyJavaPackage(String name,PackageManager mgr,String jarfile) { + public PyJavaPackage(String name, PackageManager mgr, String jarfile) { __file__ = jarfile; __name__ = name; + __mgr__ = (mgr != null) ? mgr : PySystemState.packageManager; - if( mgr == null ) - __mgr__ = PySystemState.packageManager; // default - else - __mgr__ = mgr; - - clsSet= new PyStringMap(); + clsSet = new PyStringMap(); __dict__ = new PyStringMap(); __dict__.__setitem__("__name__", new PyString(__name__)); @@ -59,27 +50,26 @@ public PyJavaPackage addPackage(String name, String jarfile) { int dot = name.indexOf('.'); - String firstName=name; - String lastName=null; + String firstName = name; + String lastName = null; if (dot != -1) { - firstName = name.substring(0,dot); - lastName = name.substring(dot+1, name.length()); + firstName = name.substring(0, dot); + lastName = name.substring(dot + 1, name.length()); } firstName = firstName.intern(); - PyJavaPackage p = (PyJavaPackage)__dict__.__finditem__(firstName); + PyJavaPackage p = (PyJavaPackage) __dict__.__finditem__(firstName); if (p == null) { - String pname = __name__.length() == 0 ? - firstName : __name__+'.'+firstName; + String pname = __name__.length() == 0 ? firstName : __name__ + '.' + firstName; p = new PyJavaPackage(pname, __mgr__, jarfile); __dict__.__setitem__(firstName, p); } else { // this code is ok here, because this is not needed for // a top level package - if (jarfile == null || !jarfile.equals(p.__file__)) + if (jarfile == null || !jarfile.equals(p.__file__)) { p.__file__ = null; + } } - if (lastName != null) return p.addPackage(lastName, jarfile); - else return p; + return lastName != null ? p.addPackage(lastName, jarfile) : p; } public PyObject addClass(String name, Class c) { @@ -91,21 +81,22 @@ /** * Add statically known classes. * - * @param classes - * their names as comma-separated string + * @param classes their names as comma-separated string */ public void addPlaceholders(String classes) { StringTokenizer tok = new StringTokenizer(classes, ",@"); - while (tok.hasMoreTokens()) { + while (tok.hasMoreTokens()) { String p = tok.nextToken(); String name = p.trim().intern(); - if (clsSet.__finditem__(name) == null) + if (clsSet.__finditem__(name) == null) { clsSet.__setitem__(name, Py.One); + } } } + @Override public PyObject __dir__() { - return __mgr__.doDir(this,false,false); + return __mgr__.doDir(this, false, false); } /** @@ -117,74 +108,78 @@ * @return list of member names */ public PyObject fillDir() { - return __mgr__.doDir(this,true,false); + return __mgr__.doDir(this, true, false); } - + @Override public PyObject __findattr_ex__(String name) { PyObject ret = __dict__.__finditem__(name); - if (ret != null) return ret; - if (__mgr__.packageExists(__name__,name)) { - __mgr__.notifyPackageImport(__name__,name); + if (ret != null) { + return ret; + + } else if (__mgr__.packageExists(__name__, name)) { + __mgr__.notifyPackageImport(__name__, name); return addPackage(name); - } - - Class c = __mgr__.findClass(__name__,name); - if (c != null) return addClass(name,c); - if (name == "__name__") return new PyString(__name__); - if (name == "__dict__") return __dict__; - if (name == "__mgr__") return Py.java2py(__mgr__); - if (name == "__file__") { - // Stored as UTF-16 for Java but expected as bytes in Python - return __file__ == null ? Py.None : Py.fileSystemEncode(__file__); + } else { + Class c = __mgr__.findClass(__name__, name); + if (c != null) { + return addClass(name, c); + } else if (name == "__name__") { + return new PyString(__name__); + } else if (name == "__dict__") { + return __dict__; + } else if (name == "__mgr__") { + return Py.java2py(__mgr__); + } else if (name == "__file__") { + // Stored as UTF-16 for Java but expected as bytes in Python + return __file__ == null ? Py.None : Py.fileSystemEncode(__file__); + } else { + return null; + } } - - return null; } + @Override public void __setattr__(String attr, PyObject value) { if (attr == "__mgr__") { - PackageManager newMgr = Py.tojava(value, - PackageManager.class); + PackageManager newMgr = Py.tojava(value, PackageManager.class); if (newMgr == null) { throw Py.TypeError("cannot set java package __mgr__ to None"); } __mgr__ = newMgr; - return; - } - if (attr == "__file__") { + } else if (attr == "__file__") { // Stored as UTF-16 for Java but presented as bytes from Python __file__ = Py.fileSystemDecode(value); - return; + } else { + super.__setattr__(attr, value); } - - super.__setattr__(attr,value); } - public String toString() { - return ""; + @Override + public String toString() { + return ""; } - /* Traverseproc implementation */ @Override public int traverse(Visitproc visit, Object arg) { - //__dict__ cannot be null + // __dict__ cannot be null int retVal = visit.visit(__dict__, arg); if (retVal != 0) { return retVal; + } else { + // clsSet cannot be null + retVal = visit.visit(clsSet, arg); + if (retVal != 0) { + return retVal; + } else { + // __mgr__ and __mgr__.topLevelPackage cannot be null + return visit.visit(__mgr__.topLevelPackage, arg); + } } - - //clsSet cannot be null - retVal = visit.visit(clsSet, arg); - if (retVal != 0) { - return retVal; - } - //__mgr__ and __mgr__.topLevelPackage cannot be null - return visit.visit(__mgr__.topLevelPackage, arg); } @Override diff --git a/src/org/python/core/packagecache/CachedJarsPackageManager.java b/src/org/python/core/packagecache/CachedJarsPackageManager.java --- a/src/org/python/core/packagecache/CachedJarsPackageManager.java +++ b/src/org/python/core/packagecache/CachedJarsPackageManager.java @@ -27,49 +27,45 @@ import java.util.zip.ZipInputStream; /** - * Abstract package manager that gathers info about statically known classes - * from a set of jars. This info can be eventually cached. Off-the-shelf this - * class offers a local file-system based cache impl. + * Abstract package manager that gathers info about statically known classes from a set of jars and + * the Java runtime. This info can be cached, eventually. Off-the-shelf this class offers a local + * file-system based cache implementation. */ public abstract class CachedJarsPackageManager extends PackageManager { /** - * Message log method - hook. This default impl does nothing. + * Message log method - hook. This default implementation does nothing. * * @param msg message text */ - protected void message(String msg) { - } + protected void message(String msg) {} /** - * Warning log method - hook. This default impl does nothing. + * Warning log method - hook. This default implementation does nothing. * * @param warn warning text */ - protected void warning(String warn) { - } + protected void warning(String warn) {} /** - * Comment log method - hook. This default impl does nothing. + * Comment log method - hook. This default implementation does nothing. * * @param msg message text */ - protected void comment(String msg) { - } + protected void comment(String msg) {} /** - * Debug log method - hook. This default impl does nothing. + * Debug log method - hook. This default implementation does nothing. * * @param msg message text */ - protected void debug(String msg) { - } + protected void debug(String msg) {} /** - * Filter class/pkg by name helper method - hook. The default impl. is used - * by {@link #addJarToPackages} in order to filter out classes whose name - * contains '$' (e.g. inner classes,...). Should be used or overriden by - * derived classes too. Also to be used in {@link #doDir}. + * Filter class/pkg by name helper method - hook. The default implementation is used by + * {@link #addJarToPackages} in order to filter out classes whose name contains '$' (e.g. inner + * classes). Should be used or overridden by derived classes too. Also to be used in + * {@link #doDir}. * * @param name class/pkg name * @param pkg if true, name refers to a pkg @@ -80,11 +76,10 @@ } /** - * Filter class by access perms helper method - hook. The default impl. is - * used by {@link #addJarToPackages} in order to filter out non-public - * classes. Should be used or overriden by derived classes too. Also to be - * used in {@link #doDir}. Access perms can be read with - * {@link #checkAccess}. + * Filter class by access perms helper method - hook. The default implementation is used by + * {@link #addJarToPackages} in order to filter out non-public classes. Should be used or + * overridden by derived classes too. Also to be used in {@link #doDir}. Access perms can be + * read with {@link #checkAccess}. * * @param name class name * @param acc class access permissions as int @@ -96,7 +91,7 @@ private boolean indexModified; - private Map jarfiles; + private Map jarfiles; private static String listToString(List list) { int n = list.size(); @@ -110,8 +105,7 @@ return ret.toString(); } - // Add a single class from zipFile to zipPackages - // Only add valid, public classes + /** Add a single class from zipFile to zipPackages. Only add valid, public classes. */ private void addZipEntry(Map[]> zipPackages, ZipEntry entry, ZipInputStream zip) throws IOException { String name = entry.getName(); @@ -154,11 +148,11 @@ } @SuppressWarnings("unchecked") - private List[] createGenericStringListArray(){ - return new List[] { Generic.list(), Generic.list() }; + private List[] createGenericStringListArray() { + return new List[] {Generic.list(), Generic.list()}; } - // Extract all of the packages in a single jarfile + /** Extract all of the packages in a single jar file */ private Map getZipPackages(InputStream jarin) throws IOException { Map[]> zipPackages = Generic.map(); @@ -172,7 +166,7 @@ // Turn each vector into a comma-separated String Map transformed = Generic.map(); - for (Entry[]> kv : zipPackages.entrySet()) { + for (Entry[]> kv : zipPackages.entrySet()) { List[] vec = kv.getValue(); String classes = listToString(vec[0]); if (vec[1].size() > 0) { @@ -185,40 +179,36 @@ } /** - * Gathers classes info from jar specified by jarurl URL. Eventually just - * using previously cached info. Eventually updated info is not cached. - * Persistent cache storage access goes through inOpenCacheFile() and - * outCreateCacheFile(). + * Gathers classes info from jar specified by a URL. Eventually just using previously cached + * info. Eventually updated info is not cached. Persistent cache storage access goes through + * inOpenCacheFile() and outCreateCacheFile(). */ public void addJarToPackages(java.net.URL jarurl) { addJarToPackages(jarurl, null, false); } /** - * Gathers classes info from jar specified by jarurl URL. Eventually just - * using previously cached info. Eventually updated info is (re-)cached if - * param cache is true. Persistent cache storage access goes through - * inOpenCacheFile() and outCreateCacheFile(). + * Gathers classes info from jar specified by URL. Eventually just using previously cached info. + * Eventually updated info is (re-)cached if param cache is true. Persistent cache storage + * access goes through inOpenCacheFile() and outCreateCacheFile(). */ public void addJarToPackages(URL jarurl, boolean cache) { addJarToPackages(jarurl, null, cache); } /** - * Gathers classes info from jar specified by File jarfile. Eventually just - * using previously cached info. Eventually updated info is not cached. - * Persistent cache storage access goes through inOpenCacheFile() and - * outCreateCacheFile(). + * Gathers classes info from jar specified by File jarfile. Eventually just using previously + * cached info. Eventually updated info is not cached. Persistent cache storage access goes + * through inOpenCacheFile() and outCreateCacheFile(). */ public void addJarToPackages(File jarfile) { addJarToPackages(null, jarfile, false); } /** - * Gathers classes info from jar specified by File jarfile. Eventually just - * using previously cached info. Eventually updated info is (re-)cached if - * param cache is true. Persistent cache storage access goes through - * inOpenCacheFile() and outCreateCacheFile(). + * Gathers classes info from jar specified by File jarfile. Eventually just using previously + * cached info. Eventually updated info is (re-)cached if param cache is true. Persistent cache + * storage access goes through inOpenCacheFile() and outCreateCacheFile(). */ public void addJarToPackages(File jarfile, boolean cache) { addJarToPackages(null, jarfile, cache); @@ -233,10 +223,11 @@ if (jarfile == null) { jarconn = jarurl.openConnection(); - // This is necessary because 'file:' url-connections - // return always 0 through getLastModified (bug?). - // And in order to handle localfiles (from urls too) - // uniformly. + /* + * This is necessary because 'file:' url-connections always return 0 through + * getLastModified (bug?). And in order to handle localfiles (from urls too) + * uniformly. + */ if (jarconn.getURL().getProtocol().equals("file")) { // ??pending: need to use java2 URLDecoder.decode? String jarfilename = jarurl.getFile(); @@ -270,8 +261,7 @@ entry = this.jarfiles.get(jarcanon); - if ((entry == null || !(new File(entry.cachefile).exists())) - && cache) { + if ((entry == null || !(new File(entry.cachefile).exists())) && cache) { comment("processing new jar, '" + jarcanon + "'"); String jarname; @@ -312,8 +302,7 @@ InputStream jarin = null; try { if (jarconn == null) { - jarin = new BufferedInputStream( - new FileInputStream(jarfile)); + jarin = new BufferedInputStream(new FileInputStream(jarfile)); } else { jarin = jarconn.getInputStream(); } @@ -334,14 +323,13 @@ } catch (IOException ioe) { // silently skip any bad directories warning("skipping bad jar, '" - + (jarfile != null ? jarfile.toString() : jarurl.toString()) - + "'"); + + (jarfile != null ? jarfile.toString() : jarurl.toString()) + "'"); } } - private void addPackages(Map zipPackages, String jarfile) { - for (Entry entry : zipPackages.entrySet()) { + private void addPackages(Map zipPackages, String jarfile) { + for (Entry entry : zipPackages.entrySet()) { String pkg = entry.getKey(); String classes = entry.getValue(); @@ -354,8 +342,10 @@ } } - // Read in cache file storing package info for a single .jar - // Return null and delete this cachefile if it is invalid + /** + * Read in cache file storing package info for a single jar. Return null and delete this + * cachefile if it is invalid. + */ @SuppressWarnings("empty-statement") private Map readCacheFile(JarXEntry entry, String jarcanon) { String cachefile = entry.cachefile; @@ -369,8 +359,8 @@ String old_jarcanon = istream.readUTF(); long old_mtime = istream.readLong(); if ((!old_jarcanon.equals(jarcanon)) || (old_mtime != mtime)) { - comment("invalid cache file: " + cachefile + ", " + jarcanon - + ":" + old_jarcanon + ", " + mtime + ":" + old_mtime); + comment("invalid cache file: " + cachefile + ", " + jarcanon + ":" + old_jarcanon + + ", " + mtime + ":" + old_mtime); deleteCacheFile(cachefile); return null; } @@ -390,7 +380,7 @@ packs.put(packageName, classes); } } catch (EOFException eof) { - //ignore + // ignore } return packs; @@ -402,15 +392,15 @@ try { istream.close(); } catch (IOException ignore) { - //ignore + // ignore } } } } - // Write a cache file storing package info for a single .jar - private void writeCacheFile(JarXEntry entry, String jarcanon, - Map zipPackages, boolean brandNew) { + /** Write a cache file storing package info for a single jar. */ + private void writeCacheFile(JarXEntry entry, String jarcanon, Map zipPackages, + boolean brandNew) { DataOutputStream ostream = null; try { ostream = outCreateCacheFile(entry, brandNew); @@ -418,7 +408,7 @@ ostream.writeLong(entry.mtime); comment("rewriting cachefile for '" + jarcanon + "'"); - for (Entry kv : zipPackages.entrySet()) { + for (Entry kv : zipPackages.entrySet()) { String classes = kv.getValue(); // Make sure each package is not larger than 64k for (String part : splitString(classes, 65535)) { @@ -434,7 +424,7 @@ try { ostream.close(); } catch (IOException ignore) { - //ignore + // ignore } } } @@ -443,9 +433,8 @@ /** * Split up a string into several chunks based on a certain size * - * The writeCacheFile method will use the writeUTF method on a - * DataOutputStream which only allows writing 64k chunks, so use - * this utility method to split it up + * The writeCacheFile method will use the writeUTF method on a DataOutputStream which only + * allows writing 64k chunks, so use this utility method to split it up * * @param str - The string to split up into chunks * @param maxLength - The max size a string should be @@ -470,8 +459,8 @@ } /** - * Initializes cache. Eventually reads back cache index. Index persistent - * storage is accessed through inOpenIndex(). + * Initializes cache. Eventually reads back cache index. Index persistent storage is accessed + * through inOpenIndex(). */ protected void initCache() { this.indexModified = false; @@ -492,7 +481,7 @@ this.jarfiles.put(jarcanon, new JarXEntry(cachefile, mtime)); } } catch (EOFException eof) { - //ignore + // ignore } } catch (IOException ioe) { warning("invalid index file"); @@ -501,16 +490,13 @@ try { istream.close(); } catch (IOException ignore) { - //ignore + // ignore } } } } - /** - * Write back cache index. Index persistent storage is accessed through - * outOpenIndex(). - */ + /** Write back cache index. Index persistent storage is accessed through outOpenIndex(). */ public void saveCache() { if (jarfiles == null || !indexModified) { return; @@ -523,7 +509,7 @@ DataOutputStream ostream = null; try { ostream = outOpenIndex(); - for (Entry entry : jarfiles.entrySet()) { + for (Entry entry : jarfiles.entrySet()) { String jarcanon = entry.getKey(); JarXEntry xentry = entry.getValue(); ostream.writeUTF(jarcanon); @@ -537,7 +523,7 @@ try { ostream.close(); } catch (IOException ignore) { - //ignore + // ignore } } } @@ -545,11 +531,9 @@ // hooks for changing cache storage - /** - * To pass a cachefile id by ref. And for internal use. See - * outCreateCacheFile - */ + /** To pass a cachefile id by ref. And for internal use. See outCreateCacheFile. */ public static class JarXEntry extends Object { + /** cachefile id */ public String cachefile; @@ -567,66 +551,57 @@ } /** - * Open cache index for reading from persistent storage - hook. Must Return - * null if this is absent. This default impl is part of the off-the-shelf - * local file-system cache impl. Can be overriden. + * Open cache index for reading from persistent storage - hook. Must Return null if this is + * absent. This default implementation is part of the off-the-shelf local file-system cache + * implementation. Can be overridden. */ protected DataInputStream inOpenIndex() throws IOException { File indexFile = new File(this.cachedir, "packages.idx"); - if (!indexFile.exists()) { return null; + } else { + FileInputStream istream = new FileInputStream(indexFile); + return new DataInputStream(new BufferedInputStream(istream)); } - - DataInputStream istream = new DataInputStream(new BufferedInputStream( - new FileInputStream(indexFile))); - - return istream; } /** - * Open cache index for writing back to persistent storage - hook. This - * default impl is part of the off-the-shelf local file-system cache impl. - * Can be overriden. + * Open cache index for writing back to persistent storage - hook. This default implementation + * is part of the off-the-shelf local file-system cache implementation. Can be overridden. */ protected DataOutputStream outOpenIndex() throws IOException { File indexFile = new File(this.cachedir, "packages.idx"); - - return new DataOutputStream(new BufferedOutputStream( - new FileOutputStream(indexFile))); + FileOutputStream ostream = new FileOutputStream(indexFile); + return new DataOutputStream(new BufferedOutputStream(ostream)); } /** - * Open cache file for reading from persistent storage - hook. This default - * impl is part of the off-the-shelf local file-system cache impl. Can be - * overriden. + * Open cache file for reading from persistent storage - hook. This default implementation is + * part of the off-the-shelf local file-system cache implementation. Can be overridden. */ - protected DataInputStream inOpenCacheFile(String cachefile) - throws IOException { - return new DataInputStream(new BufferedInputStream(new FileInputStream( - cachefile))); + protected DataInputStream inOpenCacheFile(String cachefile) throws IOException { + return new DataInputStream(new BufferedInputStream(new FileInputStream(cachefile))); } /** - * Delete (invalidated) cache file from persistent storage - hook. This - * default impl is part of the off-the-shelf local file-system cache impl. - * Can be overriden. + * Delete (invalidated) cache file from persistent storage - hook. This default implementation + * is part of the off-the-shelf local file-system cache implementation. Can be overridden. */ protected void deleteCacheFile(String cachefile) { new File(cachefile).delete(); } /** - * Create/open cache file for rewriting back to persistent storage - hook. - * If create is false, cache file is supposed to exist and must be opened - * for rewriting, entry.cachefile is a valid cachefile id. If create is - * true, cache file must be created. entry.cachefile is a flat jarname to be - * used to produce a valid cachefile id (to be put back in entry.cachefile - * on exit). This default impl is part of the off-the-shelf local - * file-system cache impl. Can be overriden. + * Create/open cache file for rewriting back to persistent storage - hook. If create is false, + * cache file is supposed to exist and must be opened for rewriting, entry.cachefile is a valid + * cachefile id. If create is true, cache file must be created. entry.cachefile is a flat + * jarname to be used to produce a valid cachefile id (to be put back in entry.cachefile on + * exit). This default implementation is part of the off-the-shelf local file-system cache + * implementation. Can be overridden. */ - protected DataOutputStream outCreateCacheFile(JarXEntry entry, - boolean create) throws IOException { + protected DataOutputStream outCreateCacheFile(JarXEntry entry, boolean create) + throws IOException { + File cachefile = null; if (create) { @@ -647,35 +622,35 @@ cachefile = new File(entry.cachefile); } - return new DataOutputStream(new BufferedOutputStream( - new FileOutputStream(cachefile))); + return new DataOutputStream(new BufferedOutputStream(new FileOutputStream(cachefile))); } - // for default cache (local fs based) impl - + /** for default cache (local fs based) implementation */ private File cachedir; /** - * Initialize off-the-shelf (default) local file-system cache impl. Must be - * called before {@link #initCache}. cachedir is the cache repository - * directory, this is eventually created. Returns true if dir works. + * Initialize off-the-shelf (default) local file-system cache implementation. Must be called + * before {@link #initCache}. cachedir is the cache repository directory, this is eventually + * created. Returns true if dir works. */ protected boolean useCacheDir(File aCachedir1) { + if (aCachedir1 == null) { return false; } + try { - if(!aCachedir1.isDirectory() && aCachedir1.mkdirs() == false) { + if (!aCachedir1.isDirectory() && aCachedir1.mkdirs() == false) { warning("can't create package cache dir, '" + aCachedir1 + "'"); return false; } - } catch(AccessControlException ace) { - warning("The java security manager isn't allowing access to the package cache dir, '" + aCachedir1 + "'"); + } catch (AccessControlException ace) { + warning("The java security manager isn't allowing access to the package cache dir, '" + + aCachedir1 + "'"); return false; } this.cachedir = aCachedir1; - return true; } diff --git a/src/org/python/core/packagecache/PackageManager.java b/src/org/python/core/packagecache/PackageManager.java --- a/src/org/python/core/packagecache/PackageManager.java +++ b/src/org/python/core/packagecache/PackageManager.java @@ -11,10 +11,8 @@ import org.python.core.PyList; import org.python.core.PyObject; import org.python.core.PyStringMap; -import org.python.core.util.FileUtil; import java.io.IOException; -import java.io.InputStream; /** * Abstract package manager. @@ -33,12 +31,11 @@ return findClass(pkg, name, "java class"); } - public void notifyPackageImport(String pkg, String name) { - } + public void notifyPackageImport(String pkg, String name) {} /** - * Dynamically check if pkg.name exists as java pkg in the controlled - * hierarchy. Should be overriden. + * Dynamically check if pkg.name exists as java pkg in the controlled hierarchy. Should be + * overridden. * * @param pkg parent pkg name * @param name candidate name @@ -47,7 +44,7 @@ public abstract boolean packageExists(String pkg, String name); /** - * Reports the specified package content names. Should be overriden. Used by + * Reports the specified package content names. Should be overridden. Used by * {@link PyJavaPackage#__dir__} and {@link PyJavaPackage#fillDir}. * * @return resulting list of names (PyList of PyString) @@ -55,39 +52,34 @@ * @param instantiate if true then instatiate reported names in package dict * @param exclpkgs exclude packages (just when instantiate is false) */ - public abstract PyList doDir(PyJavaPackage jpkg, boolean instantiate, - boolean exclpkgs); + public abstract PyList doDir(PyJavaPackage jpkg, boolean instantiate, boolean exclpkgs); /** - * Append a directory to the list of directories searched for java packages - * and java classes. + * Append a directory to the list of directories searched for java packages and java classes. * * @param dir A directory. */ public abstract void addDirectory(java.io.File dir); /** - * Append a directory to the list of directories searched for java packages - * and java classes. + * Append a directory to the list of directories searched for java packages and java classes. * * @param dir A directory name. */ public abstract void addJarDir(String dir, boolean cache); /** - * Append a jar file to the list of locations searched for java packages and - * java classes. + * Append a jar file to the list of locations searched for java packages and java classes. * * @param jarfile A directory name. */ public abstract void addJar(String jarfile, boolean cache); /** - * Basic helper implementation of {@link #doDir}. It merges information - * from jpkg {@link PyJavaPackage#clsSet} and {@link PyJavaPackage#__dict__}. + * Basic helper implementation of {@link #doDir}. It merges information from jpkg + * {@link PyJavaPackage#clsSet} and {@link PyJavaPackage#__dict__}. */ - protected PyList basicDoDir(PyJavaPackage jpkg, boolean instantiate, - boolean exclpkgs) { + protected PyList basicDoDir(PyJavaPackage jpkg, boolean instantiate, boolean exclpkgs) { PyStringMap dict = jpkg.__dict__; PyStringMap cls = jpkg.clsSet; @@ -117,14 +109,11 @@ return dict.keys(); } - /** - * Helper merging list2 into list1. Returns list1. - */ + /** Helper merging list2 into list1. Returns list1. */ protected PyList merge(PyList list1, PyList list2) { for (PyObject name : list2.asIterable()) { list1.append(name); } - return list1; } @@ -159,8 +148,7 @@ * @param jarfile involved jarfile; can be null * @return created/updated package */ - public PyJavaPackage makeJavaPackage(String name, String classes, - String jarfile) { + public PyJavaPackage makeJavaPackage(String name, String classes, String jarfile) { PyJavaPackage p = this.topLevelPackage; if (name.length() != 0) { p = p.addPackage(name, jarfile); @@ -173,7 +161,6 @@ return p; } - private static class AccessVisitor extends ClassVisitor { private int class_access; @@ -183,8 +170,8 @@ } @Override - public void visit(int version, int access, String name, - String signature, String superName, String[] interfaces) { + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { class_access = access; } @@ -194,11 +181,10 @@ } /** - * Check that a given stream is a valid Java .class file. And return its - * access permissions as an int. + * Check that a given stream is a valid Java .class file. And return its access permissions as + * an int. */ - static protected int checkAccess(java.io.InputStream cstream) - throws IOException { + static protected int checkAccess(java.io.InputStream cstream) throws IOException { try { ClassReader reader = new ClassReader(cstream); AccessVisitor visitor = new AccessVisitor(); diff --git a/src/org/python/core/packagecache/PathPackageManager.java b/src/org/python/core/packagecache/PathPackageManager.java --- a/src/org/python/core/packagecache/PathPackageManager.java +++ b/src/org/python/core/packagecache/PathPackageManager.java @@ -3,13 +3,12 @@ package org.python.core.packagecache; -import org.python.core.imp; import org.python.core.Py; import org.python.core.PyJavaPackage; import org.python.core.PyList; import org.python.core.PyObject; import org.python.core.PyString; -import org.python.core.PyUnicode; +import org.python.core.imp; import org.python.core.util.RelativeFile; import java.io.BufferedInputStream; @@ -19,9 +18,8 @@ import java.io.IOException; /** - * Path package manager. Gathering classes info dynamically from a set of - * directories in path {@link #searchPath}, and statically from a set of jars, - * like {@link CachedJarsPackageManager}. + * Path package manager. Gathering classes info dynamically from a set of directories in path + * {@link #searchPath}, and statically from a set of jars, like {@link CachedJarsPackageManager}. */ public abstract class PathPackageManager extends CachedJarsPackageManager { @@ -32,31 +30,31 @@ } /** - * Helper for {@link #packageExists(java.lang.String,java.lang.String)}. - * Scans for package pkg.name the directories in path. + * Helper for {@link #packageExists(java.lang.String,java.lang.String)}. Scans for package + * pkg.name the directories in path. */ protected boolean packageExists(PyList path, String pkg, String name) { - String child = pkg.replace('.', File.separatorChar) + File.separator - + name; + String child = pkg.replace('.', File.separatorChar) + File.separator + name; for (int i = 0; i < path.__len__(); i++) { + // Each entry in the path may be byte-encoded or unicode PyObject entry = path.pyget(i); String dir = Py.fileSystemDecode(entry); File f = new RelativeFile(dir, child); + try { if (f.isDirectory() && imp.caseok(f, name)) { /* - * Figure out if we have a directory a mixture of python and - * java or just an empty directory (which means Java) or a - * directory with only Python source (which means Python). + * Figure out if we have a directory a mixture of python and java or just an + * empty directory (which means Java) or a directory with only Python source + * (which means Python). */ PackageExistsFileFilter m = new PackageExistsFileFilter(); f.listFiles(m); boolean exists = m.packageExists(); if (exists) { - Py.writeComment("import", "java package as '" - + f.getAbsolutePath() + "'"); + Py.writeComment("import", "java package as '" + f.getAbsolutePath() + "'"); } return exists; } @@ -68,59 +66,53 @@ } private static class PackageExistsFileFilter implements FilenameFilter { + private boolean java; - private boolean python; @Override public boolean accept(File dir, String name) { - if(name.endsWith(".py") || name.endsWith("$py.class") || name.endsWith("$_PyInner.class")) { + if (name.endsWith(".py") || name.endsWith("$py.class") + || name.endsWith("$_PyInner.class")) { python = true; - }else if (name.endsWith(".class")) { + } else if (name.endsWith(".class")) { java = true; } return false; } public boolean packageExists() { - if (this.python && !this.java) { - return false; - } - return true; + return !python || java; } } /** - * Helper for {@link #doDir(PyJavaPackage,boolean,boolean)}. Scans for - * package jpkg content over the directories in path. Add to ret the found - * classes/pkgs. Filter out classes using {@link #filterByName},{@link #filterByAccess}. + * Helper for {@link #doDir(PyJavaPackage,boolean,boolean)}. Scans for package jpkg content over + * the directories in path. Add to ret the found classes/pkgs. Filter out classes using + * {@link #filterByName},{@link #filterByAccess}. */ - protected void doDir(PyList path, PyList ret, PyJavaPackage jpkg, - boolean instantiate, boolean exclpkgs) { + protected void doDir(PyList path, PyList ret, PyJavaPackage jpkg, boolean instantiate, + boolean exclpkgs) { + String child = jpkg.__name__.replace('.', File.separatorChar); for (int i = 0; i < path.__len__(); i++) { // Each entry in the path may be byte-encoded or unicode String dir = Py.fileSystemDecode(path.pyget(i)); - if (dir.length() == 0) { dir = null; } File childFile = new File(dir, child); - String[] list = childFile.list(); if (list == null) { continue; } - doList: for (int j = 0; j < list.length; j++) { + doList : for (int j = 0; j < list.length; j++) { String jname = list[j]; - File cand = new File(childFile, jname); - int jlen = jname.length(); - boolean pkgCand = false; if (cand.isDirectory()) { @@ -160,8 +152,7 @@ if (!pkgCand) { try { - int acc = checkAccess(new BufferedInputStream( - new FileInputStream(cand))); + int acc = checkAccess(new BufferedInputStream(new FileInputStream(cand))); if ((acc == -1) || filterByAccess(jname, acc)) { continue; } @@ -179,15 +170,11 @@ } ret.append(name); - } } - } - /** - * Add directory dir (if exists) to {@link #searchPath}. - */ + /** Add directory dir (if exists) to {@link #searchPath}. */ @Override public void addDirectory(File dir) { try { @@ -201,23 +188,16 @@ } } - // ??pending: - // Uses simply split and not a StringTokenizer+trim to adhere to - // sun jvm parsing of classpath. - // E.g. "a;" is parsed by sun jvm as a, ""; the latter is interpreted - // as cwd. jview trims and cwd is per default in classpath. - // The logic here should work for both(...). Need to distinguish? - // This code does not avoid duplicates in searchPath. - // Should cause no problem (?). - /** - * Adds "classpath" entry. Calls {@link #addDirectory} if path refers to a - * dir, {@link #addJarToPackages(java.io.File, boolean)} with param cache - * true if path refers to a jar. + * Scan a path that may be a mixture of directory and JAR specifiers, and within each path entry + * index the packages. Calls {@link #addDirectory} if a path entry refers to a dir, + * {@link #addJarToPackages(java.io.File, boolean)} with param cache true if the path entry + * refers to a jar. */ public void addClassPath(String path) { - String[] paths = path.split(java.io.File.pathSeparator); - for (String entry: paths) { + String[] entries = path.split(java.io.File.pathSeparator); + for (String entry : entries) { + entry = entry.trim(); if (entry.endsWith(".jar") || entry.endsWith(".zip")) { addJarToPackages(new File(entry), true); } else { @@ -230,13 +210,10 @@ } @Override - public PyList doDir(PyJavaPackage jpkg, boolean instantiate, - boolean exclpkgs) { + public PyList doDir(PyJavaPackage jpkg, boolean instantiate, boolean exclpkgs) { PyList basic = basicDoDir(jpkg, instantiate, exclpkgs); PyList ret = new PyList(); - doDir(this.searchPath, ret, jpkg, instantiate, exclpkgs); - return merge(basic, ret); } diff --git a/src/org/python/core/packagecache/SysPackageManager.java b/src/org/python/core/packagecache/SysPackageManager.java --- a/src/org/python/core/packagecache/SysPackageManager.java +++ b/src/org/python/core/packagecache/SysPackageManager.java @@ -8,9 +8,9 @@ import org.python.core.PyList; import org.python.core.PySystemState; +import java.io.File; import java.util.Properties; import java.util.StringTokenizer; -import java.io.*; /** * System package manager. Used by org.python.core.PySystemState. @@ -58,26 +58,34 @@ addJarDir(jdir, cache, cache); } + /** Index the contents of every JAR or ZIP in a directory. */ private void addJarDir(String jdir, boolean cache, boolean saveCache) { + File file = new File(jdir); - if (!file.isDirectory()) { - return; - } String[] files = file.list(); - for (int i = 0; i < files.length; i++) { - String entry = files[i]; - if (entry.endsWith(".jar") || entry.endsWith(".zip")) { - addJarToPackages(new File(jdir, entry), cache); + + if (files != null) { + // jdir is a directory, enumerated in the array files + for (int i = 0; i < files.length; i++) { + String entry = files[i]; + if (entry.endsWith(".jar") || entry.endsWith(".zip")) { + addJarToPackages(new File(jdir, entry), cache); + } } - } - if (saveCache) { - saveCache(); + + if (saveCache) { + saveCache(); + } } } + /** + * Scan a path that contains directory specifiers, and within each directory, find every JAR or + * ZIP archive and add it to the list of archives searched for packages. (This means, index it.) + * Non-directory entries are ignored. + */ private void addJarPath(String path) { - StringTokenizer tok = new StringTokenizer(path, - java.io.File.pathSeparator); + StringTokenizer tok = new StringTokenizer(path, java.io.File.pathSeparator); while (tok.hasMoreTokens()) { // ??pending: do jvms trim? how is interpreted entry=""? String entry = tok.nextToken(); @@ -85,33 +93,56 @@ } } + /** + * Walk the packages found in paths specified indirectly through the given {@code Properties} + * object, which in practice is the Jython registry. + * + * @param registry + */ private void findAllPackages(Properties registry) { - String paths = registry.getProperty("python.packages.paths", - "java.class.path,sun.boot.class.path"); - String directories = registry.getProperty( - "python.packages.directories", "java.ext.dirs"); - String fakepath = registry - .getProperty("python.packages.fakepath", null); + /* + * python.packages.directories defines a sequence of property names. Each property name is a + * path string. The default setting causes directories and JARs on the classpath and in the + * JRE (before Java 9) to be sources of Python packages. + */ + String defaultPaths = "java.class.path,sun.boot.class.path"; + String paths = registry.getProperty("python.packages.paths", defaultPaths); StringTokenizer tok = new StringTokenizer(paths, ","); while (tok.hasMoreTokens()) { + // Each property name is a path string containing directories String entry = tok.nextToken().trim(); String tmp = registry.getProperty(entry); if (tmp == null) { continue; } + // Each path may be a mixture of directory and JAR specifiers (source of packages) addClassPath(tmp); } + /* + * python.packages.directories defines a sequence of property names. Each property name is a + * path string, in which the elements are directories. Each directory contains JAR/ZIP files + * that are to be a source of Python packages. By default, these directories are those where + * the JVM stores its optional packages as JARs (a mechanism withdrawn in Java 9). + */ + String directories = registry.getProperty("python.packages.directories", "java.ext.dirs"); tok = new StringTokenizer(directories, ","); while (tok.hasMoreTokens()) { + // Each property name is a path string containing directories String entry = tok.nextToken().trim(); String tmp = registry.getProperty(entry); if (tmp == null) { continue; } + // Add the JAR/ZIP archives found in those directories to the search path for packages addJarPath(tmp); } + /* + * python.packages.fakepath defines a sequence of directories and JARs that are to be + * sources of Python packages. + */ + String fakepath = registry.getProperty("python.packages.fakepath", null); if (fakepath != null) { addClassPath(fakepath); } @@ -143,15 +174,13 @@ } @Override - public PyList doDir(PyJavaPackage jpkg, boolean instantiate, - boolean exclpkgs) { + public PyList doDir(PyJavaPackage jpkg, boolean instantiate, boolean exclpkgs) { PyList basic = basicDoDir(jpkg, instantiate, exclpkgs); PyList ret = new PyList(); doDir(this.searchPath, ret, jpkg, instantiate, exclpkgs); PySystemState system = Py.getSystemState(); - if (system.getClassLoader() == null) { doDir(system.path, ret, jpkg, instantiate, exclpkgs); } @@ -166,13 +195,11 @@ } PySystemState system = Py.getSystemState(); - - if (system.getClassLoader() == null - && packageExists(Py.getSystemState().path, pkg, name)) { + if (system.getClassLoader() == null && packageExists(Py.getSystemState().path, pkg, name)) { return true; + } else { + return false; } - - return false; } } -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Sun Nov 25 03:08:35 2018 From: jython-checkins at python.org (jeff.allen) Date: Sun, 25 Nov 2018 08:08:35 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fix_star-import_on_Java_9_?= =?utf-8?q?for_selected_modules=2C_partially_addresses_=232362=2E?= Message-ID: <20181125080835.1.7BF9C3106A141132@mg.python.org> https://hg.python.org/jython/rev/f662c11b87a0 changeset: 8198:f662c11b87a0 user: Jeff Allen date: Sun Nov 25 07:24:37 2018 +0000 summary: Fix star-import on Java 9 for selected modules, partially addresses #2362. CachedJarsPackageManager now creates PyJavaPackage objects with class lists, for packages in modules, as we already do for JARs. This is used, when the "jrt:" filesystem exists (Java 9), from SysPackageManager::findAllPackages(). Some refactoring and a lot of new comment is included, for intelligibility in this and further work. No cache file is created at present, and we process only a configured list of modules, both shortcomings we may address in further work. An update of asm to 7.0 is necessary to parse the accessibility information from Java runtime classes without instantiating them. files: Lib/test/import_nonexistent.py | 3 + Lib/test/import_star_from_java.py | 13 +- Lib/test/test_import_jy.py | 3 - build.gradle | 7 +- build.xml | 18 +- extlibs/asm-5.2.jar | Bin extlibs/asm-7.0.jar | Bin extlibs/asm-commons-5.2.jar | Bin extlibs/asm-commons-7.0.jar | Bin extlibs/asm-util-5.2.jar | Bin extlibs/asm-util-7.0.jar | Bin registry | 9 +- src/org/python/core/PyJavaPackage.java | 36 +- src/org/python/core/packagecache/CachedJarsPackageManager.java | 431 +++++++-- src/org/python/core/packagecache/PackageManager.java | 62 +- src/org/python/core/packagecache/PathPackageManager.java | 14 +- src/org/python/core/packagecache/SysPackageManager.java | 82 +- 17 files changed, 477 insertions(+), 201 deletions(-) diff --git a/Lib/test/import_nonexistent.py b/Lib/test/import_nonexistent.py --- a/Lib/test/import_nonexistent.py +++ b/Lib/test/import_nonexistent.py @@ -1,3 +1,6 @@ +# Test material for: +# SecurityManagerTest.test_nonexistent_import_with_security (test_java_integration.py) + try: import nonexistent_module except ImportError: diff --git a/Lib/test/import_star_from_java.py b/Lib/test/import_star_from_java.py --- a/Lib/test/import_star_from_java.py +++ b/Lib/test/import_star_from_java.py @@ -1,4 +1,13 @@ -from java.util.regex import * - +# Test material for ImpTestCase.test_import_star (test_import_jy.py) +# +from java.util.regex import * # Module: java.base p = Pattern.compile("foo") assert p.flags() == 0 + +from java.sql import * # Module: java.sql +d = Date(1541492230300L) +assert str(d) == '2018-11-06' + +from java.awt import * # Module: java.desktop +assert Color(255,0,255) == Color.MAGENTA + diff --git a/Lib/test/test_import_jy.py b/Lib/test/test_import_jy.py --- a/Lib/test/test_import_jy.py +++ b/Lib/test/test_import_jy.py @@ -172,9 +172,6 @@ # causes a stack overflow if the bug occurs self.assertRaises(Exception, getattr, anygui, 'abc') - @unittest.skipIf(test_support.get_java_version() >= (9,), - "Fails on Java 9+. See b.j.o. issue #2362") # FIXME - # Probably related to Java modules: ensure also works outside java.base def test_import_star(self): self.assertEquals(0, subprocess.call( [sys.executable, test_support.findfile("import_star_from_java.py")])) diff --git a/build.gradle b/build.gradle --- a/build.gradle +++ b/build.gradle @@ -149,10 +149,9 @@ implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.14' - // pin to ASM 5.2 until we upgrade compilation - implementation group: 'org.ow2.asm', name: 'asm', version: '5.2' - implementation group: 'org.ow2.asm', name: 'asm-commons', version: '5.2' - implementation group: 'org.ow2.asm', name: 'asm-util', version: '5.2' + implementation group: 'org.ow2.asm', name: 'asm', version: '7.0' + implementation group: 'org.ow2.asm', name: 'asm-commons', version: '7.0' + implementation group: 'org.ow2.asm', name: 'asm-util', version: '7.0' api group: 'com.google.guava', name: 'guava', version: '22.0-android' implementation group: 'com.ibm.icu', name: 'icu4j', version: '59.1' diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -148,10 +148,9 @@ - - - - + + + @@ -174,8 +173,8 @@ - - + + @@ -551,10 +550,9 @@ - - - - + + + diff --git a/extlibs/asm-5.2.jar b/extlibs/asm-5.2.jar deleted file mode 100644 index aea11818d5181162d58cb2c206cd2243f9357b30..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch [stripped] diff --git a/extlibs/asm-7.0.jar b/extlibs/asm-7.0.jar new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2cf38f46167192e6d65c4906c77cd7e3dce5598d GIT binary patch [stripped] diff --git a/extlibs/asm-commons-5.2.jar b/extlibs/asm-commons-5.2.jar deleted file mode 100644 index cdd2e45c99bdeceafb8cb78a0fa195a2cdb53a37..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch [stripped] diff --git a/extlibs/asm-commons-7.0.jar b/extlibs/asm-commons-7.0.jar new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ba0de6cd49dc34cb2ea5423b1a16f1122a44bdca GIT binary patch [stripped] diff --git a/extlibs/asm-util-5.2.jar b/extlibs/asm-util-5.2.jar deleted file mode 100644 index 686c3f0d04245fc1ed2d6459d2ad265033e0d62d..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch [stripped] diff --git a/extlibs/asm-util-7.0.jar b/extlibs/asm-util-7.0.jar new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..47717e5d18f9b3a6df1300c9872541b3c895d2bc GIT binary patch [stripped] diff --git a/registry b/registry --- a/registry +++ b/registry @@ -23,9 +23,14 @@ # Properties to check for initializing and updating the package cache # Values shown here are those hard-coded in Jython's cache manager. # Treat JARs on the classpath and (before Java 9) in the JRE as a source of Python packages. -#python.packages.paths = java.class.path, sun.boot.class.path +#python.packages.paths = java.class.path, sun.boot.class.path # before Java 9 +#python.packages.paths = java.class.path # from Java 9 # Treat installed optional (Java) packages as source of Python packages (before Java 9) -#python.packages.directories = java.ext.dirs +#python.packages.directories = java.ext.dirs # before Java 9 +#python.packages.directories # undefined from Java 9 +# Treat the following Java modules as sources of of Python packages (from Java 9) +#python.packages.modules = java.base, java.desktop, java.logging, java.se, java.sql, java.xml + # Set verbosity to error, warning, message, comment, or debug # for varying levels of informative messages from Jython. Normally diff --git a/src/org/python/core/PyJavaPackage.java b/src/org/python/core/PyJavaPackage.java --- a/src/org/python/core/PyJavaPackage.java +++ b/src/org/python/core/PyJavaPackage.java @@ -5,7 +5,7 @@ import org.python.core.packagecache.PackageManager; -import java.util.StringTokenizer; +import java.util.Collection; /** A representation of java package. */ public class PyJavaPackage extends PyObject implements Traverseproc { @@ -48,13 +48,25 @@ return addPackage(name, null); } + /** + * From a dotted package name {@code a.b.c} interpreted relative to this package {@code t}, + * ensure that {@code a} is in the dictionary of {@code t} and then recursively process the + * remainder {@code b.c} relative to {@code a}, finally returning the {@link #PyJavaPackage} of + * {@code t.a.b.c}. In the case where the initial package name is just {@code a}, no dots, the + * method simply ensures {@code a} is entered in {@code t}, and returns the + * {@link PyJavaPackage} of {@code t.a}. + * + * @param name a package name + * @param jarfile becomes the {@code __file__} attribute + * @return the {@link PyJavaPackage} of the package named + */ public PyJavaPackage addPackage(String name, String jarfile) { int dot = name.indexOf('.'); String firstName = name; - String lastName = null; + String remainder = null; if (dot != -1) { firstName = name.substring(0, dot); - lastName = name.substring(dot + 1, name.length()); + remainder = name.substring(dot + 1, name.length()); } firstName = firstName.intern(); PyJavaPackage p = (PyJavaPackage) __dict__.__finditem__(firstName); @@ -63,13 +75,12 @@ p = new PyJavaPackage(pname, __mgr__, jarfile); __dict__.__setitem__(firstName, p); } else { - // this code is ok here, because this is not needed for - // a top level package + // this code is ok here, because this is not needed for a top level package if (jarfile == null || !jarfile.equals(p.__file__)) { p.__file__ = null; } } - return lastName != null ? p.addPackage(lastName, jarfile) : p; + return remainder != null ? p.addPackage(remainder, jarfile) : p; } public PyObject addClass(String name, Class c) { @@ -79,15 +90,14 @@ } /** - * Add statically known classes. + * Add the classes named to this package, but with only a placeholder value. These names are + * statically known, typically from processing JAR files on the class path. * - * @param classes their names as comma-separated string + * @param classes the names as strings */ - public void addPlaceholders(String classes) { - StringTokenizer tok = new StringTokenizer(classes, ",@"); - while (tok.hasMoreTokens()) { - String p = tok.nextToken(); - String name = p.trim().intern(); + public void addPlaceholders(Collection classes) { + for (String name : classes) { + name = name.intern(); if (clsSet.__finditem__(name) == null) { clsSet.__setitem__(name, Py.One); } diff --git a/src/org/python/core/packagecache/CachedJarsPackageManager.java b/src/org/python/core/packagecache/CachedJarsPackageManager.java --- a/src/org/python/core/packagecache/CachedJarsPackageManager.java +++ b/src/org/python/core/packagecache/CachedJarsPackageManager.java @@ -4,6 +4,7 @@ package org.python.core.packagecache; import org.python.core.Options; +import org.python.core.PyJavaPackage; import org.python.util.Generic; import java.io.BufferedInputStream; @@ -19,7 +20,15 @@ import java.lang.reflect.Modifier; import java.net.URL; import java.net.URLConnection; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; import java.security.AccessControlException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -69,7 +78,7 @@ * * @param name class/pkg name * @param pkg if true, name refers to a pkg - * @return true if name must be filtered out + * @return {@code true} if name should be filtered out */ protected boolean filterByName(String name, boolean pkg) { return name.indexOf('$') != -1; @@ -78,101 +87,138 @@ /** * Filter class by access perms helper method - hook. The default implementation is used by * {@link #addJarToPackages} in order to filter out non-public classes. Should be used or - * overridden by derived classes too. Also to be used in {@link #doDir}. Access perms can be - * read with {@link #checkAccess}. + * overridden by derived classes too. Also to be used in {@link #doDir}. Access permissions can + * be read with {@link #checkAccess}. * * @param name class name * @param acc class access permissions as int - * @return true if name must be filtered out + * @return {@code true} if name should be filtered out */ protected boolean filterByAccess(String name, int acc) { return (acc & Modifier.PUBLIC) != Modifier.PUBLIC; } + /** + * Set {@code true} whenever the cache index is modified (needs ultimately to be re-written). + */ private boolean indexModified; - private Map jarfiles; + /** + * Map from some source of class definitions to the name of a file used to cache lists of + * classes in that source. {@code null} if cache is not operating. (This source must have a time + * last modified so we may check that the cache is valid). + */ + private Map index; - private static String listToString(List list) { - int n = list.size(); - StringBuilder ret = new StringBuilder(); - for (int i = 0; i < n; i++) { - ret.append(list.get(i)); - if (i < n - 1) { - ret.append(","); - } - } - return ret.toString(); - } + /** + * Process one entry from a JAR/ZIP, and if the entry is a (qualifying) Java class, add its name + * to that package's entry in the map passed in. A class is added only if + * {@link #filterByName(String, boolean)} returns {@code true}. We add it to the accessible or + * inaccessible list according to whether {@link #filterByAccess(String, int)} returns + * {@code true} for the name and permissions of that class. + * + * @param zipPackages map (to update) from package name to class names by accessibility + * @param entry possibly representing a class + * @param zip the JAR or ZIP to which the entry belongs + * @throws IOException + */ + private void addZipEntry(Map zipPackages, ZipEntry entry, ZipInputStream zip) + throws IOException { - /** Add a single class from zipFile to zipPackages. Only add valid, public classes. */ - private void addZipEntry(Map[]> zipPackages, ZipEntry entry, - ZipInputStream zip) throws IOException { String name = entry.getName(); // System.err.println("entry: "+name); - if (!name.endsWith(".class")) { - return; - } + if (name.endsWith(".class")) { - char sep = '/'; - int breakPoint = name.lastIndexOf(sep); - if (breakPoint == -1) { - breakPoint = name.lastIndexOf('\\'); - sep = '\\'; - } + // Split off the bare class name + char sep = '/'; + int slash = name.lastIndexOf(sep); + if (slash == -1) { + if ((slash = name.lastIndexOf('\\')) >= 0) { + sep = '\\'; // Shouldn't be necessary according to standards, but is. + } + } + String className = name.substring(slash + 1, name.length() - 6); - String packageName; - if (breakPoint == -1) { - packageName = ""; - } else { - packageName = name.substring(0, breakPoint).replace(sep, '.'); - } - - String className = name.substring(breakPoint + 1, name.length() - 6); + // Check acceptable name: in practice, this is used to ignore inner classes. + if (!filterByName(className, false)) { + // File this class by name against the package + String packageName = slash == -1 ? "" : name.substring(0, slash).replace(sep, '.'); + ClassList classes = zipPackages.get(packageName); - if (filterByName(className, false)) { - return; - } + if (classes == null) { + // It wasn't in the map so add it + classes = new ClassList(); + zipPackages.put(packageName, classes); + } - List[] vec = zipPackages.get(packageName); - if (vec == null) { - vec = createGenericStringListArray(); - zipPackages.put(packageName, vec); - } - int access = checkAccess(zip); - if ((access != -1) && !filterByAccess(name, access)) { - vec[0].add(className); - } else { - vec[1].add(className); + // Put the class on the right list + int access = checkAccess(zip); + if ((access != -1) && !filterByAccess(name, access)) { + classes.accessible.add(className); + } else { + classes.inaccessible.add(className); + } + } } } - @SuppressWarnings("unchecked") - private List[] createGenericStringListArray() { - return new List[] {Generic.list(), Generic.list()}; + /** Used when representing the classes in a particular package. */ + private static class ClassList { + + /** Class names of accessible classes */ + List accessible = new ArrayList(); + /** Class names of inaccessible classes */ + List inaccessible = new ArrayList(); + + /** Retrieve the two lists in "cache file" format {@code A,B,C[@D,E]}. */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + appendList(buf, accessible); + if (inaccessible.size() > 0) { + buf.append('@'); + appendList(buf, inaccessible); + } + return buf.toString(); + } + + private static void appendList(StringBuilder buf, List names) { + if (names.size() > 0) { + for (String n : names) { + buf.append(n).append(','); + } + // Deal with surplus comma + buf.deleteCharAt(buf.length() - 1); + } + } } - /** Extract all of the packages in a single jar file */ + /** + * Detect all of the packages in an open JAR/ZIP file, that contain any classes for which + * {@link #filterByName(String, boolean)} returns {@code true}. For each such package, list the + * (surviving) classes in two lists: accessible and inaccessible, which is judged according to + * {@link #filterByAccess(String, int)}. The returned map is from package to these two lists, + * now comma-separated lists, with an '@' between them. + * + * @param jarin an open JAR/ZIP file + * @return map from package name to comma/@-separated list of class names + * @throws IOException + */ private Map getZipPackages(InputStream jarin) throws IOException { - Map[]> zipPackages = Generic.map(); + Map zipPackages = Generic.map(); ZipInputStream zip = new ZipInputStream(jarin); + ZipEntry entry; - ZipEntry entry; while ((entry = zip.getNextEntry()) != null) { addZipEntry(zipPackages, entry, zip); zip.closeEntry(); } - // Turn each vector into a comma-separated String + // Turn each ClassList into a comma/@-separated String Map transformed = Generic.map(); - for (Entry[]> kv : zipPackages.entrySet()) { - List[] vec = kv.getValue(); - String classes = listToString(vec[0]); - if (vec[1].size() > 0) { - classes += '@' + listToString(vec[1]); - } - transformed.put(kv.getKey(), classes); + for (Entry kv : zipPackages.entrySet()) { + transformed.put(kv.getKey(), kv.getValue().toString()); } return transformed; @@ -206,30 +252,46 @@ } /** - * Gathers classes info from jar specified by File jarfile. Eventually just using previously - * cached info. Eventually updated info is (re-)cached if param cache is true. Persistent cache - * storage access goes through inOpenCacheFile() and outCreateCacheFile(). + * Gathers package and class lists from a jar specified by a {@code File}. Eventually just using + * previously cached info. Eventually updated info is (re-)cached if param cache is true. + * Persistent cache storage access goes through inOpenCacheFile() and outCreateCacheFile(). */ public void addJarToPackages(File jarfile, boolean cache) { addJarToPackages(null, jarfile, cache); } - private void addJarToPackages(URL jarurl, File jarfile, boolean cache) { + /** + * Create (or ensure we have) a {@link PyJavaPackage}, for each package in a jar specified by a + * file or URL, descending from {@link PackageManager#topLevelPackage} in this + * {@link PackageManager} instance. Ensure that the class list in each package is updated with + * the classes this JAR supplies to it. This information may be from a previously cached account + * of the JAR, if the last-modified time of the JAR matches a cached value. Otherwise, it will + * be obtained by inspecting the JAR, and a new cache will be written (if requested). Eventually + * updated info is (re-)cached if param cache is true. Persistent cache storage access goes + * through {@link #inOpenCacheFile(String)} and {@link #outCreateCacheFile(JarXEntry, boolean)}. + * + * @param jarurl identifying the JAR if {@code jarfile} is {@code null} + * @param jarfile identifying the JAR + * @param writeCache re-write the cache if it was out of date (and caching is active). + */ + private void addJarToPackages(URL jarurl, File jarfile, boolean writeCache) { try { - boolean caching = this.jarfiles != null; - + boolean readCache = this.index != null; + writeCache &= readCache; URLConnection jarconn = null; boolean localfile = true; if (jarfile == null) { + // We were not given a File, so the URL must be reliable (but maybe not a file) jarconn = jarurl.openConnection(); + // The following comment may be out of date. Also 2 reasons or just the bug? /* * This is necessary because 'file:' url-connections always return 0 through * getLastModified (bug?). And in order to handle localfiles (from urls too) * uniformly. */ if (jarconn.getURL().getProtocol().equals("file")) { - // ??pending: need to use java2 URLDecoder.decode? + // Although given as a URL, this *is* a file. String jarfilename = jarurl.getFile(); jarfilename = jarfilename.replace('/', File.separatorChar); jarfile = new File(jarfilename); @@ -238,10 +300,12 @@ } } + // Claimed JAR does not exist. Silently ignore. if (localfile && !jarfile.exists()) { return; } + // The classes to discover are in a local JAR file. They go in this map: Map zipPackages = null; long mtime = 0; @@ -249,8 +313,9 @@ JarXEntry entry = null; boolean brandNew = false; - if (caching) { + if (readCache) { + // Get the name and last modified time of the actual JAR on disk. if (localfile) { mtime = jarfile.lastModified(); jarcanon = jarfile.getCanonicalPath(); @@ -259,11 +324,14 @@ jarcanon = jarurl.toString(); } - entry = this.jarfiles.get(jarcanon); + // The canonical name is our key in the (in memory) index to the cache file. + entry = this.index.get(jarcanon); - if ((entry == null || !(new File(entry.cachefile).exists())) && cache) { + if (writeCache && (entry == null || !(new File(entry.cachefile).exists()))) { + // We intend to use a cache but there is no valid existing file. comment("processing new jar, '" + jarcanon + "'"); + // Create a base-name for the cache file String jarname; if (localfile) { jarname = jarfile.getName(); @@ -276,22 +344,28 @@ } jarname = jarname.substring(0, jarname.length() - 4); + // Create a new (or replacement) index entry. Remember to create the file. entry = new JarXEntry(jarname); - this.jarfiles.put(jarcanon, entry); - + this.index.put(jarcanon, entry); brandNew = true; } - if (mtime != 0 && entry != null && entry.mtime == mtime) { + // If the source has a date and the cache matches, create the map we need from it. + if (entry != null && mtime != 0 && entry.mtime == mtime) { zipPackages = readCacheFile(entry, jarcanon); } + } - } + /* + * At this point, we will have the package map from the cache if it was valid, and a + * cache is in use generally in this package manager. + */ if (zipPackages == null) { - caching = caching && cache; + // We'll have to read the actual JAR file - if (caching) { + if (writeCache) { + // Update the index entry for the cache file we shall eventually write. this.indexModified = true; if (entry.mtime != 0) { comment("processing modified jar, '" + jarcanon + "'"); @@ -299,37 +373,53 @@ entry.mtime = mtime; } + // Create the package-to-class mapping from whatever stream. InputStream jarin = null; try { if (jarconn == null) { jarin = new BufferedInputStream(new FileInputStream(jarfile)); } else { + // We were given a URL originally so use that. jarin = jarconn.getInputStream(); } zipPackages = getZipPackages(jarin); + } finally { if (jarin != null) { jarin.close(); } } - if (caching) { + if (writeCache) { + // Write what we created out to a cache file (new or updated) writeCacheFile(entry, jarcanon, zipPackages, brandNew); } } + /* + * We now have the package map we need (from a cache or by construction). Now create or + * update corresponding package objects with the discovered classes (named, but not as + * PyObjects). + */ addPackages(zipPackages, jarcanon); + } catch (IOException ioe) { - // silently skip any bad directories + // Skip the bad JAR with a message warning("skipping bad jar, '" + (jarfile != null ? jarfile.toString() : jarurl.toString()) + "'"); } - } - private void addPackages(Map zipPackages, String jarfile) { - for (Entry entry : zipPackages.entrySet()) { + /** + * From a map of package name to comma/@-separated list of classes therein, relating to a + * particular JAR file, create a {@link PyJavaPackage} for each package. + * + * @param packageToClasses source of mappings + * @param jarfile becomes the __file__ attribute of the {@link PyJavaPackage} + */ + private void addPackages(Map packageToClasses, String jarfile) { + for (Entry entry : packageToClasses.entrySet()) { String pkg = entry.getKey(); String classes = entry.getValue(); @@ -343,8 +433,8 @@ } /** - * Read in cache file storing package info for a single jar. Return null and delete this - * cachefile if it is invalid. + * Read in cache file storing package info for a single jar. Return null and delete this cache + * file if it is invalid. */ @SuppressWarnings("empty-statement") private Map readCacheFile(JarXEntry entry, String jarcanon) { @@ -398,7 +488,14 @@ } } - /** Write a cache file storing package info for a single jar. */ + /** + * Write a cache file storing package info for a single JAR. * + * + * @param entry in {@link #index} corresponding to the JAR + * @param jarcanon canonical name of the JAR (used as key into {@link #index}) + * @param zipPackages a map from package name to class names for that pacjkage in the JAR + * @param brandNew if the cache file must be created (vs. being re-written) + */ private void writeCacheFile(JarXEntry entry, String jarcanon, Map zipPackages, boolean brandNew) { DataOutputStream ostream = null; @@ -430,6 +527,80 @@ } } + /** Scan a module from the modular JVM, creating package objects. */ + protected void addModule(Path modulePath) { + try { + Map packages = getModularPackages(modulePath); + addPackages(packages, modulePath.toUri().toString()); + } catch (IOException ioe) { + warning("skipping bad module, '" + modulePath + "'" + ioe); + } + } + + /** + * Detect all of the packages in a single module, that contain any classes for which + * {@link #filterByName(String, boolean)} returns {@code true}. For each such package, list the + * (surviving) classes in two lists: accessible and inaccessible, which is judged according to + * {@link #filterByAccess(String, int)}. The returned map is from package to these two lists, + * now comma-separated lists, with an '@' between them. + */ + private Map getModularPackages(Path modulePath) throws IOException { + + final Map modPackages = Generic.map(); + + FileVisitor visitor = new SimpleFileVisitor() { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + //System.out.println(" visitFile:" + file); + + // file has at least 4 parts /modules/[module]/ ... /[name].class + int n = file.getNameCount(); + // Apply name and access tests and conditionally add to modPackages + String fileName = file.getFileName().toString(); + if (fileName.endsWith(".class") && n > 3) { + // Split off the bare class name + String className = fileName.substring(0, fileName.length() - 6); + + // Check acceptable name: in practice, this is used to ignore inner classes. + if (!filterByName(className, false)) { + // File this class by name against the package + String packageName = file.subpath(2, n - 1).toString().replace('/', '.'); + ClassList classes = modPackages.get(packageName); + + if (classes == null) { + // It wasn't in the map so add it + classes = new ClassList(); + modPackages.put(packageName, classes); + } + + // Put the class on the right list + try (InputStream c = Files.newInputStream(file, StandardOpenOption.READ)) { + int access = checkAccess(c); + if ((access != -1) && !filterByAccess(fileName, access)) { + classes.accessible.add(className); + } else { + classes.inaccessible.add(className); + } + } + } + } + return FileVisitResult.CONTINUE; + } + }; + + Files.walkFileTree(modulePath, visitor); + + // Turn each ClassList into a comma/@-separated String + Map transformed = Generic.map(); + for (Entry kv : modPackages.entrySet()) { + transformed.put(kv.getKey(), kv.getValue().toString()); + } + + return transformed; + } + /** * Split up a string into several chunks based on a certain size * @@ -459,12 +630,12 @@ } /** - * Initializes cache. Eventually reads back cache index. Index persistent storage is accessed - * through inOpenIndex(). + * Initialise the cache by reading the index from storage, through {@link #inOpenIndex()}, or by + * creating a new empty one. */ protected void initCache() { this.indexModified = false; - this.jarfiles = Generic.map(); + this.index = Generic.map(); DataInputStream istream = null; try { @@ -478,7 +649,7 @@ String jarcanon = istream.readUTF(); String cachefile = istream.readUTF(); long mtime = istream.readLong(); - this.jarfiles.put(jarcanon, new JarXEntry(cachefile, mtime)); + this.index.put(jarcanon, new JarXEntry(cachefile, mtime)); } } catch (EOFException eof) { // ignore @@ -496,9 +667,14 @@ } } - /** Write back cache index. Index persistent storage is accessed through outOpenIndex(). */ + /** + * Write back cache index. The index is a mapping from each source of class definitions + * to the file where the cache for that source is held. This list is accessed through + * outOpenIndex(). + */ public void saveCache() { - if (jarfiles == null || !indexModified) { + + if (index == null || !indexModified) { return; } @@ -509,7 +685,7 @@ DataOutputStream ostream = null; try { ostream = outOpenIndex(); - for (Entry entry : jarfiles.entrySet()) { + for (Entry entry : index.entrySet()) { String jarcanon = entry.getKey(); JarXEntry xentry = entry.getValue(); ostream.writeUTF(jarcanon); @@ -531,10 +707,15 @@ // hooks for changing cache storage - /** To pass a cachefile id by ref. And for internal use. See outCreateCacheFile. */ + /** + * Class of object used to represent a cache file and last modification time, internally and to + * {@link CachedJarsPackageManager#outCreateCacheFile}. When caching, a {@code JarXEntry} is + * created for each JAR processed for classes, and corresponds to a file in the package cache + * directory. The name is based on the name of the JAR. + */ public static class JarXEntry extends Object { - /** cachefile id */ + /** Specifies the actual cache file once that is created or opened. */ public String cachefile; public long mtime; @@ -551,8 +732,8 @@ } /** - * Open cache index for reading from persistent storage - hook. Must Return null if this is - * absent. This default implementation is part of the off-the-shelf local file-system cache + * Open cache index for reading from persistent storage – hook. Must Return null if this + * is absent. This default implementation is part of the off-the-shelf local file-system cache * implementation. Can be overridden. */ protected DataInputStream inOpenIndex() throws IOException { @@ -566,8 +747,9 @@ } /** - * Open cache index for writing back to persistent storage - hook. This default implementation - * is part of the off-the-shelf local file-system cache implementation. Can be overridden. + * Open cache index for writing back to persistent storage – hook. This default + * implementation is part of the off-the-shelf local file-system cache implementation. Can be + * overridden. */ protected DataOutputStream outOpenIndex() throws IOException { File indexFile = new File(this.cachedir, "packages.idx"); @@ -576,8 +758,8 @@ } /** - * Open cache file for reading from persistent storage - hook. This default implementation is - * part of the off-the-shelf local file-system cache implementation. Can be overridden. + * Open a particular cache file for reading from persistent storage. This default implementation + * is part of the off-the-shelf local file-system cache implementation. Can be overridden. */ protected DataInputStream inOpenCacheFile(String cachefile) throws IOException { return new DataInputStream(new BufferedInputStream(new FileInputStream(cachefile))); @@ -592,40 +774,43 @@ } /** - * Create/open cache file for rewriting back to persistent storage - hook. If create is false, - * cache file is supposed to exist and must be opened for rewriting, entry.cachefile is a valid - * cachefile id. If create is true, cache file must be created. entry.cachefile is a flat - * jarname to be used to produce a valid cachefile id (to be put back in entry.cachefile on - * exit). This default implementation is part of the off-the-shelf local file-system cache - * implementation. Can be overridden. + * Create/open cache file for rewriting back to persistent storage – hook. If + * {@code create} is {@code false}, the cache file is supposed to exist at + * {@code entry.cachefile} and will be opened for rewriting. If {@code create} is {@code true}, + * {@code entry.cachefile} is the base name (e.g. JAR or module name) for a cache to be created, + * and the full name will be put in {@code entry.cachefile} on exit. This default implementation + * is part of the off-the-shelf local file-system cache implementation. It may be overridden to + * provide a different cache medium and use of {@code entry.cachefile}. + * + * @param entry cache file description + * @param create new or use existing file named in {@code entry.cachefile} + * @return stream on which to represent the package to class-list textually + * @throws IOException */ protected DataOutputStream outCreateCacheFile(JarXEntry entry, boolean create) throws IOException { - File cachefile = null; + File file; if (create) { - int index = 1; - String suffix = ""; + // Create a new cache file with a name based on the initial value String jarname = entry.cachefile; - while (true) { - cachefile = new File(this.cachedir, jarname + suffix + ".pkc"); - // System.err.println("try cachefile: "+cachefile); - if (!cachefile.exists()) { - break; - } - suffix = "$" + index; - index += 1; + file = new File(this.cachedir, jarname + ".pkc"); + for (int index = 1; file.exists(); index++) { + // That name is in use: make up another one. + file = new File(this.cachedir, jarname + "$" + index + ".pkc"); } - entry.cachefile = cachefile.getCanonicalPath(); + entry.cachefile = file.getCanonicalPath(); + } else { - cachefile = new File(entry.cachefile); + // Use an existing cache file named in the entry + file = new File(entry.cachefile); } - return new DataOutputStream(new BufferedOutputStream(new FileOutputStream(cachefile))); + return new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); } - /** for default cache (local fs based) implementation */ + /** Directory in which cache files are stored. */ private File cachedir; /** diff --git a/src/org/python/core/packagecache/PackageManager.java b/src/org/python/core/packagecache/PackageManager.java --- a/src/org/python/core/packagecache/PackageManager.java +++ b/src/org/python/core/packagecache/PackageManager.java @@ -13,12 +13,17 @@ import org.python.core.PyStringMap; import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringTokenizer; /** * Abstract package manager. */ public abstract class PackageManager extends Object { + /** Nominal top-level package of all (Java) packages, containing "java", "com", "org", etc.. */ public PyJavaPackage topLevelPackage; public PackageManager() { @@ -117,6 +122,16 @@ return list1; } + /** + * Given the (dotted) name of a package, find the {@link PyJavaPackage} corresponding, by + * navigating from the {@link #topLevelPackage}, successively applying + * {@link PyObject#__findattr__(String)}. This in fact drives the creation of + * {@link PyJavaPackage} objects since it indirectly calls + * {@link #packageExists(String, String)}. + * + * @param name (dotted) package name + * @return the package named + */ public PyObject lookupName(String name) { PyObject top = this.topLevelPackage; do { @@ -139,13 +154,15 @@ } /** - * Creates package/updates statically known classes info. Uses - * {@link PyJavaPackage#addPackage(java.lang.String, java.lang.String) }, + * Create (or ensure we have) a {@link PyJavaPackage} for the named package and add to it the + * names of classes mentioned here. These classes are added as "place holders" only, so they + * become members of it, without being instantiated. This method relies on + * {@link PyJavaPackage#addPackage(java.lang.String, java.lang.String)} and * {@link PyJavaPackage#addPlaceholders}. * * @param name package name - * @param classes comma-separated string - * @param jarfile involved jarfile; can be null + * @param classes comma or @-sign separated string + * @param jarfile involved; can be null * @return created/updated package */ public PyJavaPackage makeJavaPackage(String name, String classes, String jarfile) { @@ -153,14 +170,11 @@ if (name.length() != 0) { p = p.addPackage(name, jarfile); } - - if (classes != null) { - p.addPlaceholders(classes); - } - + p.addPlaceholders(split(classes, ",@")); return p; } + /** ASM visitor class supporting {@link #checkAccess(InputStream)}. */ private static class AccessVisitor extends ClassVisitor { private int class_access; @@ -181,10 +195,10 @@ } /** - * Check that a given stream is a valid Java .class file. And return its access permissions as + * Check that a given stream is a valid Java .class file, and return its access permissions as * an int. */ - static protected int checkAccess(java.io.InputStream cstream) throws IOException { + static protected int checkAccess(InputStream cstream) throws IOException { try { ClassReader reader = new ClassReader(cstream); AccessVisitor visitor = new AccessVisitor(); @@ -195,4 +209,30 @@ } } + /** + * Helper to split a textual list into a list. The semantics are basically those of + * {@code StringTokenizer}, followed by {@code String.trim()} and duplicate removal. + * + * @param target compound string to split into tokens ({@code null} treated as "". + * @param sep characters, any of which will be treated as a token separator + * @return set of tokens trimmed of white space + */ + protected static Set split(String target, String sep) { + Set result = new LinkedHashSet<>(); + if (target != null) { + StringTokenizer tok = new StringTokenizer(target, sep); + while (tok.hasMoreTokens()) { + String entry = tok.nextToken().trim(); + if (entry.length() > 0) { + result.add(entry); + } + } + } + return result; + } + + /** Equivalent to {@code split(target, ",")}. See {@link #split(String, String)}. */ + protected static Set split(String target) { + return split(target, ","); + } } diff --git a/src/org/python/core/packagecache/PathPackageManager.java b/src/org/python/core/packagecache/PathPackageManager.java --- a/src/org/python/core/packagecache/PathPackageManager.java +++ b/src/org/python/core/packagecache/PathPackageManager.java @@ -30,10 +30,12 @@ } /** - * Helper for {@link #packageExists(java.lang.String,java.lang.String)}. Scans for package - * pkg.name the directories in path. + * Helper for {@link #packageExists(String,String)}. Scans the directories in the given path for + * package pkg.name. A directory with a matching name is considered to define a package if it + * contains no Python (source or compiled), or contains a Java .class file (not compiled from + * Python). */ - protected boolean packageExists(PyList path, String pkg, String name) { + protected static boolean packageExists(PyList path, String pkg, String name) { String child = pkg.replace('.', File.separatorChar) + File.separator + name; for (int i = 0; i < path.__len__(); i++) { @@ -46,9 +48,9 @@ try { if (f.isDirectory() && imp.caseok(f, name)) { /* - * Figure out if we have a directory a mixture of python and java or just an - * empty directory (which means Java) or a directory with only Python source - * (which means Python). + * f is a directory matching the package name. This directory is considered to + * define a package if it contains no Python (source or compiled), or contains a + * Java .class file (not compiled from Python). */ PackageExistsFileFilter m = new PackageExistsFileFilter(); f.listFiles(m); diff --git a/src/org/python/core/packagecache/SysPackageManager.java b/src/org/python/core/packagecache/SysPackageManager.java --- a/src/org/python/core/packagecache/SysPackageManager.java +++ b/src/org/python/core/packagecache/SysPackageManager.java @@ -9,7 +9,13 @@ import org.python.core.PySystemState; import java.io.File; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.ProviderNotFoundException; import java.util.Properties; +import java.util.Set; import java.util.StringTokenizer; /** @@ -58,7 +64,13 @@ addJarDir(jdir, cache, cache); } - /** Index the contents of every JAR or ZIP in a directory. */ + /** + * Index the contents of every JAR or ZIP in a directory. + * + * @param jdir direcory containing some JAR or ZIP files + * @param cache + * @param saveCache + */ private void addJarDir(String jdir, boolean cache, boolean saveCache) { File file = new File(jdir); @@ -89,6 +101,7 @@ while (tok.hasMoreTokens()) { // ??pending: do jvms trim? how is interpreted entry=""? String entry = tok.nextToken(); + // Use the extra flag to defer writing out the cache. addJarDir(entry, true, false); } } @@ -100,23 +113,40 @@ * @param registry */ private void findAllPackages(Properties registry) { + + String defaultPaths = "java.class.path"; + String defaultDirectories = ""; + String defaultModules = "java.base,java.desktop,java.logging,java.se,java.sql,java.xml"; + + try { + // Support for the modular JVM (particular packages). + // XXX This may not be our final approach: maybe enumerate all the packages instead? + Set modules = + split(registry.getProperty("python.packages.modules", defaultModules)); + FileSystem jrtfs = FileSystems.getFileSystem(URI.create("jrt:/")); + for (String moduleName : modules) { + Path modulePath = jrtfs.getPath("/modules/" + moduleName); + addModule(modulePath); + } + } catch (ProviderNotFoundException e) { + // Running on a JVM before Java 9: add boot class path and optional extensions. + defaultPaths = "java.class.path,sun.boot.class.path"; + defaultDirectories = "java.ext.dirs"; + } + /* - * python.packages.directories defines a sequence of property names. Each property name is a - * path string. The default setting causes directories and JARs on the classpath and in the - * JRE (before Java 9) to be sources of Python packages. + * python.packages.paths defines a sequence of property names. Each property is a path + * string. The default setting causes directories and JARs on the classpath and in the JRE + * (before Java 9) to be sources of Python packages. */ - String defaultPaths = "java.class.path,sun.boot.class.path"; - String paths = registry.getProperty("python.packages.paths", defaultPaths); - StringTokenizer tok = new StringTokenizer(paths, ","); - while (tok.hasMoreTokens()) { - // Each property name is a path string containing directories - String entry = tok.nextToken().trim(); - String tmp = registry.getProperty(entry); - if (tmp == null) { - continue; + Set paths = split(registry.getProperty("python.packages.paths", defaultPaths)); + for (String name : paths) { + // Each property is a path string containing directories + String path = registry.getProperty(name); + if (path != null) { + // Each path may be a mixture of directory and JAR specifiers (source of packages) + addClassPath(path); } - // Each path may be a mixture of directory and JAR specifiers (source of packages) - addClassPath(tmp); } /* @@ -125,17 +155,15 @@ * that are to be a source of Python packages. By default, these directories are those where * the JVM stores its optional packages as JARs (a mechanism withdrawn in Java 9). */ - String directories = registry.getProperty("python.packages.directories", "java.ext.dirs"); - tok = new StringTokenizer(directories, ","); - while (tok.hasMoreTokens()) { - // Each property name is a path string containing directories - String entry = tok.nextToken().trim(); - String tmp = registry.getProperty(entry); - if (tmp == null) { - continue; + Set directories = + split(registry.getProperty("python.packages.directories", defaultDirectories)); + for (String name : directories) { + // Each property defines a path string containing directories + String path = registry.getProperty(name); + if (path != null) { + // Add the JAR/ZIP archives in those directories to the search path for packages + addJarPath(path); } - // Add the JAR/ZIP archives found in those directories to the search path for packages - addJarPath(tmp); } /* @@ -194,8 +222,8 @@ return true; } - PySystemState system = Py.getSystemState(); - if (system.getClassLoader() == null && packageExists(Py.getSystemState().path, pkg, name)) { + PySystemState sys = Py.getSystemState(); + if (sys.getClassLoader() == null && packageExists(sys.path, pkg, name)) { return true; } else { return false; -- Repository URL: https://hg.python.org/jython